@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.
- package/README.md +44 -29
- package/lib/package.json +6 -5
- package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
- package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
- package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
- package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
- package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
- package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
- package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
- package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
- package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
- package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
- package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
- package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
- package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
- package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
- package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
- package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
- package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
- package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
- package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
- package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
- package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
- package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
- package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
- package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
- package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
- package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
- package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
- package/lib/public/assets/derive-UnLRp4r3.js +1 -0
- package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
- package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
- package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
- package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
- package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
- package/lib/public/assets/index-B0JeuZbg.js +15 -0
- package/lib/public/assets/index-B0gQkEg0.js +219 -0
- package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
- package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
- package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
- package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
- package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
- package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
- package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
- package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
- package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
- package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
- package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
- package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
- package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
- package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
- package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
- package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
- package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
- package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
- package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
- package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
- package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
- package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
- package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
- package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
- package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
- package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
- package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
- package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
- package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
- package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
- package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +16 -12
- package/lib/src/InternalHttpClient.js +27 -5
- package/lib/src/XenonCapabilityManager.js +10 -3
- package/lib/src/app/dashboardPluginLink.js +117 -0
- package/lib/src/app/index.js +13 -34
- package/lib/src/app/openapi/control.yaml +6 -4
- package/lib/src/app/openapi/grid.yaml +8 -2
- package/lib/src/app/openapi/identity.yaml +103 -31
- package/lib/src/app/openapi/platform.yaml +33 -15
- package/lib/src/app/openapi/sessions.yaml +12 -3
- package/lib/src/app/routers/apikeys.js +14 -2
- package/lib/src/app/routers/auth.js +31 -6
- package/lib/src/app/routers/control.js +13 -5
- package/lib/src/app/routers/dashboard.js +42 -27
- package/lib/src/app/routers/grid.js +5 -3
- package/lib/src/app/routers/profile.js +21 -0
- package/lib/src/app/routers/users.js +12 -6
- package/lib/src/app/swagger.js +2 -0
- package/lib/src/app/ws/h264StreamWs.js +10 -1
- package/lib/src/app/ws/nodeSocketRelay.js +77 -11
- package/lib/src/config.js +1 -0
- package/lib/src/dashboard/commandLogFields.js +42 -0
- package/lib/src/dashboard/commands.js +5 -5
- package/lib/src/dashboard/event-manager.js +64 -204
- package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
- package/lib/src/dashboard/services/failureCategories.js +264 -0
- package/lib/src/dashboard/sessionCommandSummary.js +39 -0
- package/lib/src/data-service/device-store.js +14 -1
- package/lib/src/data-service/prisma-store.js +19 -7
- package/lib/src/data-service/web-config-service.js +7 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
- package/lib/src/device-managers/IOSDeviceManager.js +10 -0
- package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
- package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
- package/lib/src/device-utils.js +29 -3
- package/lib/src/gateway/defaultGateway.js +1 -1
- package/lib/src/gateway/forwardToNode.js +6 -11
- package/lib/src/gateway/healReport.js +87 -0
- package/lib/src/gateway/hubSessionToken.js +27 -4
- package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
- package/lib/src/gateway/sessionGateway.js +12 -2
- package/lib/src/generated/client/edge.js +4 -3
- package/lib/src/generated/client/index-browser.js +1 -0
- package/lib/src/generated/client/index.d.ts +32 -0
- package/lib/src/generated/client/index.js +4 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +3 -0
- package/lib/src/generated/client/wasm.js +1 -0
- package/lib/src/helpers/outboundProxy.js +219 -0
- package/lib/src/index.js +21 -0
- package/lib/src/interceptors/CommandInterceptor.js +256 -196
- package/lib/src/interceptors/xenonScripts.js +28 -7
- package/lib/src/logger.js +3 -1
- package/lib/src/middleware/authMiddleware.js +15 -3
- package/lib/src/middleware/commandCaller.js +3 -3
- package/lib/src/middleware/verifyCredential.js +58 -1
- package/lib/src/plugin.js +16 -6
- package/lib/src/scripts/database-check.js +3 -3
- package/lib/src/scripts/initialize-database.js +35 -33
- package/lib/src/scripts/run-migrations.js +1079 -27
- package/lib/src/services/AICommandService.js +9 -2
- package/lib/src/services/AIService.js +192 -42
- package/lib/src/services/ApiKeyService.js +16 -5
- package/lib/src/services/EmailService.js +29 -0
- package/lib/src/services/InterceptorService.js +6 -2
- package/lib/src/services/OrphanSweeper.js +8 -0
- package/lib/src/services/SelectorStateService.js +11 -7
- package/lib/src/services/SelectorVerificationJob.js +21 -6
- package/lib/src/services/ServerManager.js +11 -1
- package/lib/src/services/SessionLifecycleService.js +118 -44
- package/lib/src/services/SocketClient.js +11 -0
- package/lib/src/services/SocketServer.js +380 -45
- package/lib/src/services/TeamService.js +5 -1
- package/lib/src/services/TracingService.js +171 -60
- package/lib/src/services/UserService.js +14 -3
- package/lib/src/services/UserSessionService.js +16 -4
- package/lib/src/services/bug-report/video-slice.js +4 -1
- package/lib/src/services/device-access/leaseOverride.js +7 -5
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
- package/lib/src/services/healing/HealEtalonService.js +3 -2
- package/lib/src/services/healing/HealingOrchestrator.js +57 -72
- package/lib/src/services/healing/OcrHealingProvider.js +41 -64
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
- package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
- package/lib/src/services/healing/fingerprintIdentity.js +126 -0
- package/lib/src/services/healing/resilioPath.js +274 -0
- package/lib/src/services/identity/identityChanges.js +41 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
- package/lib/src/services/logcat/deviceLogBook.js +164 -0
- package/lib/src/services/logcat/iosDriverLog.js +178 -0
- package/lib/src/services/ocr/ocrData.js +118 -0
- package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
- package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
- package/lib/src/services/passwordResetLink.js +50 -9
- package/lib/src/services/passwordResetMode.js +3 -1
- package/lib/src/services/publicUrl.js +45 -0
- package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
- package/lib/src/services/session/sessionIdentity.js +5 -3
- package/lib/src/services/session/shutdownReason.js +26 -0
- package/lib/src/services/settings/aiEngineSettings.js +219 -0
- package/lib/src/services/token/JwtKeyService.js +8 -2
- package/lib/src/services/token/mcpTokenTtl.js +27 -0
- package/lib/src/services/token/mintedLifetime.js +51 -0
- package/lib/src/sessions/LocalSession.js +29 -1
- package/lib/src/sessions/RemoteSession.js +12 -2
- package/lib/src/sessions/SessionManager.js +4 -1
- package/lib/src/sessions/sessionMemory.js +25 -0
- package/lib/test/helpers/driver-log.js +38 -0
- package/lib/test/helpers/fake-ai-provider.js +188 -0
- package/lib/test/helpers/fake-http-proxy.js +106 -0
- package/lib/test/helpers/loki-stores.js +33 -0
- package/lib/test/helpers/otlp-probe.js +107 -0
- package/lib/test/helpers/page-sources.js +164 -0
- package/lib/test/helpers/scratch-database.js +57 -13
- package/lib/test/integration/androidDevices.spec.js +1 -1
- package/lib/test/integration/audit-ingest.spec.js +10 -10
- package/lib/test/integration/auth-flow.spec.js +9 -14
- package/lib/test/integration/auth-rate-limit.spec.js +6 -3
- package/lib/test/integration/bug-report-route.spec.js +22 -2
- package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
- package/lib/test/integration/forgot-password.spec.js +17 -18
- package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
- package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
- package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
- package/lib/test/integration/migration.spec.js +6 -1
- package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
- package/lib/test/integration/ports-router.spec.js +4 -4
- package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
- package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
- package/lib/test/integration/profile-tokens.spec.js +10 -16
- package/lib/test/integration/recordings-router.spec.js +16 -16
- package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
- package/lib/test/integration/role-matrix.spec.js +4 -9
- package/lib/test/integration/sdk-leases-router.spec.js +9 -9
- package/lib/test/integration/sdk-version.spec.js +2 -2
- package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
- package/lib/test/integration/selector-state-flow.spec.js +5 -5
- package/lib/test/integration/team-membership-migration.spec.js +73 -12
- package/lib/test/integration/team-visibility-apps.spec.js +15 -22
- package/lib/test/integration/team-visibility-control.spec.js +96 -69
- package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
- package/lib/test/integration/team-visibility-grid.spec.js +16 -15
- package/lib/test/integration/team-visibility-leases.spec.js +8 -11
- package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
- package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
- package/lib/test/integration/users-flow.spec.js +7 -12
- package/lib/test/integration/users-lockout.spec.js +5 -3
- package/lib/test/unit/EmailService.test.js +66 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
- package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
- package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
- package/lib/test/unit/ai-engine-settings.spec.js +338 -0
- package/lib/test/unit/ai-provider-switch.spec.js +130 -0
- package/lib/test/unit/ai-rate-limit.spec.js +126 -0
- package/lib/test/unit/android-adb-retry.spec.js +112 -0
- package/lib/test/unit/android-h264-viewers.spec.js +272 -0
- package/lib/test/unit/android-preview-hold.spec.js +3 -2
- package/lib/test/unit/apikeys-router.spec.js +84 -0
- package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
- package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
- package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
- package/lib/test/unit/command-log-network-capture.spec.js +191 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
- package/lib/test/unit/database-check.spec.js +1 -0
- package/lib/test/unit/device-log-book.spec.js +180 -0
- package/lib/test/unit/device-utils.spec.js +13 -4
- package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
- package/lib/test/unit/event-log-session-data.spec.js +251 -0
- package/lib/test/unit/failure-analysis.spec.js +196 -0
- package/lib/test/unit/failure-categories.spec.js +130 -0
- package/lib/test/unit/failure-categorization.spec.js +393 -0
- package/lib/test/unit/fingerprint-identity.spec.js +433 -0
- package/lib/test/unit/h264-multiplexer.spec.js +46 -0
- package/lib/test/unit/heal-report.spec.js +71 -0
- package/lib/test/unit/healing-instrumentation.spec.js +6 -1
- package/lib/test/unit/healing-tier-gate.spec.js +74 -29
- package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
- package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
- package/lib/test/unit/ios-driver-log.spec.js +168 -0
- package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
- package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
- package/lib/test/unit/lease/lease-override.spec.js +23 -6
- package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
- package/lib/test/unit/logcat-ws.spec.js +78 -37
- package/lib/test/unit/logger-json-levels.spec.js +44 -0
- package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
- package/lib/test/unit/observability-example-loki.spec.js +25 -0
- package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
- package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
- package/lib/test/unit/ocr-offline.spec.js +213 -0
- package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
- package/lib/test/unit/omni-vision.spec.js +5 -7
- package/lib/test/unit/openapi-for-site.spec.js +80 -0
- package/lib/test/unit/outbound-proxy.spec.js +320 -0
- package/lib/test/unit/package-engines.spec.js +27 -0
- package/lib/test/unit/passwordResetMode.test.js +10 -2
- package/lib/test/unit/port-exhaustion.spec.js +2 -1
- package/lib/test/unit/profile-router.test.js +122 -1
- package/lib/test/unit/proxy-option.spec.js +358 -0
- package/lib/test/unit/queue-requester.spec.js +1 -1
- package/lib/test/unit/reset-link-address.spec.js +266 -0
- package/lib/test/unit/reset-link.test.js +10 -4
- package/lib/test/unit/resilio-path-healing.spec.js +270 -0
- package/lib/test/unit/run-migrations-database.spec.js +807 -0
- package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
- package/lib/test/unit/run-migrations.test.js +864 -0
- package/lib/test/unit/selector-health-actions.spec.js +3 -1
- package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
- package/lib/test/unit/selector-state-service.spec.js +72 -23
- package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
- package/lib/test/unit/selector-verification-job.spec.js +28 -19
- package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
- package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
- package/lib/test/unit/self-healing-boot.spec.js +10 -5
- package/lib/test/unit/self-healing-setting.spec.js +7 -6
- package/lib/test/unit/session-app-resolution.spec.js +5 -1
- package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
- package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
- package/lib/test/unit/session-credential-strip.spec.js +1 -1
- package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
- package/lib/test/unit/session-device-logs.spec.js +356 -0
- package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
- package/lib/test/unit/session-log-indexes.spec.js +64 -3
- package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
- package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
- package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
- package/lib/test/unit/token-mint-limits.spec.js +165 -0
- package/lib/test/unit/tracing-export-routing.spec.js +137 -0
- package/lib/test/unit/tracing-session-spans.spec.js +337 -0
- package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
- package/lib/test/unit/verify-credential.spec.js +33 -0
- package/lib/test/unit/vision-assertion.spec.js +3 -2
- package/lib/test/unit/web-config-service.spec.js +19 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
- package/prisma/schema.prisma +3 -0
- package/schema.json +16 -12
- package/scripts/dev/readme-screenshots.js +276 -23
- package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
- package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
- package/scripts/dev/screenshot-fixtures/session.js +242 -0
- package/scripts/export-openapi.js +32 -0
- package/scripts/lib/openapi-for-site.js +79 -0
- package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
- package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
- package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
- package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
- package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
- package/lib/public/assets/index-BbKEXUFH.js +0 -219
- package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
- package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
- package/lib/src/scripts/prepare-prisma.js +0 -89
- /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
require("reflect-metadata");
|
|
16
|
+
const chai_1 = require("chai");
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
18
|
+
const api_1 = require("@opentelemetry/api");
|
|
19
|
+
const api_logs_1 = require("@opentelemetry/api-logs");
|
|
20
|
+
const TracingService_1 = require("../../src/services/TracingService");
|
|
21
|
+
const logger_1 = require("../../src/logger");
|
|
22
|
+
const otlp_probe_1 = require("../helpers/otlp-probe");
|
|
23
|
+
const ENV_KEYS = [
|
|
24
|
+
'OTEL_SDK_DISABLED',
|
|
25
|
+
'OTEL_LOGS_ENABLED',
|
|
26
|
+
'OTEL_TRACES_ENABLED',
|
|
27
|
+
'OTEL_METRICS_ENABLED',
|
|
28
|
+
'OTEL_EXPORTER_OTLP_ENDPOINT',
|
|
29
|
+
'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT',
|
|
30
|
+
'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
|
|
31
|
+
'XENON_OTEL_DEBUG',
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* With tracing on, Xenon builds an OTel NodeSDK for its spans. A NodeSDK
|
|
35
|
+
* that isn't handed log processors or metric readers builds its own from the
|
|
36
|
+
* standard OTEL_* variables and registers them globally before Xenon could.
|
|
37
|
+
* Xenon's own logger and meter providers were then refused, so:
|
|
38
|
+
* - logs went out as protobuf through the SDK's exporter, not Xenon's;
|
|
39
|
+
* - metrics went to the trace URL + /v1/metrics, even with
|
|
40
|
+
* OTEL_METRICS_ENABLED=false;
|
|
41
|
+
* - only traces on also exported logs, to the trace URL + /v1/logs;
|
|
42
|
+
* - XENON_OTEL_DEBUG's console log and metric exporters never printed.
|
|
43
|
+
*
|
|
44
|
+
* A probe collector shows where each signal went.
|
|
45
|
+
*/
|
|
46
|
+
describe('TracingService — where logs and metrics go with tracing on', function () {
|
|
47
|
+
this.timeout(20000);
|
|
48
|
+
let probe;
|
|
49
|
+
let service;
|
|
50
|
+
let saved;
|
|
51
|
+
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
saved = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
|
|
53
|
+
for (const k of ENV_KEYS)
|
|
54
|
+
delete process.env[k];
|
|
55
|
+
(0, otlp_probe_1.resetOtelGlobals)();
|
|
56
|
+
logger_1.XenonLogger.setOtlpReady(false);
|
|
57
|
+
probe = yield (0, otlp_probe_1.startOtlpProbe)();
|
|
58
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = probe.url('/v1/traces');
|
|
59
|
+
}));
|
|
60
|
+
afterEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
yield (service === null || service === void 0 ? void 0 : service.shutdown());
|
|
62
|
+
service = undefined;
|
|
63
|
+
sinon_1.default.restore();
|
|
64
|
+
(0, otlp_probe_1.resetOtelGlobals)();
|
|
65
|
+
logger_1.XenonLogger.setOtlpReady(false);
|
|
66
|
+
yield probe.close();
|
|
67
|
+
for (const k of ENV_KEYS) {
|
|
68
|
+
if (saved[k] === undefined)
|
|
69
|
+
delete process.env[k];
|
|
70
|
+
else
|
|
71
|
+
process.env[k] = saved[k];
|
|
72
|
+
}
|
|
73
|
+
}));
|
|
74
|
+
function start() {
|
|
75
|
+
service = new TracingService_1.TracingService();
|
|
76
|
+
service.initialize({ isHub: true });
|
|
77
|
+
return service;
|
|
78
|
+
}
|
|
79
|
+
function countProbeMetric() {
|
|
80
|
+
api_1.metrics.getMeter('probe').createCounter('xenon.probe.count').add(1);
|
|
81
|
+
}
|
|
82
|
+
function emitProbeLog() {
|
|
83
|
+
api_logs_1.logs.getLogger('probe').emit({ body: 'probe line', severityNumber: api_logs_1.SeverityNumber.INFO });
|
|
84
|
+
}
|
|
85
|
+
it('exports no metrics with OTEL_METRICS_ENABLED=false', () => __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
process.env.OTEL_METRICS_ENABLED = 'false';
|
|
87
|
+
start();
|
|
88
|
+
countProbeMetric();
|
|
89
|
+
yield (0, otlp_probe_1.flushOtelGlobals)();
|
|
90
|
+
(0, chai_1.expect)(probe.requests.map((r) => r.path).filter((p) => p.includes('metrics'))).to.deep.equal([]);
|
|
91
|
+
}));
|
|
92
|
+
it('exports no metrics when no metrics endpoint is set, and never to the trace URL', () => __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
start();
|
|
94
|
+
countProbeMetric();
|
|
95
|
+
yield (0, otlp_probe_1.flushOtelGlobals)();
|
|
96
|
+
(0, chai_1.expect)(probe.requests.map((r) => r.path)).to.not.include('/v1/traces/v1/metrics');
|
|
97
|
+
}));
|
|
98
|
+
it("sends metrics to Xenon's metrics endpoint as OTLP/JSON", () => __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = probe.url('/xenon-metrics');
|
|
100
|
+
start();
|
|
101
|
+
countProbeMetric();
|
|
102
|
+
yield (0, otlp_probe_1.flushOtelGlobals)();
|
|
103
|
+
const sent = probe.requests.filter((r) => r.path === '/xenon-metrics');
|
|
104
|
+
(0, chai_1.expect)(sent, 'metrics export').to.have.length.greaterThan(0);
|
|
105
|
+
(0, chai_1.expect)(sent.every((r) => r.contentType.includes('application/json'))).to.equal(true);
|
|
106
|
+
(0, chai_1.expect)(sent.map((r) => r.body.toString('utf8')).join()).to.include('xenon.probe.count');
|
|
107
|
+
}));
|
|
108
|
+
it('exports no logs when only traces are configured', () => __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
start();
|
|
110
|
+
emitProbeLog();
|
|
111
|
+
yield (0, otlp_probe_1.flushOtelGlobals)();
|
|
112
|
+
(0, chai_1.expect)(probe.requests.map((r) => r.path).filter((p) => p.includes('logs'))).to.deep.equal([]);
|
|
113
|
+
}));
|
|
114
|
+
it("sends logs to Xenon's log endpoint as OTLP/JSON", () => __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = probe.url('/xenon-logs');
|
|
116
|
+
start();
|
|
117
|
+
emitProbeLog();
|
|
118
|
+
yield (0, otlp_probe_1.flushOtelGlobals)();
|
|
119
|
+
const sent = probe.requests.filter((r) => r.path === '/xenon-logs');
|
|
120
|
+
(0, chai_1.expect)(sent, 'logs export').to.have.length.greaterThan(0);
|
|
121
|
+
(0, chai_1.expect)(sent.every((r) => r.contentType.includes('application/json'))).to.equal(true);
|
|
122
|
+
(0, chai_1.expect)(sent.map((r) => r.body.toString('utf8')).join()).to.include('probe line');
|
|
123
|
+
}));
|
|
124
|
+
it('prints logs and metrics to the console with XENON_OTEL_DEBUG=true', () => __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
const printed = sinon_1.default.stub(console, 'dir');
|
|
126
|
+
process.env.XENON_OTEL_DEBUG = 'true';
|
|
127
|
+
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = probe.url('/xenon-logs');
|
|
128
|
+
start();
|
|
129
|
+
emitProbeLog();
|
|
130
|
+
countProbeMetric();
|
|
131
|
+
yield (0, otlp_probe_1.flushOtelGlobals)();
|
|
132
|
+
printed.restore();
|
|
133
|
+
const shown = printed.getCalls().map((c) => JSON.stringify(c.args[0]));
|
|
134
|
+
(0, chai_1.expect)(shown.some((s) => s.includes('probe line')), 'console log record').to.equal(true);
|
|
135
|
+
(0, chai_1.expect)(shown.some((s) => s.includes('xenon.probe.count')), 'console metric').to.equal(true);
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
@@ -0,0 +1,337 @@
|
|
|
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 http_1 = __importDefault(require("http"));
|
|
51
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
52
|
+
const typedi_1 = require("typedi");
|
|
53
|
+
const TracingService_1 = require("../../src/services/TracingService");
|
|
54
|
+
const CommandInterceptor_1 = require("../../src/interceptors/CommandInterceptor");
|
|
55
|
+
const plugin_1 = require("../../src/plugin");
|
|
56
|
+
const DeviceService = __importStar(require("../../src/data-service/device-service"));
|
|
57
|
+
const ActiveLeases = __importStar(require("../../src/services/lease/activeLeases"));
|
|
58
|
+
const device_utils_1 = require("../../src/device-utils");
|
|
59
|
+
const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
|
|
60
|
+
const SessionMetricsService_1 = require("../../src/services/metrics/SessionMetricsService");
|
|
61
|
+
const OrphanSweeper_1 = require("../../src/services/OrphanSweeper");
|
|
62
|
+
const PhoneNetworkRestore_1 = require("../../src/services/network/PhoneNetworkRestore");
|
|
63
|
+
const event_manager_1 = require("../../src/dashboard/event-manager");
|
|
64
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
65
|
+
const SessionStatus_1 = require("../../src/types/SessionStatus");
|
|
66
|
+
const logger_1 = require("../../src/logger");
|
|
67
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
68
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
69
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
70
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
71
|
+
const otlp_probe_1 = require("../helpers/otlp-probe");
|
|
72
|
+
const ENV_KEYS = [
|
|
73
|
+
'OTEL_SDK_DISABLED',
|
|
74
|
+
'OTEL_LOGS_ENABLED',
|
|
75
|
+
'OTEL_TRACES_ENABLED',
|
|
76
|
+
'OTEL_METRICS_ENABLED',
|
|
77
|
+
'OTEL_EXPORTER_OTLP_ENDPOINT',
|
|
78
|
+
'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT',
|
|
79
|
+
'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
|
|
80
|
+
'XENON_OTEL_DEBUG',
|
|
81
|
+
];
|
|
82
|
+
const ERROR = 2;
|
|
83
|
+
const OK = 1;
|
|
84
|
+
/**
|
|
85
|
+
* A hub starts a span for every session it creates, a node's and a cloud
|
|
86
|
+
* provider's included, and its commands' spans are its children. The only
|
|
87
|
+
* place that ended it was Appium's new-command timeout with the dashboard on,
|
|
88
|
+
* so every other session's span stayed open in memory for the life of the
|
|
89
|
+
* process and was never exported, and its commands showed no parent. A
|
|
90
|
+
* failed command's span also ended OK.
|
|
91
|
+
*/
|
|
92
|
+
describe('TracingService — session and command spans', function () {
|
|
93
|
+
this.timeout(30000);
|
|
94
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
95
|
+
let probe;
|
|
96
|
+
let tracing;
|
|
97
|
+
let restoreRegs;
|
|
98
|
+
let saved;
|
|
99
|
+
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
saved = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
|
|
101
|
+
for (const k of ENV_KEYS)
|
|
102
|
+
delete process.env[k];
|
|
103
|
+
(0, otlp_probe_1.resetOtelGlobals)();
|
|
104
|
+
logger_1.XenonLogger.setOtlpReady(false);
|
|
105
|
+
probe = yield (0, otlp_probe_1.startOtlpProbe)();
|
|
106
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = probe.url('/v1/traces');
|
|
107
|
+
restoreRegs = (0, container_registration_1.saveRegistrations)(TracingService_1.TracingService, PhoneNetworkRestore_1.PhoneNetworkRestore, SessionMetricsService_1.SessionMetricsService);
|
|
108
|
+
tracing = new TracingService_1.TracingService();
|
|
109
|
+
tracing.initialize({ isHub: true });
|
|
110
|
+
typedi_1.Container.set(TracingService_1.TracingService, tracing);
|
|
111
|
+
typedi_1.Container.set(PhoneNetworkRestore_1.PhoneNetworkRestore, {
|
|
112
|
+
restoreSession: sinon_1.default.stub().resolves(),
|
|
113
|
+
restoreAll: sinon_1.default.stub().resolves(),
|
|
114
|
+
});
|
|
115
|
+
typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, { stop: sinon_1.default.stub().resolves() });
|
|
116
|
+
sinon_1.default.stub(DeviceService, 'releaseSessionDevices').resolves();
|
|
117
|
+
sinon_1.default.stub(event_manager_1.DASHBORD_EVENT_MANAGER, 'onSessionStopped').resolves();
|
|
118
|
+
}));
|
|
119
|
+
afterEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
120
|
+
yield tracing.shutdown();
|
|
121
|
+
sinon_1.default.restore();
|
|
122
|
+
restoreRegs();
|
|
123
|
+
(0, otlp_probe_1.resetOtelGlobals)();
|
|
124
|
+
yield probe.close();
|
|
125
|
+
for (const k of ENV_KEYS) {
|
|
126
|
+
if (saved[k] === undefined)
|
|
127
|
+
delete process.env[k];
|
|
128
|
+
else
|
|
129
|
+
process.env[k] = saved[k];
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
/** The spans exported once the tracer has sent everything it holds. */
|
|
133
|
+
function exported() {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
yield tracing.shutdown();
|
|
136
|
+
return probe.spans();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function sessionSpans(sessionId) {
|
|
140
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
141
|
+
return (yield exported()).filter((s) => s.name === `Session: ${sessionId}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
describe('a session span ends once, however the session ends', () => {
|
|
145
|
+
it('when the test deletes a session Xenon keeps no record of (dashboard off)', () => __awaiter(this, void 0, void 0, function* () {
|
|
146
|
+
var _a;
|
|
147
|
+
tracing.startSessionSpan('s-delete', 's-delete');
|
|
148
|
+
(0, chai_1.expect)(SessionManager_1.SESSION_MANAGER.getSession('s-delete')).to.equal(undefined);
|
|
149
|
+
yield typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService).deleteSession(sinon_1.default.stub().resolves(), 's-delete');
|
|
150
|
+
const spans = yield sessionSpans('s-delete');
|
|
151
|
+
(0, chai_1.expect)(spans).to.have.length(1);
|
|
152
|
+
(0, chai_1.expect)((_a = spans[0].status) === null || _a === void 0 ? void 0 : _a.code).to.equal(OK);
|
|
153
|
+
}));
|
|
154
|
+
it('as failed when a session ends failed (a node no longer has it)', () => __awaiter(this, void 0, void 0, function* () {
|
|
155
|
+
var _a;
|
|
156
|
+
tracing.startSessionSpan('s-gone', 's-gone');
|
|
157
|
+
yield typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService).deleteSession(() => __awaiter(this, void 0, void 0, function* () { return undefined; }), 's-gone', SessionStatus_1.SessionStatus.FAILED, 'Session terminal failure (gone)');
|
|
158
|
+
const [span] = yield sessionSpans('s-gone');
|
|
159
|
+
(0, chai_1.expect)((_a = span.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
160
|
+
(0, chai_1.expect)((0, otlp_probe_1.attributeOf)(span.attributes, 'xenon.session.stop_reason')).to.equal('Session terminal failure (gone)');
|
|
161
|
+
}));
|
|
162
|
+
it('when Appium ends the session itself, with the dashboard off', () => __awaiter(this, void 0, void 0, function* () {
|
|
163
|
+
var _a;
|
|
164
|
+
tracing.startSessionSpan('s-timeout', 's-timeout');
|
|
165
|
+
const plugin = { pluginArgs: {}, xenonLog: { withSession: () => ({ info() { } }) } };
|
|
166
|
+
yield plugin_1.XenonPlugin.prototype.onUnexpectedShutdown.call(plugin, { sessionId: 's-timeout', caps: { udid: 'R5CT' } }, new Error('New command timeout'));
|
|
167
|
+
const spans = yield sessionSpans('s-timeout');
|
|
168
|
+
(0, chai_1.expect)(spans).to.have.length(1);
|
|
169
|
+
(0, chai_1.expect)((_a = spans[0].status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
170
|
+
}));
|
|
171
|
+
it("when Xenon's idle check releases the phone", () => __awaiter(this, void 0, void 0, function* () {
|
|
172
|
+
var _a;
|
|
173
|
+
tracing.startSessionSpan('s-idle', 's-idle');
|
|
174
|
+
sinon_1.default.stub(DeviceService, 'getAllDevices').resolves([
|
|
175
|
+
{
|
|
176
|
+
udid: 'R5CT',
|
|
177
|
+
host: 'http://127.0.0.1:4723',
|
|
178
|
+
busy: true,
|
|
179
|
+
userBlocked: false,
|
|
180
|
+
session_id: 's-idle',
|
|
181
|
+
claimSessionId: 's-idle',
|
|
182
|
+
lastCmdExecutedAt: Date.now() - 10 * 60 * 1000,
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
sinon_1.default.stub(ActiveLeases, 'activeLeasesByDevice').resolves(new Map());
|
|
186
|
+
sinon_1.default.stub(DeviceService, 'releaseSessionDevice').resolves(true);
|
|
187
|
+
yield (0, device_utils_1.releaseBlockedDevices)(60);
|
|
188
|
+
const [span] = yield sessionSpans('s-idle');
|
|
189
|
+
(0, chai_1.expect)(span, 'idle session span').to.not.equal(undefined);
|
|
190
|
+
(0, chai_1.expect)((_a = span.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
191
|
+
}));
|
|
192
|
+
it('when a graceful shutdown drains the session', () => __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
var _a;
|
|
194
|
+
tracing.startSessionSpan('s-down', 's-down');
|
|
195
|
+
yield typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService).stopSessionForShutdown('s-down', 'shutdown');
|
|
196
|
+
const [span] = yield sessionSpans('s-down');
|
|
197
|
+
(0, chai_1.expect)(span, 'drained session span').to.not.equal(undefined);
|
|
198
|
+
(0, chai_1.expect)((_a = span.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
199
|
+
}));
|
|
200
|
+
it("when a session's heartbeat goes stale", () => __awaiter(this, void 0, void 0, function* () {
|
|
201
|
+
var _a;
|
|
202
|
+
tracing.startSessionSpan('s-stale', 's-stale');
|
|
203
|
+
yield scratch.db.session.deleteMany({});
|
|
204
|
+
yield scratch.db.session.create({
|
|
205
|
+
data: {
|
|
206
|
+
id: 's-stale',
|
|
207
|
+
status: 'running',
|
|
208
|
+
desired_capabilities: '{}',
|
|
209
|
+
session_capabilities: '{}',
|
|
210
|
+
node_id: 'n',
|
|
211
|
+
has_live_video: false,
|
|
212
|
+
device_udid: 'R5CT',
|
|
213
|
+
device_platform: 'android',
|
|
214
|
+
device_version: '14',
|
|
215
|
+
last_heartbeat_at: new Date(Date.now() - 60 * 60 * 1000),
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
yield new OrphanSweeper_1.OrphanSweeper().sweep({ heartbeatIntervalMs: 1000 });
|
|
219
|
+
const [span] = yield sessionSpans('s-stale');
|
|
220
|
+
(0, chai_1.expect)(span, 'stale session span').to.not.equal(undefined);
|
|
221
|
+
(0, chai_1.expect)((_a = span.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
222
|
+
}));
|
|
223
|
+
it('when the server shuts down with the session still running elsewhere', () => __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
tracing.startSessionSpan('s-on-node', 's-on-node');
|
|
225
|
+
const [span] = yield sessionSpans('s-on-node');
|
|
226
|
+
(0, chai_1.expect)(span, 'open session span at shutdown').to.not.equal(undefined);
|
|
227
|
+
(0, chai_1.expect)((0, otlp_probe_1.attributeOf)(span.attributes, 'xenon.session.stop_reason')).to.equal('Xenon shut down while the session was running');
|
|
228
|
+
}));
|
|
229
|
+
it('within its budget when the collector never answers', () => __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
var _a;
|
|
231
|
+
const silent = http_1.default.createServer(() => undefined);
|
|
232
|
+
yield new Promise((resolve) => silent.listen(0, '127.0.0.1', () => resolve()));
|
|
233
|
+
try {
|
|
234
|
+
yield tracing.shutdown();
|
|
235
|
+
(0, otlp_probe_1.resetOtelGlobals)();
|
|
236
|
+
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = `http://127.0.0.1:${silent.address().port}/v1/traces`;
|
|
237
|
+
tracing = new TracingService_1.TracingService();
|
|
238
|
+
tracing.initialize({ isHub: true });
|
|
239
|
+
tracing.startSessionSpan('s-hung', 's-hung');
|
|
240
|
+
const started = Date.now();
|
|
241
|
+
yield tracing.shutdownWithin(300);
|
|
242
|
+
(0, chai_1.expect)(Date.now() - started).to.be.lessThan(2000);
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
(_a = silent.closeAllConnections) === null || _a === void 0 ? void 0 : _a.call(silent);
|
|
246
|
+
silent.close();
|
|
247
|
+
}
|
|
248
|
+
}));
|
|
249
|
+
it('when Xenon fails to finish creating it', () => __awaiter(this, void 0, void 0, function* () {
|
|
250
|
+
var _a;
|
|
251
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
252
|
+
const savedContext = Object.assign({}, context);
|
|
253
|
+
context.setContext(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), 4723, 'hub-1', '');
|
|
254
|
+
sinon_1.default.stub(DeviceService, 'claimDeviceForSession').rejects(new Error('database is locked'));
|
|
255
|
+
try {
|
|
256
|
+
const finishing = typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService).finalizeSession({ value: ['s-half', {}] }, { udid: 'R5CT', platform: 'android', host: 'http://127.0.0.1:4723', nodeId: 'hub-1' }, { alwaysMatch: {}, firstMatch: [{}] }, {}, false);
|
|
257
|
+
yield finishing.then(() => chai_1.expect.fail('the create was expected to fail'), () => undefined);
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
Object.assign(context, savedContext);
|
|
261
|
+
}
|
|
262
|
+
const [span] = yield sessionSpans('s-half');
|
|
263
|
+
(0, chai_1.expect)(span, 'half-created session span').to.not.equal(undefined);
|
|
264
|
+
(0, chai_1.expect)((_a = span.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
265
|
+
(0, chai_1.expect)((0, otlp_probe_1.attributeOf)(span.attributes, 'xenon.session.stop_reason')).to.include('database is locked');
|
|
266
|
+
}));
|
|
267
|
+
it('only once when two endings race', () => __awaiter(this, void 0, void 0, function* () {
|
|
268
|
+
tracing.startSessionSpan('s-twice', 's-twice');
|
|
269
|
+
const lifecycle = typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService);
|
|
270
|
+
yield Promise.all([
|
|
271
|
+
lifecycle.deleteSession(() => __awaiter(this, void 0, void 0, function* () { return undefined; }), 's-twice'),
|
|
272
|
+
lifecycle.stopSessionForShutdown('s-twice', 'shutdown'),
|
|
273
|
+
]);
|
|
274
|
+
(0, chai_1.expect)(yield sessionSpans('s-twice')).to.have.length(1);
|
|
275
|
+
}));
|
|
276
|
+
});
|
|
277
|
+
describe("a command's span", () => {
|
|
278
|
+
beforeEach(() => {
|
|
279
|
+
sinon_1.default.stub(DeviceService, 'updateCmdExecutedTime').resolves();
|
|
280
|
+
sinon_1.default.stub(event_manager_1.DASHBORD_EVENT_MANAGER, 'beforeSessionCommand').resolves(true);
|
|
281
|
+
});
|
|
282
|
+
function run(next) {
|
|
283
|
+
return typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).handle(next, { sessionId: 's-cmd', caps: {} }, 'getPageSource', [], {}, true);
|
|
284
|
+
}
|
|
285
|
+
it('is a child of its session span', () => __awaiter(this, void 0, void 0, function* () {
|
|
286
|
+
var _a;
|
|
287
|
+
tracing.startSessionSpan('s-cmd', 's-cmd');
|
|
288
|
+
yield run(() => __awaiter(this, void 0, void 0, function* () { return '<xml/>'; }));
|
|
289
|
+
tracing.endSessionSpan('s-cmd');
|
|
290
|
+
const spans = yield exported();
|
|
291
|
+
const session = spans.find((s) => s.name === 'Session: s-cmd');
|
|
292
|
+
const command = spans.find((s) => s.name === 'getPageSource');
|
|
293
|
+
(0, chai_1.expect)(session, 'session span').to.not.equal(undefined);
|
|
294
|
+
(0, chai_1.expect)(command, 'command span').to.not.equal(undefined);
|
|
295
|
+
(0, chai_1.expect)(command === null || command === void 0 ? void 0 : command.parentSpanId).to.equal(session === null || session === void 0 ? void 0 : session.spanId);
|
|
296
|
+
(0, chai_1.expect)((_a = command === null || command === void 0 ? void 0 : command.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(OK);
|
|
297
|
+
}));
|
|
298
|
+
it('records the error and ends ERROR when the command fails', () => __awaiter(this, void 0, void 0, function* () {
|
|
299
|
+
var _a, _b, _c;
|
|
300
|
+
tracing.startSessionSpan('s-cmd', 's-cmd');
|
|
301
|
+
let thrown;
|
|
302
|
+
try {
|
|
303
|
+
yield run(() => __awaiter(this, void 0, void 0, function* () {
|
|
304
|
+
throw new Error('An unknown server-side error occurred: boom');
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
thrown = err;
|
|
309
|
+
}
|
|
310
|
+
(0, chai_1.expect)(thrown).to.be.instanceOf(Error);
|
|
311
|
+
const command = (yield exported()).find((s) => s.name === 'getPageSource');
|
|
312
|
+
(0, chai_1.expect)((_a = command === null || command === void 0 ? void 0 : command.status) === null || _a === void 0 ? void 0 : _a.code).to.equal(ERROR);
|
|
313
|
+
(0, chai_1.expect)((_b = command === null || command === void 0 ? void 0 : command.status) === null || _b === void 0 ? void 0 : _b.message).to.equal('An unknown server-side error occurred: boom');
|
|
314
|
+
const exception = (_c = command === null || command === void 0 ? void 0 : command.events) === null || _c === void 0 ? void 0 : _c.find((e) => e.name === 'exception');
|
|
315
|
+
(0, chai_1.expect)((0, otlp_probe_1.attributeOf)(exception === null || exception === void 0 ? void 0 : exception.attributes, 'exception.message')).to.equal('An unknown server-side error occurred: boom');
|
|
316
|
+
}));
|
|
317
|
+
it("keeps each of two concurrent same-named commands' outcome on its own span", () => __awaiter(this, void 0, void 0, function* () {
|
|
318
|
+
var _a;
|
|
319
|
+
// The Inspector reads the page source while the test does: both spans
|
|
320
|
+
// are found by `<session>:getPageSource`, the second over the first.
|
|
321
|
+
tracing.startSessionSpan('s-cmd', 's-cmd');
|
|
322
|
+
let finishB;
|
|
323
|
+
const b = run(() => new Promise((resolve) => (finishB = resolve)));
|
|
324
|
+
const a = run(() => __awaiter(this, void 0, void 0, function* () {
|
|
325
|
+
throw new Error('A failed');
|
|
326
|
+
}));
|
|
327
|
+
yield a.catch(() => undefined);
|
|
328
|
+
finishB('<xml/>');
|
|
329
|
+
yield b;
|
|
330
|
+
const commands = (yield exported()).filter((s) => s.name === 'getPageSource');
|
|
331
|
+
(0, chai_1.expect)(commands, 'both command spans').to.have.length(2);
|
|
332
|
+
(0, chai_1.expect)(commands.map((c) => { var _a; return (_a = c.status) === null || _a === void 0 ? void 0 : _a.code; }).sort()).to.deep.equal([OK, ERROR]);
|
|
333
|
+
const failed = commands.find((c) => { var _a; return ((_a = c.status) === null || _a === void 0 ? void 0 : _a.code) === ERROR; });
|
|
334
|
+
(0, chai_1.expect)((_a = failed === null || failed === void 0 ? void 0 : failed.status) === null || _a === void 0 ? void 0 : _a.message).to.equal('A failed');
|
|
335
|
+
}));
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
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 plugin_1 = require("../../src/plugin");
|
|
53
|
+
const DeviceService = __importStar(require("../../src/data-service/device-service"));
|
|
54
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
55
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
56
|
+
const SessionMetricsService_1 = require("../../src/services/metrics/SessionMetricsService");
|
|
57
|
+
const PhoneNetworkRestore_1 = require("../../src/services/network/PhoneNetworkRestore");
|
|
58
|
+
const SocketServer_1 = require("../../src/services/SocketServer");
|
|
59
|
+
const MetricsService_1 = require("../../src/services/MetricsService");
|
|
60
|
+
const NotificationService_1 = require("../../src/services/NotificationService");
|
|
61
|
+
const AIService_1 = require("../../src/services/AIService");
|
|
62
|
+
const shutdownReason_1 = require("../../src/services/session/shutdownReason");
|
|
63
|
+
const prisma_1 = require("../../src/prisma");
|
|
64
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
65
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
66
|
+
/**
|
|
67
|
+
* A session Appium ends by itself reaches `XenonPlugin.onUnexpectedShutdown`
|
|
68
|
+
* with a cause. For an idle session that is Appium's new-command timeout.
|
|
69
|
+
* The plugin threw the cause away and recorded "Driver shut down
|
|
70
|
+
* unexpectedly", which no failure pattern matches, so an idle session on this
|
|
71
|
+
* server's own phones was filed UNKNOWN and its "Why it failed" card opened
|
|
72
|
+
* the general runbook rather than the Timeout one.
|
|
73
|
+
*/
|
|
74
|
+
// What @appium/base-driver 10.2 passes when a session sits idle too long.
|
|
75
|
+
const IDLE = "New Command Timeout of 60 seconds expired. Try customizing the timeout using the 'newCommandTimeout' desired capability";
|
|
76
|
+
describe('the reason of a session Appium ends by itself', () => {
|
|
77
|
+
it("is Appium's cause: its new-command timeout", () => {
|
|
78
|
+
(0, chai_1.expect)((0, shutdownReason_1.unexpectedShutdownReason)(new Error(IDLE))).to.equal(IDLE);
|
|
79
|
+
});
|
|
80
|
+
it("is a driver's own account of giving up, which still reads as one", () => {
|
|
81
|
+
(0, chai_1.expect)((0, shutdownReason_1.unexpectedShutdownReason)(new Error('The driver was unexpectedly shut down!'))).to.equal('The driver was unexpectedly shut down!');
|
|
82
|
+
});
|
|
83
|
+
it('is the old fixed reason when there is no cause to tell', () => {
|
|
84
|
+
for (const cause of [undefined, null, new Error(''), new Error(' '), {}, 42]) {
|
|
85
|
+
(0, chai_1.expect)((0, shutdownReason_1.unexpectedShutdownReason)(cause), String(cause)).to.equal(shutdownReason_1.UNEXPECTED_SHUTDOWN_REASON);
|
|
86
|
+
}
|
|
87
|
+
// The umbrella's placeholder when a driver gives none.
|
|
88
|
+
(0, chai_1.expect)((0, shutdownReason_1.unexpectedShutdownReason)(new Error('Unknown error'))).to.equal(shutdownReason_1.UNEXPECTED_SHUTDOWN_REASON);
|
|
89
|
+
});
|
|
90
|
+
it('is the message alone, never a stack', () => {
|
|
91
|
+
const err = new Error(IDLE);
|
|
92
|
+
(0, chai_1.expect)((0, shutdownReason_1.unexpectedShutdownReason)(err)).not.to.contain(' at ');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('a session Appium ends for being idle, on a hub that records sessions', function () {
|
|
96
|
+
this.timeout(30000);
|
|
97
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
98
|
+
let restoreRegs;
|
|
99
|
+
let wasHub;
|
|
100
|
+
const plugin = {
|
|
101
|
+
pluginArgs: { enableDashboard: true },
|
|
102
|
+
xenonLog: { withSession: () => ({ info() { } }) },
|
|
103
|
+
};
|
|
104
|
+
const seed = (id) => scratch.db.session.create({
|
|
105
|
+
data: {
|
|
106
|
+
id,
|
|
107
|
+
status: 'running',
|
|
108
|
+
desired_capabilities: '{}',
|
|
109
|
+
session_capabilities: '{}',
|
|
110
|
+
node_id: 'n-1',
|
|
111
|
+
has_live_video: false,
|
|
112
|
+
device_udid: 'R5CT',
|
|
113
|
+
device_platform: 'android',
|
|
114
|
+
device_version: '14',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const shutDown = (id, cause) => plugin_1.XenonPlugin.prototype.onUnexpectedShutdown.call(plugin, { sessionId: id, caps: { udid: 'R5CT' } }, cause);
|
|
118
|
+
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
restoreRegs = (0, container_registration_1.saveRegistrations)(PhoneNetworkRestore_1.PhoneNetworkRestore, SessionMetricsService_1.SessionMetricsService, SocketServer_1.SocketServer, MetricsService_1.MetricsService, NotificationService_1.NotificationService);
|
|
120
|
+
typedi_1.Container.set(PhoneNetworkRestore_1.PhoneNetworkRestore, { restoreSession: sinon_1.default.stub().resolves() });
|
|
121
|
+
typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, { stop: sinon_1.default.stub().resolves() });
|
|
122
|
+
typedi_1.Container.set(SocketServer_1.SocketServer, {
|
|
123
|
+
emitToDashboard: sinon_1.default.stub(),
|
|
124
|
+
emitToDashboardForDevices: sinon_1.default.stub().resolves(),
|
|
125
|
+
hasScopedDashboard: sinon_1.default.stub().returns(false),
|
|
126
|
+
});
|
|
127
|
+
typedi_1.Container.set(MetricsService_1.MetricsService, {
|
|
128
|
+
incrementSessionSuccess: sinon_1.default.stub(),
|
|
129
|
+
incrementSessionFailure: sinon_1.default.stub(),
|
|
130
|
+
});
|
|
131
|
+
typedi_1.Container.set(NotificationService_1.NotificationService, { notifySessionFailed: sinon_1.default.stub().resolves() });
|
|
132
|
+
sinon_1.default.stub(DeviceService, 'releaseSessionDevices').resolves();
|
|
133
|
+
sinon_1.default
|
|
134
|
+
.stub(device_store_1.DeviceStoreFactory, 'getStore')
|
|
135
|
+
.returns({ getDevices: () => __awaiter(this, void 0, void 0, function* () { return []; }), findDevices: () => __awaiter(this, void 0, void 0, function* () { return []; }) });
|
|
136
|
+
sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'getSession').returns(undefined);
|
|
137
|
+
sinon_1.default.stub(AIService_1.AI_SERVICE, 'isEnabled').returns(false);
|
|
138
|
+
wasHub = plugin_1.XenonPlugin.IS_HUB;
|
|
139
|
+
plugin_1.XenonPlugin.IS_HUB = true;
|
|
140
|
+
yield scratch.db.session.deleteMany({});
|
|
141
|
+
}));
|
|
142
|
+
afterEach(() => {
|
|
143
|
+
plugin_1.XenonPlugin.IS_HUB = wasHub;
|
|
144
|
+
sinon_1.default.restore();
|
|
145
|
+
restoreRegs();
|
|
146
|
+
});
|
|
147
|
+
it("is failed with Appium's reason and filed TIMEOUT", () => __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
yield seed('s-idle');
|
|
149
|
+
yield shutDown('s-idle', new Error(IDLE));
|
|
150
|
+
const row = yield prisma_1.prisma.session.findUnique({ where: { id: 's-idle' } });
|
|
151
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.status).to.equal('failed');
|
|
152
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.failure_reason).to.equal(IDLE);
|
|
153
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.failure_category).to.equal('TIMEOUT');
|
|
154
|
+
}));
|
|
155
|
+
it('keeps the old reason, filed as a lost session, when Appium gives no cause', () => __awaiter(this, void 0, void 0, function* () {
|
|
156
|
+
yield seed('s-crash');
|
|
157
|
+
yield shutDown('s-crash', undefined);
|
|
158
|
+
const row = yield prisma_1.prisma.session.findUnique({ where: { id: 's-crash' } });
|
|
159
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.status).to.equal('failed');
|
|
160
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.failure_reason).to.equal(shutdownReason_1.UNEXPECTED_SHUTDOWN_REASON);
|
|
161
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.failure_category).to.equal('SESSION_LOST');
|
|
162
|
+
}));
|
|
163
|
+
});
|