@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
|
@@ -8,9 +8,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
const chai_1 = require("chai");
|
|
13
16
|
const events_1 = require("events");
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const ffmpegPath_1 = require("../../../src/helpers/ffmpegPath");
|
|
14
19
|
const video_slice_1 = require("../../../src/services/bug-report/video-slice");
|
|
15
20
|
class FakeProc extends events_1.EventEmitter {
|
|
16
21
|
constructor() {
|
|
@@ -41,6 +46,20 @@ describe('sliceVideo', () => {
|
|
|
41
46
|
if (!result.ok)
|
|
42
47
|
(0, chai_1.expect)(result.error).to.include('exit 1');
|
|
43
48
|
}));
|
|
49
|
+
it("runs Xenon's own ffmpeg, never a bare `ffmpeg` from PATH", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
// A server started from the Mac app has no shell PATH, and src/index.ts puts
|
|
51
|
+
// Xenon's ffmpeg folder at the end of PATH, behind any system ffmpeg.
|
|
52
|
+
const proc = new FakeProc();
|
|
53
|
+
let command = '';
|
|
54
|
+
const p = (0, video_slice_1.sliceVideo)('/in.mp4', 0, 60, '/tmp/out.mp4', (cmd) => {
|
|
55
|
+
command = cmd;
|
|
56
|
+
return proc;
|
|
57
|
+
});
|
|
58
|
+
setImmediate(() => proc.emit('exit', 0));
|
|
59
|
+
yield p;
|
|
60
|
+
(0, chai_1.expect)(command).to.equal((0, ffmpegPath_1.resolveFfmpegPath)());
|
|
61
|
+
(0, chai_1.expect)(path_1.default.isAbsolute(command)).to.equal(true);
|
|
62
|
+
}));
|
|
44
63
|
it('returns ok=false on spawn error', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
64
|
const proc = new FakeProc();
|
|
46
65
|
const p = (0, video_slice_1.sliceVideo)('/in.mp4', 0, 60, '/tmp/out.mp4', fakeSpawn(proc));
|
|
@@ -0,0 +1,191 @@
|
|
|
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 express_1 = __importDefault(require("express"));
|
|
19
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
20
|
+
const dashboard_1 = __importDefault(require("../../src/app/routers/dashboard"));
|
|
21
|
+
const event_manager_1 = require("../../src/dashboard/event-manager");
|
|
22
|
+
const commandLogFields_1 = require("../../src/dashboard/commandLogFields");
|
|
23
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
24
|
+
const SelectorStateService_1 = require("../../src/services/SelectorStateService");
|
|
25
|
+
const SocketServer_1 = require("../../src/services/SocketServer");
|
|
26
|
+
const typedi_1 = require("typedi");
|
|
27
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
28
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
29
|
+
/**
|
|
30
|
+
* A session's network capture (its requests, its HAR, its mocks) is for admins
|
|
31
|
+
* only: the /interceptor routes, the Network panel and the live events. The
|
|
32
|
+
* command log is read by anyone who can see the session (`session_log`, bug
|
|
33
|
+
* reports). A hub records every command it forwards to a node, so a test's own
|
|
34
|
+
* `xenon: exportHar` put the whole capture, sign-in headers included, in front
|
|
35
|
+
* of every member of the phone's team.
|
|
36
|
+
*/
|
|
37
|
+
describe('The command log keeps no network capture', () => {
|
|
38
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
39
|
+
const SESSION = 'cl-session-1';
|
|
40
|
+
const SECRET = 'eyJ-app-access-token';
|
|
41
|
+
const HAR = {
|
|
42
|
+
log: {
|
|
43
|
+
entries: [{ request: { headers: [{ name: 'authorization', value: `Bearer ${SECRET}` }] } }],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
let restore;
|
|
47
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
|
+
restore = (0, container_registration_1.saveRegistrations)(SocketServer_1.SocketServer);
|
|
49
|
+
typedi_1.Container.set(SocketServer_1.SocketServer, {
|
|
50
|
+
emitToDashboardForDevices: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
|
|
51
|
+
hasScopedDashboard: () => false,
|
|
52
|
+
});
|
|
53
|
+
yield scratch.db.sessionLog.deleteMany({});
|
|
54
|
+
yield scratch.db.session.deleteMany({});
|
|
55
|
+
yield scratch.db.device.deleteMany({});
|
|
56
|
+
yield scratch.db.team.deleteMany({});
|
|
57
|
+
yield scratch.db.team.create({ data: { id: 'team-a', name: 'Team A' } });
|
|
58
|
+
yield scratch.db.device.create({
|
|
59
|
+
data: {
|
|
60
|
+
udid: 'cl-phone-a',
|
|
61
|
+
host: 'http://node:4723',
|
|
62
|
+
platform: 'android',
|
|
63
|
+
teamId: 'team-a',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
yield scratch.db.session.create({
|
|
67
|
+
data: {
|
|
68
|
+
id: SESSION,
|
|
69
|
+
desired_capabilities: '{}',
|
|
70
|
+
session_capabilities: '{}',
|
|
71
|
+
node_id: 'node-1',
|
|
72
|
+
has_live_video: false,
|
|
73
|
+
device_udid: 'cl-phone-a',
|
|
74
|
+
device_platform: 'android',
|
|
75
|
+
device_version: '14',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'getSession').returns({
|
|
79
|
+
getId: () => SESSION,
|
|
80
|
+
getDevice: () => ({ udid: 'cl-phone-a', name: 'P', platform: 'android' }),
|
|
81
|
+
getXenonOption: () => undefined,
|
|
82
|
+
getScreenShot: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
83
|
+
});
|
|
84
|
+
sinon_1.default.stub(SelectorStateService_1.SelectorStateService.prototype, 'onHealRecorded').resolves(null);
|
|
85
|
+
}));
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
sinon_1.default.restore();
|
|
88
|
+
restore();
|
|
89
|
+
});
|
|
90
|
+
/** A command as the hub's gateway hands it over: the client's W3C body and the node's answer. */
|
|
91
|
+
function forwarded(script, args, answer) {
|
|
92
|
+
return event_manager_1.DASHBORD_EVENT_MANAGER.afterSessionCommand(SESSION, 'execute', null, {
|
|
93
|
+
body: { script, args },
|
|
94
|
+
method: 'POST',
|
|
95
|
+
originalUrl: `/wd/hub/session/${SESSION}/execute/sync`,
|
|
96
|
+
}, {}, JSON.stringify(answer));
|
|
97
|
+
}
|
|
98
|
+
/** What a member of the phone's team reads. */
|
|
99
|
+
function sessionLogAsMember() {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
const app = (0, express_1.default)();
|
|
102
|
+
app.use((req, _res, next) => {
|
|
103
|
+
req.auth = {
|
|
104
|
+
kind: 'user-session',
|
|
105
|
+
userId: 'cl-u-member',
|
|
106
|
+
role: 'MEMBER',
|
|
107
|
+
scopes: 'devices,sessions,read',
|
|
108
|
+
teamIds: ['team-a'],
|
|
109
|
+
};
|
|
110
|
+
next();
|
|
111
|
+
});
|
|
112
|
+
dashboard_1.default.register(app);
|
|
113
|
+
const res = yield (0, loopbackRequest_1.default)(app).get(`/session/${SESSION}/session_log`);
|
|
114
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
115
|
+
return res.body;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
it('records which network script ran and whether it worked, never its arguments or its answer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
119
|
+
var _a, _b;
|
|
120
|
+
yield forwarded('xenon: exportHar', [], { value: HAR });
|
|
121
|
+
yield forwarded('xe:getRequests', [], { value: [HAR] });
|
|
122
|
+
yield forwarded('getMocks', [], { value: [{ respond: { body: { token: SECRET } } }] });
|
|
123
|
+
yield forwarded('xenon: addMock', [{ respond: { body: { token: SECRET } } }], {
|
|
124
|
+
value: { id: 'm1' },
|
|
125
|
+
});
|
|
126
|
+
yield forwarded('xenon: removeMock', [{ id: 'm1' }], { value: { removed: true } });
|
|
127
|
+
yield forwarded('xenon: clearMocks', [], { value: { ok: true } });
|
|
128
|
+
const rows = yield sessionLogAsMember();
|
|
129
|
+
(0, chai_1.expect)(rows).to.have.length(6);
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
(0, chai_1.expect)(`${row.body}${row.response}`).to.not.include(SECRET);
|
|
132
|
+
(0, chai_1.expect)(row.is_success).to.equal(true);
|
|
133
|
+
// Every one, removeMock and clearMocks included, whose answers hold no secret.
|
|
134
|
+
(0, chai_1.expect)(JSON.parse(row.response), (_a = row.body) !== null && _a !== void 0 ? _a : '').to.deep.equal({
|
|
135
|
+
value: commandLogFields_1.NETWORK_CAPTURE_NOT_KEPT,
|
|
136
|
+
});
|
|
137
|
+
(0, chai_1.expect)(JSON.parse((_b = row.body) !== null && _b !== void 0 ? _b : '{}').args).to.equal(commandLogFields_1.NETWORK_CAPTURE_NOT_KEPT);
|
|
138
|
+
}
|
|
139
|
+
(0, chai_1.expect)(rows.map((r) => { var _a; return JSON.parse((_a = r.body) !== null && _a !== void 0 ? _a : '{}').script; })).to.have.members([
|
|
140
|
+
'xenon: exportHar',
|
|
141
|
+
'xe:getRequests',
|
|
142
|
+
'getMocks',
|
|
143
|
+
'xenon: addMock',
|
|
144
|
+
'xenon: removeMock',
|
|
145
|
+
'xenon: clearMocks',
|
|
146
|
+
]);
|
|
147
|
+
}));
|
|
148
|
+
it('keeps a failed network script’s error, which carries no capture', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
149
|
+
const error = {
|
|
150
|
+
value: {
|
|
151
|
+
error: 'unknown error',
|
|
152
|
+
message: `Interceptor not active for session ${SESSION}`,
|
|
153
|
+
stacktrace: '',
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
yield forwarded('xenon: exportHar', [], error);
|
|
157
|
+
const [row] = yield sessionLogAsMember();
|
|
158
|
+
(0, chai_1.expect)(row.is_success).to.equal(false);
|
|
159
|
+
(0, chai_1.expect)(JSON.parse(row.response)).to.deep.equal(error);
|
|
160
|
+
}));
|
|
161
|
+
it('on the server that drives the phone, a failed network script keeps its error but not its arguments', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
162
|
+
var _a;
|
|
163
|
+
// CommandInterceptor answers these scripts before its hooks; only a throw
|
|
164
|
+
// (no capture running) reaches the log, from its catch, with the
|
|
165
|
+
// command's arguments as they came: [script, args].
|
|
166
|
+
const error = {
|
|
167
|
+
value: { error: `Interceptor not active for session ${SESSION}` },
|
|
168
|
+
sessionId: SESSION,
|
|
169
|
+
};
|
|
170
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.afterSessionCommand(SESSION, 'execute', null, {
|
|
171
|
+
body: ['xenon: addMock', [{ respond: { body: { token: SECRET } } }]],
|
|
172
|
+
method: 'POST',
|
|
173
|
+
originalUrl: '/execute',
|
|
174
|
+
}, {}, JSON.stringify(error));
|
|
175
|
+
const [row] = yield sessionLogAsMember();
|
|
176
|
+
(0, chai_1.expect)(row.body).to.not.include(SECRET);
|
|
177
|
+
(0, chai_1.expect)(JSON.parse((_a = row.body) !== null && _a !== void 0 ? _a : '[]')[0]).to.equal('xenon: addMock');
|
|
178
|
+
(0, chai_1.expect)(JSON.parse(row.response)).to.deep.equal(error);
|
|
179
|
+
(0, chai_1.expect)(row.is_success).to.equal(false);
|
|
180
|
+
}));
|
|
181
|
+
it('logs every other script, and a script the node’s interceptor doesn’t answer, whole', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
+
// `plugin:` is only the autowait commands' prefix: `plugin: exportHar` is
|
|
183
|
+
// never a network script, and its answer is the driver's.
|
|
184
|
+
yield forwarded('mobile: shell', [{ command: 'echo' }], { value: SECRET });
|
|
185
|
+
yield forwarded('plugin: exportHar', [], { value: SECRET });
|
|
186
|
+
const rows = yield sessionLogAsMember();
|
|
187
|
+
(0, chai_1.expect)(rows).to.have.length(2);
|
|
188
|
+
for (const row of rows)
|
|
189
|
+
(0, chai_1.expect)(row.response).to.include(SECRET);
|
|
190
|
+
}));
|
|
191
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
require("reflect-metadata");
|
|
49
|
+
const chai_1 = require("chai");
|
|
50
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
51
|
+
const typedi_1 = require("typedi");
|
|
52
|
+
const event_manager_1 = require("../../src/dashboard/event-manager");
|
|
53
|
+
const XenonCapabilityManager_1 = require("../../src/XenonCapabilityManager");
|
|
54
|
+
const assets = __importStar(require("../../src/dashboard/asset-manager"));
|
|
55
|
+
const sessionService = __importStar(require("../../src/dashboard/services/session-service"));
|
|
56
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
57
|
+
const SessionMetricsService_1 = require("../../src/services/metrics/SessionMetricsService");
|
|
58
|
+
const SessionDeviceLogs_1 = require("../../src/services/logcat/SessionDeviceLogs");
|
|
59
|
+
const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
|
|
60
|
+
const SocketServer_1 = require("../../src/services/SocketServer");
|
|
61
|
+
const TracingService_1 = require("../../src/services/TracingService");
|
|
62
|
+
const MetricsService_1 = require("../../src/services/MetricsService");
|
|
63
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
64
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
65
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
66
|
+
const pendingSessions = __importStar(require("../../src/data-service/pending-sessions-service"));
|
|
67
|
+
const deviceService = __importStar(require("../../src/data-service/device-service"));
|
|
68
|
+
const deviceUtils = __importStar(require("../../src/device-utils"));
|
|
69
|
+
const config_1 = require("../../src/config");
|
|
70
|
+
const prisma_1 = require("../../src/prisma");
|
|
71
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
72
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
73
|
+
/**
|
|
74
|
+
* A session's device log is recorded from about when its phone was given to
|
|
75
|
+
* it, once the session's row exists (the lines point at it), and written to
|
|
76
|
+
* the end however the session ends: every ending reaches onSessionStopped.
|
|
77
|
+
*/
|
|
78
|
+
describe('EventManager: an Android session’s device log', () => {
|
|
79
|
+
let restore;
|
|
80
|
+
let deviceLogs;
|
|
81
|
+
let order;
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
restore = (0, container_registration_1.saveRegistrations)(SessionDeviceLogs_1.SessionDeviceLogs, SessionMetricsService_1.SessionMetricsService, SocketServer_1.SocketServer);
|
|
84
|
+
order = [];
|
|
85
|
+
deviceLogs = {
|
|
86
|
+
start: sinon_1.default.stub().callsFake(() => __awaiter(void 0, void 0, void 0, function* () { return order.push('device logs'); })),
|
|
87
|
+
stop: sinon_1.default.stub().resolves(),
|
|
88
|
+
noteOff: sinon_1.default.stub().resolves(),
|
|
89
|
+
};
|
|
90
|
+
typedi_1.Container.set(SessionDeviceLogs_1.SessionDeviceLogs, deviceLogs);
|
|
91
|
+
typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, {
|
|
92
|
+
appliesTo: () => false,
|
|
93
|
+
start: sinon_1.default.stub(),
|
|
94
|
+
stop: sinon_1.default.stub().resolves(),
|
|
95
|
+
});
|
|
96
|
+
typedi_1.Container.set(SocketServer_1.SocketServer, {
|
|
97
|
+
emitToDashboard: sinon_1.default.stub(),
|
|
98
|
+
emitToDashboardForDevices: sinon_1.default.stub().resolves(),
|
|
99
|
+
hasScopedDashboard: sinon_1.default.stub().returns(false),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
sinon_1.default.restore();
|
|
104
|
+
restore();
|
|
105
|
+
});
|
|
106
|
+
function startSession(platform, deviceLog) {
|
|
107
|
+
sinon_1.default.stub(assets, 'prepareDirectory');
|
|
108
|
+
sinon_1.default.stub(sessionService, 'getOrCreateNewBuild').resolves({ id: 'b-logs' });
|
|
109
|
+
sinon_1.default.stub(TracingService_1.TracingService.prototype, 'getTraceId').returns('t-1');
|
|
110
|
+
sinon_1.default.stub(MetricsService_1.MetricsService.prototype, 'incrementSessionStart');
|
|
111
|
+
sinon_1.default.stub(prisma_1.prisma.session, 'create').callsFake(() => __awaiter(this, void 0, void 0, function* () {
|
|
112
|
+
order.push('row');
|
|
113
|
+
return {};
|
|
114
|
+
}));
|
|
115
|
+
const session = Object.assign({ getId: () => 's-logs-1', getCapabilities: () => ({ platformName: platform }), getLiveVideoUrl: () => null, startPerformanceRecording: sinon_1.default.stub().resolves(), apiKeyId: null, userId: null, allocatedAt: 1700000000000 }, (deviceLog ? { deviceLog } : {}));
|
|
116
|
+
const device = { udid: 'phone-l', platform, host: 'h', name: 'phone', sdk: '10' };
|
|
117
|
+
return { session, device };
|
|
118
|
+
}
|
|
119
|
+
it("starts once the session's row is written, from when the phone was given to it", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
120
|
+
const { session, device } = startSession('android');
|
|
121
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStarted({}, session, device);
|
|
122
|
+
(0, chai_1.expect)(order).to.deep.equal(['row', 'device logs']);
|
|
123
|
+
(0, chai_1.expect)(deviceLogs.start.firstCall.args[0]).to.deep.equal({
|
|
124
|
+
sessionId: 's-logs-1',
|
|
125
|
+
device,
|
|
126
|
+
since: 1700000000000,
|
|
127
|
+
driverLog: undefined,
|
|
128
|
+
appUnderTest: undefined,
|
|
129
|
+
});
|
|
130
|
+
}));
|
|
131
|
+
// The switch, from the session's capabilities as the create parses them.
|
|
132
|
+
const parsed = (alwaysMatch) => (0, XenonCapabilityManager_1.getXenonCapabilities)({ alwaysMatch, firstMatch: [{}] });
|
|
133
|
+
function stubSessionStart() {
|
|
134
|
+
sinon_1.default.stub(assets, 'prepareDirectory');
|
|
135
|
+
sinon_1.default.stub(sessionService, 'getOrCreateNewBuild').resolves({ id: 'b-logs' });
|
|
136
|
+
sinon_1.default.stub(TracingService_1.TracingService.prototype, 'getTraceId').returns('t-1');
|
|
137
|
+
sinon_1.default.stub(MetricsService_1.MetricsService.prototype, 'incrementSessionStart');
|
|
138
|
+
sinon_1.default.stub(prisma_1.prisma.session, 'create').resolves({});
|
|
139
|
+
return {
|
|
140
|
+
session: {
|
|
141
|
+
getId: () => 's-logs-off',
|
|
142
|
+
getCapabilities: () => ({ platformName: 'Android' }),
|
|
143
|
+
getLiveVideoUrl: () => null,
|
|
144
|
+
apiKeyId: null,
|
|
145
|
+
userId: null,
|
|
146
|
+
},
|
|
147
|
+
device: { udid: 'phone-l', platform: 'android', host: 'h', name: 'S9', sdk: '10' },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
it('records it when the session says nothing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
+
const { session, device } = stubSessionStart();
|
|
152
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStarted(parsed({}), session, device);
|
|
153
|
+
(0, chai_1.expect)(deviceLogs.start.calledOnce).to.equal(true);
|
|
154
|
+
(0, chai_1.expect)(deviceLogs.noteOff.called).to.equal(false);
|
|
155
|
+
}));
|
|
156
|
+
it('keeps none, and says so, for a session that turned it off', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
+
const { session, device } = stubSessionStart();
|
|
158
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStarted(parsed({ 'xe:save_device_logs': false }), session, device);
|
|
159
|
+
(0, chai_1.expect)(deviceLogs.start.called).to.equal(false);
|
|
160
|
+
(0, chai_1.expect)(deviceLogs.noteOff.calledOnceWithExactly('s-logs-off')).to.equal(true);
|
|
161
|
+
}));
|
|
162
|
+
it("doesn't listen to an iPhone's driver log when the session turned it off", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
163
|
+
const driverLog = { on: sinon_1.default.stub(), removeListener: sinon_1.default.stub() };
|
|
164
|
+
const { session, device } = startSession('ios', () => driverLog);
|
|
165
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStarted(parsed({ 'xe:saveDeviceLogs': 'false' }), session, device);
|
|
166
|
+
(0, chai_1.expect)(deviceLogs.start.called).to.equal(false);
|
|
167
|
+
(0, chai_1.expect)(deviceLogs.noteOff.calledOnceWithExactly('s-logs-1')).to.equal(true);
|
|
168
|
+
(0, chai_1.expect)(driverLog.on.called).to.equal(false);
|
|
169
|
+
}));
|
|
170
|
+
it("hands an iPhone's session the log its driver captures, and its app", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
|
+
const driverLog = { on() { }, removeListener() { } };
|
|
172
|
+
const { session, device } = startSession('ios', () => driverLog);
|
|
173
|
+
session.appUnderTest = () => 'com.example.shop';
|
|
174
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStarted({}, session, device);
|
|
175
|
+
(0, chai_1.expect)(deviceLogs.start.firstCall.args[0].driverLog).to.equal(driverLog);
|
|
176
|
+
(0, chai_1.expect)(deviceLogs.start.firstCall.args[0].appUnderTest).to.equal('com.example.shop');
|
|
177
|
+
}));
|
|
178
|
+
it('writes it to the end when the session stops, even one no longer in memory', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
179
|
+
sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'getSession').returns(undefined);
|
|
180
|
+
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({ getDevices: () => __awaiter(void 0, void 0, void 0, function* () { return []; }) });
|
|
181
|
+
sinon_1.default
|
|
182
|
+
.stub(prisma_1.prisma.session, 'findFirst')
|
|
183
|
+
.resolves({ id: 's-logs-2', status: 'running', device_udid: 'phone-l' });
|
|
184
|
+
sinon_1.default.stub(prisma_1.prisma.session, 'update').resolves({});
|
|
185
|
+
sinon_1.default.stub(prisma_1.prisma.sessionLog, 'findFirst').resolves(null);
|
|
186
|
+
let statusWritten = false;
|
|
187
|
+
prisma_1.prisma.session.update.callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
188
|
+
statusWritten = true;
|
|
189
|
+
return {};
|
|
190
|
+
}));
|
|
191
|
+
deviceLogs.stop.callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
192
|
+
// The session page reads the device log again once the session has
|
|
193
|
+
// ended: by then every line is written.
|
|
194
|
+
(0, chai_1.expect)(statusWritten, 'the session was marked ended first').to.equal(false);
|
|
195
|
+
}));
|
|
196
|
+
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStopped('s-logs-2');
|
|
197
|
+
(0, chai_1.expect)(deviceLogs.stop.calledOnceWithExactly('s-logs-2')).to.equal(true);
|
|
198
|
+
(0, chai_1.expect)(statusWritten).to.equal(true);
|
|
199
|
+
}));
|
|
200
|
+
});
|
|
201
|
+
describe('createSession: when the phone was given to the session', () => {
|
|
202
|
+
(0, loki_stores_1.useLokiStores)();
|
|
203
|
+
let context;
|
|
204
|
+
let saved;
|
|
205
|
+
let authDisabledBefore;
|
|
206
|
+
beforeEach(() => {
|
|
207
|
+
authDisabledBefore = config_1.config.authDisabled;
|
|
208
|
+
config_1.config.authDisabled = true;
|
|
209
|
+
context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
210
|
+
saved = { pluginArgs: context.pluginArgs, nodeId: context.nodeId };
|
|
211
|
+
context.pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, { bindHostOrIp: '127.0.0.1' });
|
|
212
|
+
context.nodeId = 'node-here';
|
|
213
|
+
});
|
|
214
|
+
afterEach(() => {
|
|
215
|
+
config_1.config.authDisabled = authDisabledBefore;
|
|
216
|
+
Object.assign(context, saved);
|
|
217
|
+
sinon_1.default.restore();
|
|
218
|
+
});
|
|
219
|
+
it('is noted on the session, between the allocation and the driver starting', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
220
|
+
const device = {
|
|
221
|
+
udid: 'u1',
|
|
222
|
+
host: 'h1',
|
|
223
|
+
platform: 'android',
|
|
224
|
+
nodeId: 'node-here',
|
|
225
|
+
teamId: null,
|
|
226
|
+
};
|
|
227
|
+
let allocatedBy = 0;
|
|
228
|
+
let driverAt = 0;
|
|
229
|
+
sinon_1.default.stub(pendingSessions, 'addNewPendingSession').resolves();
|
|
230
|
+
sinon_1.default.stub(pendingSessions, 'removePendingSession').resolves();
|
|
231
|
+
sinon_1.default.stub(deviceService, 'updateDeviceProgress').resolves();
|
|
232
|
+
sinon_1.default.stub(deviceService, 'updatedAllocatedDevice').resolves();
|
|
233
|
+
sinon_1.default.stub(deviceUtils, 'allocateDeviceForSession').callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
234
|
+
allocatedBy = Date.now();
|
|
235
|
+
return device;
|
|
236
|
+
}));
|
|
237
|
+
const svc = new SessionLifecycleService_1.SessionLifecycleService();
|
|
238
|
+
const instance = {};
|
|
239
|
+
sinon_1.default.stub(svc, 'createSessionInstance').returns(instance);
|
|
240
|
+
sinon_1.default.stub(svc, 'applyPostSessionLogic').resolves();
|
|
241
|
+
const next = sinon_1.default.stub().callsFake(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
242
|
+
driverAt = Date.now();
|
|
243
|
+
// A slow driver start: the session's log still counts from before it.
|
|
244
|
+
yield new Promise((r) => setTimeout(r, 20));
|
|
245
|
+
return { value: ['s-alloc', { platformName: 'Android' }] };
|
|
246
|
+
}));
|
|
247
|
+
yield svc.createSession(next, {}, {
|
|
248
|
+
alwaysMatch: { platformName: 'Android', 'appium:automationName': 'UiAutomator2' },
|
|
249
|
+
firstMatch: [{}],
|
|
250
|
+
});
|
|
251
|
+
(0, chai_1.expect)(instance.allocatedAt).to.be.a('number');
|
|
252
|
+
(0, chai_1.expect)(instance.allocatedAt).to.be.at.least(allocatedBy);
|
|
253
|
+
(0, chai_1.expect)(instance.allocatedAt).to.be.at.most(driverAt);
|
|
254
|
+
}));
|
|
255
|
+
});
|
|
@@ -332,7 +332,7 @@ describe('Dashboard events name their phone (team-scoped call sites)', () => {
|
|
|
332
332
|
config_1.config.sessionAssetsPath = assets;
|
|
333
333
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
334
334
|
});
|
|
335
|
-
it("request, started and stopped: the session's device, even after stop() drops the state", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
335
|
+
it("request, started and stopped: the session's device, admins only, even after stop() drops the state", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
336
336
|
const svc = new InterceptorService_1.InterceptorService();
|
|
337
337
|
typedi_1.Container.set(PortAllocator_1.PortAllocator, { releaseForUdid: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }) });
|
|
338
338
|
svc.androidAdapter = {
|
|
@@ -353,16 +353,19 @@ describe('Dashboard events name their phone (team-scoped call sites)', () => {
|
|
|
353
353
|
svc.emit({ type: 'request', sessionId: 's4', payload: { id: 'q1' } }, 'phone-a');
|
|
354
354
|
svc.emit({ type: 'session_started', sessionId: 's4', port: 1, host: 'h' }, 'phone-a');
|
|
355
355
|
yield svc.stop('s4');
|
|
356
|
+
// Captured traffic (headers, bodies) is for admins only, as REST's
|
|
357
|
+
// /interceptor routes are; so are the capture's start and stop.
|
|
356
358
|
(0, chai_1.expect)(scoped(SocketEvents_1.SocketEvents.INTERCEPTOR_REQUEST)[0].args.slice(1)).to.deep.equal([
|
|
357
359
|
{ id: 'q1' },
|
|
358
|
-
{ udid: 'phone-a' },
|
|
360
|
+
{ udid: 'phone-a', adminOnly: true },
|
|
359
361
|
]);
|
|
360
362
|
(0, chai_1.expect)(scoped(SocketEvents_1.SocketEvents.INTERCEPTOR_SESSION_STARTED)[0].args[2]).to.deep.equal({
|
|
361
363
|
udid: 'phone-a',
|
|
364
|
+
adminOnly: true,
|
|
362
365
|
});
|
|
363
366
|
(0, chai_1.expect)(scoped(SocketEvents_1.SocketEvents.INTERCEPTOR_SESSION_STOPPED)[0].args.slice(1)).to.deep.equal([
|
|
364
367
|
{ sessionId: 's4' },
|
|
365
|
-
{ udid: 'phone-a' },
|
|
368
|
+
{ udid: 'phone-a', adminOnly: true },
|
|
366
369
|
]);
|
|
367
370
|
}));
|
|
368
371
|
});
|