@xenon-device-management/xenon 2.13.2 → 2.15.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 +17 -8
- package/lib/package.json +5 -5
- package/lib/public/assets/{AnnotationOverlay-BXruClDk.js → AnnotationOverlay-Btq3dZRI.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-qTS4LPLC.js → ApiKeyGate-C9OoAgg_.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-DBC_xczB.js +2 -0
- package/lib/public/assets/{EmptyState-Cr17PdDv.js → EmptyState-DqWU2-vM.js} +1 -1
- package/lib/public/assets/{FieldGroup-Bvn08svv.js → FieldGroup-CveQ3xrA.js} +1 -1
- package/lib/public/assets/{FilterMenu-DDSO8aDf.js → FilterMenu-BfKDSfHz.js} +1 -1
- package/lib/public/assets/{Menu-B-Qn2X3T.js → Menu-CtdCybzA.js} +1 -1
- package/lib/public/assets/{Modal-Cg9V5JYU.js → Modal-Bsvj1tm2.js} +1 -1
- package/lib/public/assets/{RecordingPage-BTUs9VTW.js → RecordingPage-CRofOiyx.js} +1 -1
- package/lib/public/assets/{RecordingsPage-DYcfxITK.js → RecordingsPage-jNQHb2IR.js} +2 -2
- package/lib/public/assets/{SegmentedControl-Zy7suhT5.js → SegmentedControl-C8HtTha-.js} +1 -1
- package/lib/public/assets/{SettingCard-CWYmycoe.js → SettingCard-D8K1LZNo.js} +1 -1
- package/lib/public/assets/{Table-io9fwa98.js → Table-zNTX92Pn.js} +1 -1
- package/lib/public/assets/{activity-Bdf_uIWl.js → activity-CJ8LwZq9.js} +1 -1
- package/lib/public/assets/ai-settings-CgxQZqrc.js +16 -0
- package/lib/public/assets/{api-keys-CPqtVpm3.js → api-keys-ECpo-zTo.js} +1 -1
- package/lib/public/assets/{apps-BgZan0H9.js → apps-BjGymyf3.js} +2 -2
- package/lib/public/assets/{arrow-left-B27ckB31.js → arrow-left-BWfCbeHP.js} +1 -1
- package/lib/public/assets/{arrow-right-DOF8ITCz.js → arrow-right-DC7UGf_n.js} +1 -1
- package/lib/public/assets/{arrow-up-right-eFG5KTFS.js → arrow-up-right-BOjwybDk.js} +1 -1
- package/lib/public/assets/{auth-shell-DlC3_27j.js → auth-shell-CxMTo9aH.js} +2 -2
- package/lib/public/assets/{builds-page-BuXpPfKo.js → builds-page-DOCm4HkA.js} +1 -1
- package/lib/public/assets/{button-fIW8owMI.js → button-Cg08-EMT.js} +1 -1
- package/lib/public/assets/{calendar-WNjmrF-a.js → calendar-CA1ikLJs.js} +1 -1
- package/lib/public/assets/{check-BdTh1Sq7.js → check-BMTaH72W.js} +1 -1
- package/lib/public/assets/{chevron-right-B4BKlsqk.js → chevron-right-B2QOv9Nx.js} +1 -1
- package/lib/public/assets/{circle-check-zJU80_EF.js → circle-check-CYXBoUqT.js} +1 -1
- package/lib/public/assets/{circle-x-5Uwquo0h.js → circle-x-DTwnSAnl.js} +1 -1
- package/lib/public/assets/{clock-BtUprDsZ.js → clock-C8_HViQP.js} +1 -1
- package/lib/public/assets/{copy-DGSbZI3u.js → copy-Be7LNMXV.js} +1 -1
- package/lib/public/assets/derive-UnLRp4r3.js +1 -0
- package/lib/public/assets/device-explorer-BOqtfGXz.js +282 -0
- package/lib/public/assets/{download-WG6l7BgD.js → download-B56lwxWP.js} +1 -1
- package/lib/public/assets/forgot-password-CCU6bzLn.js +1 -0
- package/lib/public/assets/h264Stream-C-OtmIuk.js +26 -0
- package/lib/public/assets/index-B9KQ6LsY.js +15 -0
- package/lib/public/assets/{index-DI1fGIyj.js → index-BOxe43p1.js} +5 -5
- package/lib/public/assets/{index-HDQVe1gi.js → index-CfxsUslp.js} +1 -1
- package/lib/public/assets/{input-BjDRKCea.js → input-CZHFjbSg.js} +1 -1
- package/lib/public/assets/{line-chart-C0zdgd0v.js → line-chart-Gl_Dz3X_.js} +1 -1
- package/lib/public/assets/{list-checks-BdW5CjM8.js → list-checks-CCgjeyGY.js} +1 -1
- package/lib/public/assets/{lock-Dy3L4I8E.js → lock-CkOwgDcx.js} +1 -1
- package/lib/public/assets/{login-BHbY9CLG.js → login-D5cc4w7M.js} +1 -1
- package/lib/public/assets/maintenance-settings-t7qr3fvX.js +6 -0
- package/lib/public/assets/{monitor-8xjkeHHi.js → monitor-DEJixKQO.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CXdKMXQ8.js → mouse-pointer-2-CtOrk4M8.js} +1 -1
- package/lib/public/assets/{network-Cj8JOKON.js → network-Dii9qdvk.js} +1 -1
- package/lib/public/assets/{overview-CTg7F0im.js → overview-rolAACrO.js} +1 -1
- package/lib/public/assets/{page-header--aTyJXq6.js → page-header-COQZxJfR.js} +1 -1
- package/lib/public/assets/{play-B-FodgHV.js → play-DrN1Se_R.js} +1 -1
- package/lib/public/assets/{plus-CsIk10Cq.js → plus-5aq-9uRx.js} +1 -1
- package/lib/public/assets/{profile-page-DkFKkMfl.js → profile-page-6i5kY6kF.js} +1 -1
- package/lib/public/assets/{reset-password-I2-zqLlI.js → reset-password-sp2IBrc7.js} +1 -1
- package/lib/public/assets/runbook-page-DJDjeopU.js +253 -0
- package/lib/public/assets/{select-DvCSv25z.js → select-Bpyn7rWO.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BLVfMJs_.js → selector-detail-redirect-BH74vmKv.js} +1 -1
- package/lib/public/assets/{selector-health-page-oqadRokn.js → selector-health-page-Cw_g218H.js} +2 -2
- package/lib/public/assets/session-detail-page-AZJE6XXE.js +4 -0
- package/lib/public/assets/settings-C3uyGhyc.js +6 -0
- package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
- package/lib/public/assets/{stat-tile-DV9Ix-4O.js → stat-tile-xVqAKIlI.js} +1 -1
- package/lib/public/assets/{tablet-6b_SEN2X.js → tablet-BxUScAeO.js} +1 -1
- package/lib/public/assets/{teams-BjZyfuHh.js → teams-B5ljGkY2.js} +1 -1
- package/lib/public/assets/{trash-2-CTZpPSdd.js → trash-2-B701i8sy.js} +1 -1
- package/lib/public/assets/{upload-P3XzwQUb.js → upload-C2gr44RS.js} +1 -1
- package/lib/public/assets/{use-builds-data-1cOCMf-z.js → use-builds-data-NeQOFEoy.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DgF9w088.js → use-password-reset-mode-DEzJq5ef.js} +1 -1
- package/lib/public/assets/{users-DMF9Mk5H.js → users-D_vv_yz4.js} +1 -1
- package/lib/public/assets/{users-CiNTKUiz.js → users-MY6eNe6s.js} +1 -1
- package/lib/public/assets/{video-off-CHp1KU_T.js → video-off-OK1lNZQn.js} +1 -1
- package/lib/public/assets/webhook-settings-BU-C9lBf.js +1 -0
- package/lib/public/assets/{zap-DRz0tDq5.js → zap-DO9ry1iz.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +23 -25
- package/lib/src/XenonCapabilityManager.js +22 -5
- package/lib/src/app/dashboardPluginLink.js +117 -0
- package/lib/src/app/index.js +13 -34
- package/lib/src/app/openapi/control.yaml +73 -20
- package/lib/src/app/openapi/grid.yaml +31 -7
- package/lib/src/app/openapi/identity.yaml +103 -31
- package/lib/src/app/openapi/platform.yaml +165 -39
- package/lib/src/app/openapi/sessions.yaml +10 -3
- package/lib/src/app/routers/apikeys.js +14 -2
- package/lib/src/app/routers/auth.js +29 -5
- package/lib/src/app/routers/control.js +20 -2
- package/lib/src/app/routers/dashboard.js +69 -26
- package/lib/src/app/routers/grid.js +37 -13
- package/lib/src/app/routers/profile.js +21 -0
- package/lib/src/app/routers/streamType.js +7 -2
- package/lib/src/app/routers/users.js +10 -5
- package/lib/src/app/routers/webhook.js +8 -4
- package/lib/src/app/swagger.js +2 -0
- package/lib/src/app/ws/h264StreamWs.js +10 -1
- package/lib/src/config.js +51 -5
- package/lib/src/dashboard/commands.js +185 -141
- package/lib/src/dashboard/event-manager.js +88 -67
- package/lib/src/dashboard/services/failure-analysis-service.js +136 -45
- package/lib/src/dashboard/sessionCommandSummary.js +39 -0
- package/lib/src/data-service/appleFamily.js +43 -0
- package/lib/src/data-service/db.js +11 -0
- package/lib/src/data-service/device-service.js +6 -1
- package/lib/src/data-service/device-store.js +76 -3
- package/lib/src/data-service/deviceClaims.js +37 -0
- package/lib/src/data-service/deviceFieldOwners.js +25 -11
- package/lib/src/data-service/deviceSettings.js +79 -0
- package/lib/src/data-service/prisma-store.js +169 -15
- package/lib/src/data-service/web-config-service.js +50 -92
- package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
- package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
- package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
- package/lib/src/device-managers/ios/WDAClient.js +44 -43
- package/lib/src/device-managers/shellCommands.js +96 -0
- package/lib/src/device-utils.js +114 -40
- package/lib/src/gateway/defaultGateway.js +1 -1
- package/lib/src/gateway/forwardToNode.js +2 -0
- package/lib/src/gateway/healReport.js +87 -0
- package/lib/src/gateway/sessionGateway.js +12 -2
- package/lib/src/generated/client/edge.js +18 -3
- package/lib/src/generated/client/index-browser.js +15 -0
- package/lib/src/generated/client/index.d.ts +2086 -379
- package/lib/src/generated/client/index.js +18 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +38 -7
- package/lib/src/generated/client/wasm.js +15 -0
- package/lib/src/interceptors/CommandInterceptor.js +271 -188
- package/lib/src/interceptors/xenonScripts.js +55 -0
- package/lib/src/interfaces/IPluginArgs.js +1 -2
- 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 +23 -4
- package/lib/src/prisma.js +1 -1
- package/lib/src/scripts/database-check.js +124 -0
- package/lib/src/services/AICommandService.js +9 -2
- package/lib/src/services/AIService.js +281 -32
- package/lib/src/services/CleanupService.js +6 -1
- package/lib/src/services/EmailService.js +29 -0
- package/lib/src/services/InterceptorService.js +70 -13
- package/lib/src/services/NetworkConditioningService.js +85 -88
- package/lib/src/services/NotificationService.js +36 -29
- package/lib/src/services/OrphanSweeper.js +7 -0
- package/lib/src/services/PortAllocator.js +5 -0
- package/lib/src/services/SelectorVerificationJob.js +15 -4
- package/lib/src/services/ServerManager.js +105 -9
- package/lib/src/services/SessionLifecycleService.js +133 -75
- package/lib/src/services/ShutdownCoordinator.js +33 -0
- package/lib/src/services/SocketServer.js +19 -3
- package/lib/src/services/TeamService.js +20 -3
- 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 +85 -71
- 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/interceptor/AndroidProxyAdapter.js +18 -0
- package/lib/src/services/interceptor/interceptorOptions.js +44 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +271 -0
- package/lib/src/services/logcat/deviceLogBook.js +163 -0
- package/lib/src/services/network/AndroidRadios.js +71 -0
- package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
- package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
- package/lib/src/services/network/adbForPhone.js +60 -0
- package/lib/src/services/network/phoneNetworkLock.js +20 -0
- package/lib/src/services/network/xenonProxy.js +107 -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 +132 -95
- package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
- package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
- package/lib/src/services/omni-vision/screenScale.js +68 -0
- 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/recording/concurrency-gate.js +12 -3
- package/lib/src/services/session/sessionIdentity.js +5 -3
- package/lib/src/services/session/shutdownReason.js +26 -0
- package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
- package/lib/src/services/settings/aiEngineSettings.js +219 -0
- package/lib/src/services/settings/labSettings.js +149 -0
- package/lib/src/services/token/mintedLifetime.js +51 -0
- package/lib/src/services/webhookEvents.js +173 -0
- package/lib/src/sessions/SessionManager.js +8 -2
- package/lib/src/sessions/sessionMemory.js +25 -0
- package/lib/test/helpers/fake-adb.js +58 -0
- package/lib/test/helpers/fake-ai-provider.js +188 -0
- package/lib/test/helpers/fake-device-table.js +63 -24
- package/lib/test/helpers/loki-stores.js +33 -0
- package/lib/test/helpers/page-sources.js +164 -0
- package/lib/test/helpers/scratch-database.js +58 -13
- 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/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/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/TeamService.test.js +3 -0
- package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
- 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-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/apple-family.spec.js +36 -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/cleanup-settings-db.spec.js +103 -0
- package/lib/test/unit/cleanup-settings.spec.js +244 -0
- package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
- package/lib/test/unit/dashboard-commands.spec.js +249 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +207 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
- package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
- package/lib/test/unit/database-check.spec.js +108 -0
- package/lib/test/unit/default-plugin-args.spec.js +33 -0
- package/lib/test/unit/device-log-book.spec.js +180 -0
- package/lib/test/unit/device-offline-webhook.spec.js +81 -0
- package/lib/test/unit/device-setting-columns.spec.js +66 -0
- package/lib/test/unit/device-settings-loki.spec.js +87 -0
- package/lib/test/unit/device-settings-survive.spec.js +555 -0
- package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
- package/lib/test/unit/emulator-boot.spec.js +91 -0
- package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
- package/lib/test/unit/event-log-session-data.spec.js +244 -0
- package/lib/test/unit/failure-analysis.spec.js +182 -0
- package/lib/test/unit/failure-categories.spec.js +128 -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 +73 -29
- package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +219 -0
- package/lib/test/unit/hub-node-same-host.spec.js +5 -0
- package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
- package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
- package/lib/test/unit/json-logging-option.spec.js +74 -0
- package/lib/test/unit/lab-settings.spec.js +218 -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/max-sessions.spec.js +240 -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/ocr-text-match.spec.js +44 -0
- package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
- package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +159 -0
- package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
- package/lib/test/unit/omni-vision.spec.js +6 -8
- package/lib/test/unit/package-engines.spec.js +27 -0
- package/lib/test/unit/partial-config-file.spec.js +14 -0
- package/lib/test/unit/passwordResetMode.test.js +10 -2
- package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
- package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
- package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
- package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
- package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
- package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
- package/lib/test/unit/phone-network/session-start.spec.js +110 -0
- package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
- package/lib/test/unit/plugin.spec.js +3 -3
- package/lib/test/unit/profile-router.test.js +122 -1
- package/lib/test/unit/queue-requester.spec.js +1 -1
- package/lib/test/unit/recording-options.spec.js +181 -0
- 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/selector-learning-every-session.spec.js +179 -0
- package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
- package/lib/test/unit/selector-verification-job.spec.js +13 -13
- package/lib/test/unit/self-healing-boot.spec.js +80 -0
- package/lib/test/unit/self-healing-setting.spec.js +273 -0
- 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.spec.js +347 -0
- package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
- package/lib/test/unit/session-failed-webhook.spec.js +170 -0
- package/lib/test/unit/session-log-indexes.spec.js +64 -3
- package/lib/test/unit/shell-commands.spec.js +133 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
- package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
- package/lib/test/unit/stream-start-pipelines.spec.js +264 -0
- package/lib/test/unit/stream-status-type.spec.js +4 -0
- package/lib/test/unit/token-mint-limits.spec.js +165 -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 +119 -0
- package/lib/test/unit/wda-client-shell.spec.js +133 -0
- package/lib/test/unit/web-config-service.spec.js +98 -0
- package/lib/test/unit/webhook-delivery.spec.js +24 -0
- package/lib/test/unit/webhook-payloads.spec.js +273 -0
- package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
- package/package.json +5 -5
- package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
- package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
- package/prisma/schema.prisma +38 -7
- package/schema.json +23 -25
- package/scripts/generate-types-from-schema.js +1 -2
- package/scripts/lib/always-present-plugin-args.js +4 -1
- package/lib/public/assets/BugReportButton-BoGhlI7F.js +0 -15
- package/lib/public/assets/DeviceMosaicView-8J0oYqMM.js +0 -2
- package/lib/public/assets/ai-settings-Bce5SJob.js +0 -21
- package/lib/public/assets/device-explorer-C-KnhMHI.js +0 -279
- package/lib/public/assets/forgot-password-DnudqFFo.js +0 -1
- package/lib/public/assets/maintenance-settings-zDVlOMVK.js +0 -6
- package/lib/public/assets/recording-group-store-WVM3hoXn.js +0 -26
- package/lib/public/assets/runbook-page-DJTVCJX7.js +0 -124
- package/lib/public/assets/session-detail-page-CGn55Pvl.js +0 -4
- package/lib/public/assets/settings-MsVU2ukE.js +0 -6
- package/lib/public/assets/webhook-settings-DiS8geIm.js +0 -1
- /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
const chai_1 = require("chai");
|
|
16
|
+
const http_1 = __importDefault(require("http"));
|
|
17
|
+
const healReport_1 = require("../../src/gateway/healReport");
|
|
18
|
+
/**
|
|
19
|
+
* How a node hands a heal back to its hub on the answer to the hub's command
|
|
20
|
+
* (healReport.ts): what goes on the answer, and what the hub accepts off it.
|
|
21
|
+
*/
|
|
22
|
+
describe('A heal reported to the hub', () => {
|
|
23
|
+
const answer = () => new http_1.default.ServerResponse(new http_1.default.IncomingMessage(null));
|
|
24
|
+
const heal = (selector) => ({
|
|
25
|
+
originalSelector: selector,
|
|
26
|
+
originalStrategy: 'xpath',
|
|
27
|
+
healedSelector: "//*[@text='Anmelden']",
|
|
28
|
+
healedStrategy: 'xpath',
|
|
29
|
+
confidence: 0.82,
|
|
30
|
+
tier: 2,
|
|
31
|
+
});
|
|
32
|
+
it('goes on the answer and comes off it whole, whatever its characters', () => {
|
|
33
|
+
const res = answer();
|
|
34
|
+
const sent = heal("//*[@text='Ünïcödé “quotes”']");
|
|
35
|
+
(0, chai_1.expect)((0, healReport_1.runReportingHeals)(res, () => (0, healReport_1.reportHeal)(sent))).to.equal('reported');
|
|
36
|
+
const headers = { [healReport_1.HEAL_REPORT_HEADER]: String(res.getHeader(healReport_1.HEAL_REPORT_HEADER)) };
|
|
37
|
+
(0, chai_1.expect)((0, healReport_1.takeHealReport)(headers)).to.deep.equal(sent);
|
|
38
|
+
(0, chai_1.expect)(headers, 'taken off the headers').to.not.have.property(healReport_1.HEAL_REPORT_HEADER);
|
|
39
|
+
});
|
|
40
|
+
it('is not sent for a command that did not come from a hub', () => {
|
|
41
|
+
(0, chai_1.expect)((0, healReport_1.reportHeal)(heal('//a'))).to.equal('not-from-hub');
|
|
42
|
+
});
|
|
43
|
+
it('is not sent when too long for the hub to read, so the command still answers', () => {
|
|
44
|
+
const res = answer();
|
|
45
|
+
const reported = (0, healReport_1.runReportingHeals)(res, () => (0, healReport_1.reportHeal)(heal(`//${'x'.repeat(10000)}`)));
|
|
46
|
+
(0, chai_1.expect)(reported).to.equal('too-long');
|
|
47
|
+
(0, chai_1.expect)(res.getHeader(healReport_1.HEAL_REPORT_HEADER)).to.equal(undefined);
|
|
48
|
+
});
|
|
49
|
+
it('is not put on an answer that has closed, by work the command left running', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
const res = answer();
|
|
51
|
+
let release;
|
|
52
|
+
const gate = new Promise((resolve) => (release = resolve));
|
|
53
|
+
// Async work started during the command keeps its context after it.
|
|
54
|
+
const lingering = (0, healReport_1.runReportingHeals)(res, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
+
yield gate;
|
|
56
|
+
return (0, healReport_1.reportHeal)(heal('//late'));
|
|
57
|
+
}));
|
|
58
|
+
res.emit('close');
|
|
59
|
+
release();
|
|
60
|
+
(0, chai_1.expect)(yield lingering).to.equal('not-from-hub');
|
|
61
|
+
}));
|
|
62
|
+
it('is ignored when it is not one', () => {
|
|
63
|
+
const notJson = { [healReport_1.HEAL_REPORT_HEADER]: 'not base64 json' };
|
|
64
|
+
const notAHeal = {
|
|
65
|
+
[healReport_1.HEAL_REPORT_HEADER]: Buffer.from('{"originalSelector":1}').toString('base64url'),
|
|
66
|
+
};
|
|
67
|
+
(0, chai_1.expect)((0, healReport_1.takeHealReport)(notJson)).to.equal(undefined);
|
|
68
|
+
(0, chai_1.expect)((0, healReport_1.takeHealReport)(notAHeal)).to.equal(undefined);
|
|
69
|
+
(0, chai_1.expect)((0, healReport_1.takeHealReport)({})).to.equal(undefined);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -187,7 +187,11 @@ describe('HealingOrchestrator instrumentation', () => {
|
|
|
187
187
|
(0, chai_1.expect)(mockSpan.end.calledOnce).to.equal(true);
|
|
188
188
|
}));
|
|
189
189
|
it('marks span ERROR and ends on context-collection failure (driver throws)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
190
|
-
|
|
190
|
+
// A tier to run, or nothing is collected at all; it never runs here.
|
|
191
|
+
const heal = sinon_1.default.stub().resolves(null);
|
|
192
|
+
const orch = makeOrchestratorWithProviders([
|
|
193
|
+
{ name: 'p1', tier: types_1.HealingTier.TIER_1_RECOVERY, heal },
|
|
194
|
+
]);
|
|
191
195
|
const driver = {
|
|
192
196
|
getPageSource: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
197
|
throw new Error('disconnected');
|
|
@@ -198,6 +202,7 @@ describe('HealingOrchestrator instrumentation', () => {
|
|
|
198
202
|
(0, chai_1.expect)(result).to.equal(null);
|
|
199
203
|
(0, chai_1.expect)(mockSpan.addEvent.calledWith('context_collection_failed')).to.equal(true);
|
|
200
204
|
(0, chai_1.expect)(mockSpan.end.calledOnce).to.equal(true);
|
|
205
|
+
(0, chai_1.expect)(heal.called).to.equal(false);
|
|
201
206
|
}));
|
|
202
207
|
it('does not put the original selector text on span attributes (cardinality discipline)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
203
208
|
const provider = {
|
|
@@ -51,54 +51,84 @@ describe('filterProvidersByTier (§2.7 healing-tier gate)', () => {
|
|
|
51
51
|
const result = (0, HealingOrchestrator_1.filterProvidersByTier)(providers, []);
|
|
52
52
|
(0, chai_1.expect)(result).to.deep.equal([]);
|
|
53
53
|
});
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
it('passes a numeric array through unchanged', () => {
|
|
54
|
+
// The value is a privacy control: a session that leaves out Visual AI (4)
|
|
55
|
+
// and the LLM (5) keeps its screen away from the AI provider. So a value
|
|
56
|
+
// that can't be read fails closed for those two, and open for the rest:
|
|
57
|
+
// the tiers that stay on the server (1, 2, 3) still run. Through 2.14 any
|
|
58
|
+
// such value, and an empty list, ran every tier, the AI ones included.
|
|
59
|
+
describe('coerceHealingTiersCap', () => {
|
|
60
|
+
it('passes a list of tier numbers through unchanged', () => {
|
|
62
61
|
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)([1, 3])).to.deep.equal([1, 3]);
|
|
62
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)([5, 4, 1])).to.deep.equal([5, 4, 1]);
|
|
63
63
|
});
|
|
64
|
-
it('
|
|
65
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)([1, '2'])).to.deep.equal([1]);
|
|
66
|
-
});
|
|
67
|
-
it('non-array (string/undefined/null) → undefined (run all)', () => {
|
|
68
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)('1,2')).to.equal(undefined);
|
|
64
|
+
it('runs every tier when the session sets nothing', () => {
|
|
69
65
|
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)(undefined)).to.equal(undefined);
|
|
70
66
|
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)(null)).to.equal(undefined);
|
|
71
67
|
});
|
|
72
|
-
it('
|
|
73
|
-
|
|
74
|
-
(0, chai_1.expect)(coerced).to.equal(undefined);
|
|
75
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.filterProvidersByTier)(providers, coerced)).to.deep.equal(providers);
|
|
76
|
-
});
|
|
77
|
-
it('explicitly-empty [] also → undefined (run all; the cap restricts, it cannot disable)', () => {
|
|
78
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)([])).to.equal(undefined);
|
|
68
|
+
it('runs no tier for an empty list', () => {
|
|
69
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)([])).to.deep.equal([]);
|
|
79
70
|
});
|
|
71
|
+
for (const [label, value] of [
|
|
72
|
+
['a string', '1,2'],
|
|
73
|
+
['numbers as strings', ['1', '2']],
|
|
74
|
+
['a list with a string in it', [1, '2']],
|
|
75
|
+
['a number that is no tier', [1, 6]],
|
|
76
|
+
['tier 0', [0]],
|
|
77
|
+
['a fraction', [1.5]],
|
|
78
|
+
['a lone number', 4],
|
|
79
|
+
['an object', { llm: false }],
|
|
80
|
+
['true', true],
|
|
81
|
+
]) {
|
|
82
|
+
it(`runs only the tiers that stay on the server for ${label}`, () => {
|
|
83
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.coerceHealingTiersCap)(value)).to.deep.equal([1, 2, 3]);
|
|
84
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.filterProvidersByTier)(providers, (0, HealingOrchestrator_1.coerceHealingTiersCap)(value))).to.deep.equal(providers.slice(0, 3));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
80
87
|
});
|
|
81
88
|
// What CommandInterceptor hands attemptHealing, read from the session's
|
|
82
89
|
// capabilities.
|
|
83
90
|
describe('healingTiersFromCaps', () => {
|
|
84
91
|
it('reads xe:options.healingTiers', () => {
|
|
85
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xe:options': { healingTiers: [1, 2] } })).to.deep.equal(
|
|
86
|
-
1, 2,
|
|
87
|
-
|
|
92
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xe:options': { healingTiers: [1, 2] } })).to.deep.equal({
|
|
93
|
+
tiers: [1, 2],
|
|
94
|
+
});
|
|
88
95
|
});
|
|
89
96
|
it('still reads the xenon:options alias', () => {
|
|
90
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xenon:options': { healingTiers: [3] } })).to.deep.equal(
|
|
97
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xenon:options': { healingTiers: [3] } })).to.deep.equal({
|
|
98
|
+
tiers: [3],
|
|
99
|
+
});
|
|
91
100
|
});
|
|
92
101
|
it('prefers xe:options when both set it', () => {
|
|
93
102
|
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({
|
|
94
103
|
'xenon:options': { healingTiers: [5] },
|
|
95
104
|
'xe:options': { healingTiers: [1] },
|
|
96
|
-
})).to.deep.equal([1]);
|
|
105
|
+
})).to.deep.equal({ tiers: [1] });
|
|
106
|
+
});
|
|
107
|
+
it('runs every tier with no capabilities, or none set', () => {
|
|
108
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)(undefined)).to.deep.equal({ tiers: undefined });
|
|
109
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({})).to.deep.equal({ tiers: undefined });
|
|
110
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xe:options': {} })).to.deep.equal({ tiers: undefined });
|
|
97
111
|
});
|
|
98
|
-
it('
|
|
99
|
-
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)(
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
it('names a value it could not read, for the log', () => {
|
|
113
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xe:options': { healingTiers: ['1'] } })).to.deep.equal({
|
|
114
|
+
tiers: [1, 2, 3],
|
|
115
|
+
unreadable: 'healingTiers is ["1"], not a list of tier numbers from 1 to 5',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// xenonOptionsIn skips a namespace that isn't an object, so this one used
|
|
119
|
+
// to read as "nothing set", and ran every tier.
|
|
120
|
+
it('runs only the tiers that stay on the server when xe:options is not an object', () => {
|
|
121
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({ 'xe:options': '{"healingTiers":[1]}' })).to.deep.equal({
|
|
122
|
+
tiers: [1, 2, 3],
|
|
123
|
+
unreadable: 'xe:options is "{\\"healingTiers\\":[1]}", not an object',
|
|
124
|
+
});
|
|
125
|
+
(0, chai_1.expect)((0, HealingOrchestrator_1.healingTiersFromCaps)({
|
|
126
|
+
'xenon:options': ['healingTiers'],
|
|
127
|
+
'xe:options': { healingTiers: [1, 2, 3, 4, 5] },
|
|
128
|
+
})).to.deep.equal({
|
|
129
|
+
tiers: [1, 2, 3],
|
|
130
|
+
unreadable: 'xenon:options is ["healingTiers"], not an object',
|
|
131
|
+
});
|
|
102
132
|
});
|
|
103
133
|
});
|
|
104
134
|
});
|
|
@@ -136,6 +166,20 @@ describe('HealingOrchestrator.attemptHealing allowedTiers dispatch gate', () =>
|
|
|
136
166
|
yield orchestrator.attemptHealing('sess-1', mockDriver, 'xpath', '//broken', [1]);
|
|
137
167
|
(0, chai_1.expect)(dispatched).to.deep.equal(['ResilioTree Provider']);
|
|
138
168
|
}));
|
|
169
|
+
it('collects nothing and dispatches nothing when no tier is allowed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
170
|
+
let collected = 0;
|
|
171
|
+
const driver = Object.assign(Object.assign({}, mockDriver), { getPageSource: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
172
|
+
collected++;
|
|
173
|
+
return '<xml/>';
|
|
174
|
+
}), getScreenshot: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
175
|
+
collected++;
|
|
176
|
+
return 'fake-screenshot';
|
|
177
|
+
}) });
|
|
178
|
+
const healed = yield orchestrator.attemptHealing('sess-3', driver, 'xpath', '//broken', []);
|
|
179
|
+
(0, chai_1.expect)(healed).to.equal(null);
|
|
180
|
+
(0, chai_1.expect)(dispatched).to.deep.equal([]);
|
|
181
|
+
(0, chai_1.expect)(collected).to.equal(0);
|
|
182
|
+
}));
|
|
139
183
|
it('dispatches to the full provider set when allowedTiers is omitted (regression)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
184
|
yield orchestrator.attemptHealing('sess-2', mockDriver, 'xpath', '//broken');
|
|
141
185
|
(0, chai_1.expect)(dispatched).to.deep.equal([
|
|
@@ -0,0 +1,219 @@
|
|
|
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 typedi_1 = require("typedi");
|
|
19
|
+
const base_driver_1 = require("@appium/base-driver");
|
|
20
|
+
const CommandInterceptor_1 = require("../../src/interceptors/CommandInterceptor");
|
|
21
|
+
const HealingOrchestrator_1 = require("../../src/services/healing/HealingOrchestrator");
|
|
22
|
+
const AIService_1 = require("../../src/services/AIService");
|
|
23
|
+
const SelfHealingSwitch_1 = require("../../src/services/settings/SelfHealingSwitch");
|
|
24
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
25
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
26
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
27
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
28
|
+
/**
|
|
29
|
+
* A session's `xe:options.healingTiers` keeps its screen away from the AI
|
|
30
|
+
* provider: with `[1, 2, 3]`, Visual AI (4) and the LLM (5) never run, so the
|
|
31
|
+
* screenshot and page source never leave the server.
|
|
32
|
+
*
|
|
33
|
+
* Through 2.14 the interceptor read the tiers from SESSION_MANAGER, which holds
|
|
34
|
+
* a local session only with the dashboard on or a video recorded. A session
|
|
35
|
+
* with `record_video: false` on a server with the dashboard off (the default),
|
|
36
|
+
* a node's for its hub included, wasn't there: the interceptor found no
|
|
37
|
+
* capabilities and ran every tier. The tiers are now read from the session's
|
|
38
|
+
* own driver, which Appium hands the plugin with every command. These sessions
|
|
39
|
+
* are registered nowhere, as such a session isn't.
|
|
40
|
+
*/
|
|
41
|
+
describe("a session's healing tiers, on a session the dashboard doesn't track", () => {
|
|
42
|
+
(0, scratch_database_1.useScratchDatabase)();
|
|
43
|
+
let restoreContainer;
|
|
44
|
+
let dispatched;
|
|
45
|
+
let visualFind;
|
|
46
|
+
let healLocator;
|
|
47
|
+
const drivers = [];
|
|
48
|
+
const pluginArgs = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { enableDashboard: false });
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(HealingOrchestrator_1.HealingOrchestrator, SelfHealingSwitch_1.SelfHealingSwitch);
|
|
51
|
+
// The switch as at boot with nothing saved: healing on.
|
|
52
|
+
typedi_1.Container.set(SelfHealingSwitch_1.SelfHealingSwitch, new SelfHealingSwitch_1.SelfHealingSwitch());
|
|
53
|
+
const etalons = {
|
|
54
|
+
getSignature: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
55
|
+
saveSignature: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
56
|
+
};
|
|
57
|
+
const orchestrator = new HealingOrchestrator_1.HealingOrchestrator(etalons);
|
|
58
|
+
dispatched = [];
|
|
59
|
+
const providers = orchestrator.providers;
|
|
60
|
+
// The three local tiers are only recorded. Visual AI and the LLM run as
|
|
61
|
+
// they are, up to the AI service, which is stubbed: what reaches it is
|
|
62
|
+
// what would have gone to the provider.
|
|
63
|
+
for (const provider of providers.slice(0, 3)) {
|
|
64
|
+
provider.heal = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
|
+
dispatched.push(provider.name);
|
|
66
|
+
return null;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (const provider of providers.slice(3)) {
|
|
70
|
+
const heal = provider.heal.bind(provider);
|
|
71
|
+
provider.heal = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
72
|
+
dispatched.push(provider.name);
|
|
73
|
+
return heal(context);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
typedi_1.Container.set(HealingOrchestrator_1.HealingOrchestrator, orchestrator);
|
|
77
|
+
sinon_1.default.stub(AIService_1.AI_SERVICE, 'isEnabled').returns(true);
|
|
78
|
+
visualFind = sinon_1.default.stub(AIService_1.AI_SERVICE, 'visualFind').resolves(null);
|
|
79
|
+
healLocator = sinon_1.default.stub(AIService_1.AI_SERVICE, 'healLocator').resolves(null);
|
|
80
|
+
});
|
|
81
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
+
sinon_1.default.restore();
|
|
83
|
+
restoreContainer();
|
|
84
|
+
for (const driver of drivers.splice(0))
|
|
85
|
+
yield driver.deleteSession();
|
|
86
|
+
}));
|
|
87
|
+
/**
|
|
88
|
+
* The driver Appium hands the plugin for a session's commands: a session
|
|
89
|
+
* created from the capabilities the client sent, as Appium creates it.
|
|
90
|
+
*/
|
|
91
|
+
function sessionWith(xenonOptions) {
|
|
92
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
const driver = new base_driver_1.BaseDriver({});
|
|
94
|
+
// Every driver warns that it doesn't know `xe:options`; not this spec's business.
|
|
95
|
+
sinon_1.default.stub(driver.log, 'warn');
|
|
96
|
+
const alwaysMatch = {
|
|
97
|
+
platformName: 'Android',
|
|
98
|
+
'appium:automationName': 'UiAutomator2',
|
|
99
|
+
};
|
|
100
|
+
if (xenonOptions)
|
|
101
|
+
Object.assign(alwaysMatch, xenonOptions);
|
|
102
|
+
yield driver.createSession({ alwaysMatch, firstMatch: [{}] });
|
|
103
|
+
drivers.push(driver);
|
|
104
|
+
Object.assign(driver, {
|
|
105
|
+
getPageSource: () => __awaiter(this, void 0, void 0, function* () { return '<hierarchy><node text="Pay now"/></hierarchy>'; }),
|
|
106
|
+
getScreenshot: () => __awaiter(this, void 0, void 0, function* () { return 'iVBORw0KGgo='; }),
|
|
107
|
+
findElements: () => __awaiter(this, void 0, void 0, function* () { return []; }),
|
|
108
|
+
});
|
|
109
|
+
return driver;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** A findElement the driver answers with NoSuchElement, as a broken selector does. */
|
|
113
|
+
function findMissing(driver_1, _a) {
|
|
114
|
+
return __awaiter(this, arguments, void 0, function* (driver, { isHub }) {
|
|
115
|
+
const sessionId = driver.sessionId;
|
|
116
|
+
return typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor)
|
|
117
|
+
.handle(() => __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
throw Object.assign(new Error('NoSuchElement: An element could not be located'), {
|
|
119
|
+
name: 'NoSuchElementError',
|
|
120
|
+
});
|
|
121
|
+
}), driver, 'findElement', ['xpath', '//android.widget.Button[@text="Pay"]', sessionId], pluginArgs, isHub)
|
|
122
|
+
.then(() => undefined, (err) => err);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
for (const [where, isHub] of [
|
|
126
|
+
['on a standalone server', true],
|
|
127
|
+
["on a node running its hub's session", false],
|
|
128
|
+
]) {
|
|
129
|
+
describe(where, () => {
|
|
130
|
+
it('never calls the AI provider for a session that allows tiers 1, 2 and 3', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
131
|
+
const driver = yield sessionWith({ 'xe:options': { healingTiers: [1, 2, 3] } });
|
|
132
|
+
// The case: with the dashboard off and no video, nothing registers it.
|
|
133
|
+
(0, chai_1.expect)(SessionManager_1.SESSION_MANAGER.getSession(driver.sessionId)).to.equal(undefined);
|
|
134
|
+
const thrown = yield findMissing(driver, { isHub });
|
|
135
|
+
(0, chai_1.expect)(visualFind.called, 'Visual AI sent the screenshot').to.equal(false);
|
|
136
|
+
(0, chai_1.expect)(healLocator.called, 'the LLM was sent the page source').to.equal(false);
|
|
137
|
+
(0, chai_1.expect)(dispatched).to.deep.equal([
|
|
138
|
+
'ResilioTree Provider',
|
|
139
|
+
'Fuzzy XML Provider',
|
|
140
|
+
'OCR Text Provider',
|
|
141
|
+
]);
|
|
142
|
+
// Nothing healed it, so the test sees its own failure.
|
|
143
|
+
(0, chai_1.expect)(thrown === null || thrown === void 0 ? void 0 : thrown.name).to.equal('NoSuchElementError');
|
|
144
|
+
}));
|
|
145
|
+
it('reads the xenon:options alias the same way', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
|
+
const driver = yield sessionWith({ 'xenon:options': { healingTiers: [2] } });
|
|
147
|
+
yield findMissing(driver, { isHub });
|
|
148
|
+
(0, chai_1.expect)(visualFind.called).to.equal(false);
|
|
149
|
+
(0, chai_1.expect)(healLocator.called).to.equal(false);
|
|
150
|
+
(0, chai_1.expect)(dispatched).to.deep.equal(['Fuzzy XML Provider']);
|
|
151
|
+
}));
|
|
152
|
+
it('still runs every tier for a session that asks for none in particular', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
+
const driver = yield sessionWith(null);
|
|
154
|
+
yield findMissing(driver, { isHub });
|
|
155
|
+
(0, chai_1.expect)(dispatched).to.deep.equal([
|
|
156
|
+
'ResilioTree Provider',
|
|
157
|
+
'Fuzzy XML Provider',
|
|
158
|
+
'OCR Text Provider',
|
|
159
|
+
'Visual AI Provider',
|
|
160
|
+
'LLM Reasoning Provider',
|
|
161
|
+
]);
|
|
162
|
+
(0, chai_1.expect)(visualFind.calledOnce).to.equal(true);
|
|
163
|
+
(0, chai_1.expect)(healLocator.calledOnce).to.equal(true);
|
|
164
|
+
}));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
describe('a value that is not a list of tier numbers', () => {
|
|
168
|
+
for (const [label, value] of [
|
|
169
|
+
['a string', '1,2,3'],
|
|
170
|
+
['numbers as strings', ['1', '2']],
|
|
171
|
+
['a list with a string in it', [4, 'llm']],
|
|
172
|
+
['a number that is no tier', [1, 6]],
|
|
173
|
+
['a lone number', 4],
|
|
174
|
+
['an object', { visualAi: false }],
|
|
175
|
+
]) {
|
|
176
|
+
it(`runs only the tiers that stay on the server: ${label}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
177
|
+
const driver = yield sessionWith({ 'xe:options': { healingTiers: value } });
|
|
178
|
+
yield findMissing(driver, { isHub: true });
|
|
179
|
+
(0, chai_1.expect)(visualFind.called).to.equal(false);
|
|
180
|
+
(0, chai_1.expect)(healLocator.called).to.equal(false);
|
|
181
|
+
(0, chai_1.expect)(dispatched).to.deep.equal([
|
|
182
|
+
'ResilioTree Provider',
|
|
183
|
+
'Fuzzy XML Provider',
|
|
184
|
+
'OCR Text Provider',
|
|
185
|
+
]);
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
it('runs only the tiers that stay on the server when xe:options is not an object', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
189
|
+
const driver = yield sessionWith({ 'xe:options': '{"healingTiers":[1,2,3]}' });
|
|
190
|
+
yield findMissing(driver, { isHub: true });
|
|
191
|
+
(0, chai_1.expect)(visualFind.called).to.equal(false);
|
|
192
|
+
(0, chai_1.expect)(healLocator.called).to.equal(false);
|
|
193
|
+
(0, chai_1.expect)(dispatched).to.deep.equal([
|
|
194
|
+
'ResilioTree Provider',
|
|
195
|
+
'Fuzzy XML Provider',
|
|
196
|
+
'OCR Text Provider',
|
|
197
|
+
]);
|
|
198
|
+
}));
|
|
199
|
+
it('says so in the log, once per session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
200
|
+
const warn = sinon_1.default.stub(typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).log, 'warn');
|
|
201
|
+
const driver = yield sessionWith({ 'xe:options': { healingTiers: ['1', '2'] } });
|
|
202
|
+
yield findMissing(driver, { isHub: true });
|
|
203
|
+
yield findMissing(driver, { isHub: true });
|
|
204
|
+
const said = warn.getCalls().filter((c) => /healingTiers/.test(String(c.args[0])));
|
|
205
|
+
(0, chai_1.expect)(said).to.have.length(1);
|
|
206
|
+
(0, chai_1.expect)(String(said[0].args[0])).to.contain('["1","2"]');
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
it('heals with no tier, and takes no screenshot, for a session that lists none', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
210
|
+
const driver = yield sessionWith({ 'xe:options': { healingTiers: [] } });
|
|
211
|
+
const screenshot = sinon_1.default.spy(driver, 'getScreenshot');
|
|
212
|
+
const source = sinon_1.default.spy(driver, 'getPageSource');
|
|
213
|
+
const thrown = yield findMissing(driver, { isHub: true });
|
|
214
|
+
(0, chai_1.expect)(dispatched).to.deep.equal([]);
|
|
215
|
+
(0, chai_1.expect)(screenshot.called).to.equal(false);
|
|
216
|
+
(0, chai_1.expect)(source.called).to.equal(false);
|
|
217
|
+
(0, chai_1.expect)(thrown === null || thrown === void 0 ? void 0 : thrown.name).to.equal('NoSuchElementError');
|
|
218
|
+
}));
|
|
219
|
+
});
|