@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
|
@@ -25,7 +25,9 @@ describe('POST /healing/selector/state (real queries)', function () {
|
|
|
25
25
|
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
26
26
|
yield (0, selector_health_fixture_1.seedSelectorHealth)(scratch.db);
|
|
27
27
|
// The service's transactions use the scratch database's own client.
|
|
28
|
-
typedi_1.Container.set(SelectorStateService_1.SelectorStateService, new SelectorStateService_1.SelectorStateService(scratch.db, {
|
|
28
|
+
typedi_1.Container.set(SelectorStateService_1.SelectorStateService, new SelectorStateService_1.SelectorStateService(scratch.db, {
|
|
29
|
+
emitToDashboardForSelector: () => __awaiter(this, void 0, void 0, function* () { return undefined; }),
|
|
30
|
+
}));
|
|
29
31
|
}));
|
|
30
32
|
after(() => {
|
|
31
33
|
typedi_1.Container.remove(SelectorStateService_1.SelectorStateService);
|
|
@@ -0,0 +1,179 @@
|
|
|
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 CommandInterceptor_1 = require("../../src/interceptors/CommandInterceptor");
|
|
20
|
+
const HealEtalonService_1 = require("../../src/services/healing/HealEtalonService");
|
|
21
|
+
const SelfHealingSwitch_1 = require("../../src/services/settings/SelfHealingSwitch");
|
|
22
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
23
|
+
const event_manager_1 = require("../../src/dashboard/event-manager");
|
|
24
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
25
|
+
const waitFor_1 = require("../../src/services/autowait/waitFor");
|
|
26
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
27
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
28
|
+
/**
|
|
29
|
+
* After a findElement finds its element, Xenon learns the selector's
|
|
30
|
+
* fingerprint (the element's attributes, position and path), which the
|
|
31
|
+
* Resilio and Fuzzy XML tiers heal it with later, on the server, without
|
|
32
|
+
* calling the AI provider.
|
|
33
|
+
*
|
|
34
|
+
* Through 2.14 it learnt only behind the dashboard's per-command record
|
|
35
|
+
* (`isHub && enableDashboard && SESSION_MANAGER.isValidSession`): never with
|
|
36
|
+
* `enableDashboard` off (the default), and never on a node. Those servers
|
|
37
|
+
* healed without fingerprints, so a broken selector went on to OCR, Visual AI
|
|
38
|
+
* and the LLM. Learning now follows the self-healing switch alone, on every
|
|
39
|
+
* session this server drives. The record itself is unchanged.
|
|
40
|
+
*/
|
|
41
|
+
describe('selector learning, on every session this server drives', () => {
|
|
42
|
+
(0, scratch_database_1.useScratchDatabase)();
|
|
43
|
+
let restoreContainer;
|
|
44
|
+
let etalons;
|
|
45
|
+
let learn;
|
|
46
|
+
let record;
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(HealEtalonService_1.HealEtalonService, SelfHealingSwitch_1.SelfHealingSwitch);
|
|
49
|
+
// The switch as at boot with nothing saved: it follows the options.
|
|
50
|
+
typedi_1.Container.set(SelfHealingSwitch_1.SelfHealingSwitch, new SelfHealingSwitch_1.SelfHealingSwitch());
|
|
51
|
+
etalons = {
|
|
52
|
+
getSignature: sinon_1.default.stub().resolves(null),
|
|
53
|
+
saveSignature: sinon_1.default.stub().resolves(),
|
|
54
|
+
};
|
|
55
|
+
typedi_1.Container.set(HealEtalonService_1.HealEtalonService, etalons);
|
|
56
|
+
learn = sinon_1.default.spy(CommandInterceptor_1.CommandInterceptor.prototype, 'triggerLearning');
|
|
57
|
+
record = sinon_1.default.stub(event_manager_1.DASHBORD_EVENT_MANAGER, 'afterSessionCommand').resolves();
|
|
58
|
+
});
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
sinon_1.default.restore();
|
|
61
|
+
restoreContainer();
|
|
62
|
+
});
|
|
63
|
+
const options = (over = {}) => (Object.assign(Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { enableDashboard: false }), over));
|
|
64
|
+
let sessions = 0;
|
|
65
|
+
/** A session's driver, with what learning reads from a found element. */
|
|
66
|
+
function sessionDriver(caps = {}) {
|
|
67
|
+
sessions += 1;
|
|
68
|
+
return {
|
|
69
|
+
sessionId: `learn-${sessions}-${Date.now()}`,
|
|
70
|
+
caps: Object.assign({ platformName: 'Android' }, caps),
|
|
71
|
+
// As UiAutomator2 answers: String(value), so "null" for an unset one.
|
|
72
|
+
getAttribute: (name, _id) => __awaiter(this, void 0, void 0, function* () { var _a; return String((_a = { 'resource-id': 'com.shop:id/pay', text: 'Pay now' }[name]) !== null && _a !== void 0 ? _a : null); }),
|
|
73
|
+
getElementRect: () => __awaiter(this, void 0, void 0, function* () { return ({ x: 40, y: 900, width: 300, height: 96 }); }),
|
|
74
|
+
getName: () => __awaiter(this, void 0, void 0, function* () { return 'android.widget.Button'; }),
|
|
75
|
+
getPageSource: () => __awaiter(this, void 0, void 0, function* () { return '<hierarchy><android.widget.Button text="Pay now" resource-id="com.shop:id/pay"/></hierarchy>'; }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const PAY = ['id', 'com.shop:id/pay'];
|
|
79
|
+
const FOUND = { ELEMENT: 'el-1', 'element-6066-11e4-a52e-4f735466cecf': 'el-1' };
|
|
80
|
+
/** A findElement the driver answers with its element. */
|
|
81
|
+
function find(driver_1, pluginArgs_1, _a) {
|
|
82
|
+
return __awaiter(this, arguments, void 0, function* (driver, pluginArgs, { isHub, command = 'findElement' }) {
|
|
83
|
+
const response = command === 'findElements' ? [FOUND] : FOUND;
|
|
84
|
+
return typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).handle(() => __awaiter(this, void 0, void 0, function* () { return response; }), driver, command, [...PAY, driver.sessionId], pluginArgs, isHub);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Learning runs in the background, after the find has answered. */
|
|
88
|
+
function learnt() {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
const result = yield (0, waitFor_1.waitFor)(() => __awaiter(this, void 0, void 0, function* () { return (etalons.saveSignature.called ? true : null); }), {
|
|
91
|
+
timeoutMs: 2000,
|
|
92
|
+
intervalMs: 5,
|
|
93
|
+
});
|
|
94
|
+
return 'value' in result;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
for (const [where, isHub] of [
|
|
98
|
+
['a standalone server', true],
|
|
99
|
+
["a node running its hub's session", false],
|
|
100
|
+
]) {
|
|
101
|
+
it(`learns a found selector on ${where} with enableDashboard off`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
+
const driver = sessionDriver();
|
|
103
|
+
// The case: nothing registers it, as nothing registered it before.
|
|
104
|
+
(0, chai_1.expect)(SessionManager_1.SESSION_MANAGER.isValidSession(driver.sessionId)).to.equal(false);
|
|
105
|
+
const answer = yield find(driver, options(), { isHub });
|
|
106
|
+
(0, chai_1.expect)(answer).to.deep.equal(FOUND);
|
|
107
|
+
(0, chai_1.expect)(learn.calledOnce, 'learning started').to.equal(true);
|
|
108
|
+
(0, chai_1.expect)(yield learnt(), 'a fingerprint was saved').to.equal(true);
|
|
109
|
+
(0, chai_1.expect)(etalons.saveSignature.firstCall.args.slice(0, 2)).to.deep.equal(PAY);
|
|
110
|
+
// A fingerprint that says which element it is, which Fuzzy XML can use.
|
|
111
|
+
(0, chai_1.expect)(etalons.saveSignature.firstCall.args[2].attributes).to.deep.include({
|
|
112
|
+
name: 'resource-id',
|
|
113
|
+
value: 'com.shop:id/pay',
|
|
114
|
+
});
|
|
115
|
+
// The dashboard's record is still the dashboard's: nothing was recorded.
|
|
116
|
+
(0, chai_1.expect)(record.called).to.equal(false);
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
it('learns a selector autowait found', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
120
|
+
const driver = sessionDriver();
|
|
121
|
+
const pluginArgs = options({
|
|
122
|
+
autowait: {
|
|
123
|
+
enabled: true,
|
|
124
|
+
timeoutMs: 500,
|
|
125
|
+
intervalBetweenAttemptsMs: 10,
|
|
126
|
+
excludeEnabledCheck: [],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
yield find(driver, pluginArgs, { isHub: true });
|
|
130
|
+
(0, chai_1.expect)(learn.calledOnce).to.equal(true);
|
|
131
|
+
(0, chai_1.expect)(yield learnt()).to.equal(true);
|
|
132
|
+
}));
|
|
133
|
+
it('learns once, and still records, where the dashboard records the session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
+
const driver = sessionDriver();
|
|
135
|
+
sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'isValidSession').returns(true);
|
|
136
|
+
yield find(driver, options({ enableDashboard: true }), { isHub: true });
|
|
137
|
+
(0, chai_1.expect)(record.calledOnce).to.equal(true);
|
|
138
|
+
(0, chai_1.expect)(learn.calledOnce).to.equal(true);
|
|
139
|
+
(0, chai_1.expect)(yield learnt()).to.equal(true);
|
|
140
|
+
}));
|
|
141
|
+
it('learns nothing with self-healing off', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
|
+
yield find(sessionDriver(), options({ enableSelfHealing: false }), { isHub: true });
|
|
143
|
+
(0, chai_1.expect)(learn.called).to.equal(false);
|
|
144
|
+
}));
|
|
145
|
+
it('learns nothing from a session that turned its own healing off', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
|
+
const driver = sessionDriver({ 'xe:options': { healingTiers: [] } });
|
|
147
|
+
yield find(driver, options(), { isHub: true });
|
|
148
|
+
(0, chai_1.expect)(learn.called).to.equal(false);
|
|
149
|
+
}));
|
|
150
|
+
it('still learns from a session that limits its tiers', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
+
const driver = sessionDriver({ 'xe:options': { healingTiers: [3] } });
|
|
152
|
+
yield find(driver, options(), { isHub: true });
|
|
153
|
+
(0, chai_1.expect)(learn.calledOnce).to.equal(true);
|
|
154
|
+
}));
|
|
155
|
+
it('learns nothing from findElements, as before', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
156
|
+
yield find(sessionDriver(), options(), { isHub: true, command: 'findElements' });
|
|
157
|
+
(0, chai_1.expect)(learn.called).to.equal(false);
|
|
158
|
+
}));
|
|
159
|
+
it('answers the find even when deciding whether to learn throws', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
160
|
+
sinon_1.default.stub(SelfHealingSwitch_1.SelfHealingSwitch.prototype, 'isEnabled').throws(new Error('switch unreadable'));
|
|
161
|
+
const answer = yield find(sessionDriver(), options(), { isHub: true });
|
|
162
|
+
(0, chai_1.expect)(answer).to.deep.equal(FOUND);
|
|
163
|
+
(0, chai_1.expect)(learn.called).to.equal(false);
|
|
164
|
+
}));
|
|
165
|
+
it('answers the find even when learning fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
|
+
etalons.getSignature.rejects(new Error('database is locked'));
|
|
167
|
+
const driver = sessionDriver();
|
|
168
|
+
const answer = yield find(driver, options(), { isHub: true });
|
|
169
|
+
(0, chai_1.expect)(answer).to.deep.equal(FOUND);
|
|
170
|
+
// The background pass ends, and the session may learn again.
|
|
171
|
+
const settled = yield (0, waitFor_1.waitFor)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
172
|
+
return typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).learningSessions.has(driver.sessionId)
|
|
173
|
+
? null
|
|
174
|
+
: true;
|
|
175
|
+
}), { timeoutMs: 2000, intervalMs: 5 });
|
|
176
|
+
(0, chai_1.expect)('value' in settled).to.equal(true);
|
|
177
|
+
(0, chai_1.expect)(etalons.saveSignature.called).to.equal(false);
|
|
178
|
+
}));
|
|
179
|
+
});
|
|
@@ -38,7 +38,7 @@ function buildPrismaStub() {
|
|
|
38
38
|
}
|
|
39
39
|
function buildSocketStub() {
|
|
40
40
|
return {
|
|
41
|
-
|
|
41
|
+
emitToDashboardForSelector: sinon_1.default.stub().resolves(),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
const STRATEGY = 'accessibility id';
|
|
@@ -105,8 +105,8 @@ describe('SelectorStateService', () => {
|
|
|
105
105
|
selector: SELECTOR,
|
|
106
106
|
apiKeyId: API_KEY_ID,
|
|
107
107
|
});
|
|
108
|
-
(0, chai_1.expect)(socketStub.
|
|
109
|
-
const [event, payload] = socketStub.
|
|
108
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.equal(true);
|
|
109
|
+
const [event, payload] = socketStub.emitToDashboardForSelector.firstCall.args;
|
|
110
110
|
(0, chai_1.expect)(event).to.equal(SocketEvents_1.SocketEvents.SELECTOR_FIXED);
|
|
111
111
|
(0, chai_1.expect)(payload).to.include({
|
|
112
112
|
original_strategy: STRATEGY,
|
|
@@ -133,7 +133,7 @@ describe('SelectorStateService', () => {
|
|
|
133
133
|
(0, chai_1.expect)(err).to.be.instanceOf(SelectorStateService_1.SelectorStateConflictError);
|
|
134
134
|
(0, chai_1.expect)(err.currentStatus).to.equal('muted');
|
|
135
135
|
(0, chai_1.expect)(prismaStub.selectorState.upsert.called).to.equal(false);
|
|
136
|
-
(0, chai_1.expect)(socketStub.
|
|
136
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
137
137
|
}));
|
|
138
138
|
});
|
|
139
139
|
describe('mute', () => {
|
|
@@ -170,8 +170,8 @@ describe('SelectorStateService', () => {
|
|
|
170
170
|
selector: SELECTOR,
|
|
171
171
|
apiKeyId: API_KEY_ID,
|
|
172
172
|
});
|
|
173
|
-
(0, chai_1.expect)(socketStub.
|
|
174
|
-
const [event, payload] = socketStub.
|
|
173
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.equal(true);
|
|
174
|
+
const [event, payload] = socketStub.emitToDashboardForSelector.firstCall.args;
|
|
175
175
|
(0, chai_1.expect)(event).to.equal(SocketEvents_1.SocketEvents.SELECTOR_MUTED);
|
|
176
176
|
(0, chai_1.expect)(payload.status).to.equal('muted');
|
|
177
177
|
(0, chai_1.expect)(payload.muted_at).to.equal('2026-04-25T02:00:00.000Z');
|
|
@@ -195,8 +195,8 @@ describe('SelectorStateService', () => {
|
|
|
195
195
|
});
|
|
196
196
|
(0, chai_1.expect)(prismaStub.selectorState.delete.calledOnce).to.equal(true);
|
|
197
197
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
198
|
-
(0, chai_1.expect)(socketStub.
|
|
199
|
-
(0, chai_1.expect)(socketStub.
|
|
198
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.equal(true);
|
|
199
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_UNMUTED);
|
|
200
200
|
}));
|
|
201
201
|
it('keeps the row and sets status=active when history exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
202
202
|
const muted = makeRow({
|
|
@@ -217,8 +217,8 @@ describe('SelectorStateService', () => {
|
|
|
217
217
|
const args = prismaStub.selectorState.update.firstCall.args[0];
|
|
218
218
|
(0, chai_1.expect)(args.data.status).to.equal('active');
|
|
219
219
|
(0, chai_1.expect)(args.data.muted_at).to.equal(null);
|
|
220
|
-
(0, chai_1.expect)(socketStub.
|
|
221
|
-
(0, chai_1.expect)(socketStub.
|
|
220
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.equal(true);
|
|
221
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_UNMUTED);
|
|
222
222
|
}));
|
|
223
223
|
it('is a no-op when the row exists but is not muted', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
224
224
|
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'active' }));
|
|
@@ -229,7 +229,7 @@ describe('SelectorStateService', () => {
|
|
|
229
229
|
});
|
|
230
230
|
(0, chai_1.expect)(prismaStub.selectorState.delete.called).to.equal(false);
|
|
231
231
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
232
|
-
(0, chai_1.expect)(socketStub.
|
|
232
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
233
233
|
}));
|
|
234
234
|
it('is a no-op when no row exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
235
235
|
prismaStub.selectorState.findUnique.resolves(null);
|
|
@@ -240,7 +240,7 @@ describe('SelectorStateService', () => {
|
|
|
240
240
|
});
|
|
241
241
|
(0, chai_1.expect)(prismaStub.selectorState.delete.called).to.equal(false);
|
|
242
242
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
243
|
-
(0, chai_1.expect)(socketStub.
|
|
243
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
244
244
|
}));
|
|
245
245
|
});
|
|
246
246
|
describe('cancelVerification', () => {
|
|
@@ -261,7 +261,7 @@ describe('SelectorStateService', () => {
|
|
|
261
261
|
(0, chai_1.expect)(err.currentStatus).to.equal('active');
|
|
262
262
|
(0, chai_1.expect)(prismaStub.selectorState.delete.called).to.equal(false);
|
|
263
263
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
264
|
-
(0, chai_1.expect)(socketStub.
|
|
264
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
265
265
|
}));
|
|
266
266
|
it('throws SelectorStateConflictError when no row exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
267
267
|
prismaStub.selectorState.findUnique.resolves(null);
|
|
@@ -294,8 +294,8 @@ describe('SelectorStateService', () => {
|
|
|
294
294
|
});
|
|
295
295
|
(0, chai_1.expect)(prismaStub.selectorState.delete.calledOnce).to.equal(true);
|
|
296
296
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
297
|
-
(0, chai_1.expect)(socketStub.
|
|
298
|
-
(0, chai_1.expect)(socketStub.
|
|
297
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.equal(true);
|
|
298
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_CANCELLED);
|
|
299
299
|
}));
|
|
300
300
|
it('keeps row and clears fixed state when history exists (resolved_at not null)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
301
301
|
const pending = makeRow({
|
|
@@ -318,7 +318,7 @@ describe('SelectorStateService', () => {
|
|
|
318
318
|
(0, chai_1.expect)(args.data.status).to.equal('active');
|
|
319
319
|
(0, chai_1.expect)(args.data.fixed_at).to.equal(null);
|
|
320
320
|
(0, chai_1.expect)(args.data.clean_builds_count).to.equal(0);
|
|
321
|
-
(0, chai_1.expect)(socketStub.
|
|
321
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_CANCELLED);
|
|
322
322
|
}));
|
|
323
323
|
});
|
|
324
324
|
describe('onHealRecorded', () => {
|
|
@@ -344,8 +344,8 @@ describe('SelectorStateService', () => {
|
|
|
344
344
|
(0, chai_1.expect)(args.data.resolved_at).to.equal(null);
|
|
345
345
|
(0, chai_1.expect)(args.data.regression_count).to.deep.equal({ increment: 1 });
|
|
346
346
|
(0, chai_1.expect)(args.data.clean_builds_count).to.equal(0);
|
|
347
|
-
(0, chai_1.expect)(socketStub.
|
|
348
|
-
(0, chai_1.expect)(socketStub.
|
|
347
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.equal(true);
|
|
348
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_REGRESSED);
|
|
349
349
|
}));
|
|
350
350
|
it('flips resolved → active, clears resolved_at, and increments regression_count', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
351
351
|
const resolved = makeRow({
|
|
@@ -368,7 +368,7 @@ describe('SelectorStateService', () => {
|
|
|
368
368
|
(0, chai_1.expect)(args.data.resolved_at).to.equal(null);
|
|
369
369
|
(0, chai_1.expect)(args.data.fixed_at).to.equal(null);
|
|
370
370
|
(0, chai_1.expect)(args.data.regression_count).to.deep.equal({ increment: 1 });
|
|
371
|
-
(0, chai_1.expect)(socketStub.
|
|
371
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_REGRESSED);
|
|
372
372
|
}));
|
|
373
373
|
it('is a no-op when the row is muted', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
374
374
|
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'muted' }));
|
|
@@ -378,7 +378,7 @@ describe('SelectorStateService', () => {
|
|
|
378
378
|
sessionId: SESSION_ID,
|
|
379
379
|
});
|
|
380
380
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
381
|
-
(0, chai_1.expect)(socketStub.
|
|
381
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
382
382
|
}));
|
|
383
383
|
it('is a no-op when no row exists', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
384
384
|
prismaStub.selectorState.findUnique.resolves(null);
|
|
@@ -388,7 +388,7 @@ describe('SelectorStateService', () => {
|
|
|
388
388
|
sessionId: SESSION_ID,
|
|
389
389
|
});
|
|
390
390
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
391
|
-
(0, chai_1.expect)(socketStub.
|
|
391
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
392
392
|
}));
|
|
393
393
|
it('is a no-op when row is already active', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
394
394
|
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'active' }));
|
|
@@ -398,7 +398,7 @@ describe('SelectorStateService', () => {
|
|
|
398
398
|
sessionId: SESSION_ID,
|
|
399
399
|
});
|
|
400
400
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.equal(false);
|
|
401
|
-
(0, chai_1.expect)(socketStub.
|
|
401
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
402
402
|
}));
|
|
403
403
|
});
|
|
404
404
|
describe('getState', () => {
|
|
@@ -443,7 +443,7 @@ describe('SelectorStateService', () => {
|
|
|
443
443
|
thrown = e;
|
|
444
444
|
}
|
|
445
445
|
(0, chai_1.expect)(thrown.message).to.equal('disk full');
|
|
446
|
-
(0, chai_1.expect)(socketStub.
|
|
446
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.equal(false);
|
|
447
447
|
}));
|
|
448
448
|
it('records the mute with its reason', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
449
449
|
prismaStub.selectorState.upsert.resolves(makeRow({ status: 'muted', muted_at: new Date() }));
|
|
@@ -483,4 +483,53 @@ describe('SelectorStateService', () => {
|
|
|
483
483
|
(0, chai_1.expect)(prismaStub.selectorEvent.create.called).to.equal(false);
|
|
484
484
|
}));
|
|
485
485
|
});
|
|
486
|
+
// The socket server sends a selector's event only to the dashboards whose
|
|
487
|
+
// caller may see the selector, so every event names it.
|
|
488
|
+
describe('each event names its selector, for the socket server to scope it', () => {
|
|
489
|
+
const KEY = { strategy: STRATEGY, selector: SELECTOR };
|
|
490
|
+
const ctx = { strategy: STRATEGY, selector: SELECTOR, apiKeyId: API_KEY_ID };
|
|
491
|
+
const sent = () => socketStub.emitToDashboardForSelector.getCalls().map((c) => [c.args[0], c.args[2]]);
|
|
492
|
+
it('markFixed and mute', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
493
|
+
prismaStub.selectorState.findUnique.resolves(null);
|
|
494
|
+
prismaStub.selectorState.upsert.resolves(makeRow({ status: 'pending' }));
|
|
495
|
+
yield svc.markFixed(ctx);
|
|
496
|
+
prismaStub.selectorState.upsert.resolves(makeRow({ status: 'muted' }));
|
|
497
|
+
yield svc.mute(ctx);
|
|
498
|
+
(0, chai_1.expect)(sent()).to.deep.equal([
|
|
499
|
+
[SocketEvents_1.SocketEvents.SELECTOR_FIXED, KEY],
|
|
500
|
+
[SocketEvents_1.SocketEvents.SELECTOR_MUTED, KEY],
|
|
501
|
+
]);
|
|
502
|
+
}));
|
|
503
|
+
it('unmute and cancelVerification, whether the row is kept or deleted', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
504
|
+
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'muted' }));
|
|
505
|
+
yield svc.unmute(ctx);
|
|
506
|
+
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'muted', regression_count: 1 }));
|
|
507
|
+
prismaStub.selectorState.update.resolves(makeRow({ status: 'active', regression_count: 1 }));
|
|
508
|
+
yield svc.unmute(ctx);
|
|
509
|
+
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'pending' }));
|
|
510
|
+
yield svc.cancelVerification(ctx);
|
|
511
|
+
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'pending', regression_count: 1 }));
|
|
512
|
+
yield svc.cancelVerification(ctx);
|
|
513
|
+
(0, chai_1.expect)(sent()).to.deep.equal([
|
|
514
|
+
[SocketEvents_1.SocketEvents.SELECTOR_UNMUTED, KEY],
|
|
515
|
+
[SocketEvents_1.SocketEvents.SELECTOR_UNMUTED, KEY],
|
|
516
|
+
[SocketEvents_1.SocketEvents.SELECTOR_CANCELLED, KEY],
|
|
517
|
+
[SocketEvents_1.SocketEvents.SELECTOR_CANCELLED, KEY],
|
|
518
|
+
]);
|
|
519
|
+
}));
|
|
520
|
+
it('onHealRecorded', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
521
|
+
prismaStub.selectorState.findUnique.resolves(makeRow({ status: 'pending' }));
|
|
522
|
+
prismaStub.selectorState.update.resolves(makeRow({ status: 'active', regression_count: 1 }));
|
|
523
|
+
yield svc.onHealRecorded({ strategy: STRATEGY, selector: SELECTOR, sessionId: SESSION_ID });
|
|
524
|
+
(0, chai_1.expect)(sent()).to.deep.equal([[SocketEvents_1.SocketEvents.SELECTOR_REGRESSED, KEY]]);
|
|
525
|
+
}));
|
|
526
|
+
it('a heal recorded with no strategy names strategy ""', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
527
|
+
prismaStub.selectorState.findUnique.resolves(null);
|
|
528
|
+
prismaStub.selectorState.upsert.resolves(makeRow({ status: 'muted', original_strategy: '' }));
|
|
529
|
+
yield svc.mute({ strategy: '', selector: SELECTOR, apiKeyId: API_KEY_ID });
|
|
530
|
+
(0, chai_1.expect)(sent()).to.deep.equal([
|
|
531
|
+
[SocketEvents_1.SocketEvents.SELECTOR_MUTED, { strategy: '', selector: SELECTOR }],
|
|
532
|
+
]);
|
|
533
|
+
}));
|
|
534
|
+
});
|
|
486
535
|
});
|
|
@@ -0,0 +1,130 @@
|
|
|
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 scratch_database_1 = require("../helpers/scratch-database");
|
|
19
|
+
const SelectorVerificationJob_1 = require("../../src/services/SelectorVerificationJob");
|
|
20
|
+
const HOUR = 60 * 60 * 1000;
|
|
21
|
+
const ELEMENT = '{"value":{"element-6066-11e4-a52e-4f735466cecf":"e-1"},"sessionId":"s"}';
|
|
22
|
+
const ELEMENTS = '{"value":[{"element-6066-11e4-a52e-4f735466cecf":"e-1"}],"sessionId":"s"}';
|
|
23
|
+
const EMPTY_LIST = '{"value":[],"sessionId":"s"}';
|
|
24
|
+
const NOT_FOUND = '{"value":{"error":"An element could not be located on the page"},"sessionId":"s"}';
|
|
25
|
+
/**
|
|
26
|
+
* A clean build is one where the selector was found without healing. Through
|
|
27
|
+
* 2.14 it was any build with a find of the selector that didn't heal, so a
|
|
28
|
+
* selector whose find failed outright in three builds was verified as fixed.
|
|
29
|
+
* The rows are the ones the dashboard writes (event-manager.afterSessionCommand):
|
|
30
|
+
* a failed find has `is_error` and an error body, an empty findElements has
|
|
31
|
+
* `{"value":[]}`, and the real SQL runs against a scratch database.
|
|
32
|
+
*/
|
|
33
|
+
describe('SelectorVerificationJob counts only builds where the selector was found', function () {
|
|
34
|
+
this.timeout(90000);
|
|
35
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
36
|
+
let socket;
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
const fixedAt = new Date(now - 48 * HOUR);
|
|
39
|
+
const builds = ['cb-b1', 'cb-b2', 'cb-b3'];
|
|
40
|
+
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
for (const b of builds) {
|
|
42
|
+
yield scratch.db.build.create({ data: { id: b, name: b } });
|
|
43
|
+
yield scratch.db.session.create({
|
|
44
|
+
data: {
|
|
45
|
+
id: `cb-s-${b}`,
|
|
46
|
+
build_id: b,
|
|
47
|
+
device_udid: 'cb-phone',
|
|
48
|
+
device_platform: 'android',
|
|
49
|
+
device_version: '14',
|
|
50
|
+
desired_capabilities: '{}',
|
|
51
|
+
session_capabilities: '{}',
|
|
52
|
+
node_id: 'localhost',
|
|
53
|
+
has_live_video: false,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
yield scratch.db.selectorEvent.deleteMany();
|
|
60
|
+
yield scratch.db.selectorState.deleteMany();
|
|
61
|
+
yield scratch.db.sessionLog.deleteMany();
|
|
62
|
+
socket = { emitToDashboardForSelector: sinon_1.default.stub().resolves() };
|
|
63
|
+
yield scratch.db.selectorState.create({
|
|
64
|
+
data: {
|
|
65
|
+
original_strategy: 'xpath',
|
|
66
|
+
original_selector: '//login',
|
|
67
|
+
status: 'pending',
|
|
68
|
+
fixed_at: fixedAt,
|
|
69
|
+
clean_builds_count: 0,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}));
|
|
73
|
+
const find = (build, command, response, isError = false) => scratch.db.sessionLog.create({
|
|
74
|
+
data: {
|
|
75
|
+
session_id: `cb-s-${build}`,
|
|
76
|
+
command_name: command,
|
|
77
|
+
url: command === 'findElement' ? '/findElement' : '/findElements',
|
|
78
|
+
method: 'POST',
|
|
79
|
+
title: 'Find element',
|
|
80
|
+
response,
|
|
81
|
+
is_success: !isError,
|
|
82
|
+
is_error: isError,
|
|
83
|
+
is_healed: false,
|
|
84
|
+
original_strategy: 'xpath',
|
|
85
|
+
original_selector: '//login',
|
|
86
|
+
createdAt: new Date(now - HOUR),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const run = () => new SelectorVerificationJob_1.SelectorVerificationJob(scratch.db, socket).run();
|
|
90
|
+
const row = () => scratch.db.selectorState.findUniqueOrThrow({
|
|
91
|
+
where: {
|
|
92
|
+
original_strategy_original_selector: {
|
|
93
|
+
original_strategy: 'xpath',
|
|
94
|
+
original_selector: '//login',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
it('does not count builds where the find failed outright', () => __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
for (const b of builds)
|
|
100
|
+
yield find(b, 'findElement', NOT_FOUND, true);
|
|
101
|
+
yield run();
|
|
102
|
+
const state = yield row();
|
|
103
|
+
(0, chai_1.expect)(state.status).to.equal('pending');
|
|
104
|
+
(0, chai_1.expect)(state.clean_builds_count).to.equal(0);
|
|
105
|
+
}));
|
|
106
|
+
it('does not count builds where findElements found nothing', () => __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
for (const b of builds)
|
|
108
|
+
yield find(b, 'findElements', EMPTY_LIST);
|
|
109
|
+
yield run();
|
|
110
|
+
const state = yield row();
|
|
111
|
+
(0, chai_1.expect)(state.status).to.equal('pending');
|
|
112
|
+
(0, chai_1.expect)(state.clean_builds_count).to.equal(0);
|
|
113
|
+
}));
|
|
114
|
+
it('verifies the selector after three builds that found it', () => __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
yield find('cb-b1', 'findElement', ELEMENT);
|
|
116
|
+
yield find('cb-b2', 'findElements', ELEMENTS);
|
|
117
|
+
yield find('cb-b3', 'findElement', ELEMENT);
|
|
118
|
+
yield run();
|
|
119
|
+
(0, chai_1.expect)((yield row()).status).to.equal('resolved');
|
|
120
|
+
}));
|
|
121
|
+
it('counts a build that found it once, even if another find of it failed', () => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
+
yield find('cb-b1', 'findElement', NOT_FOUND, true);
|
|
123
|
+
yield find('cb-b1', 'findElement', ELEMENT);
|
|
124
|
+
yield find('cb-b2', 'findElement', NOT_FOUND, true);
|
|
125
|
+
yield run();
|
|
126
|
+
const state = yield row();
|
|
127
|
+
(0, chai_1.expect)(state.status).to.equal('pending');
|
|
128
|
+
(0, chai_1.expect)(state.clean_builds_count).to.equal(1);
|
|
129
|
+
}));
|
|
130
|
+
});
|
|
@@ -49,7 +49,7 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
49
49
|
prismaStub.$transaction = sinon_1.default
|
|
50
50
|
.stub()
|
|
51
51
|
.callsFake((fn) => fn(prismaStub));
|
|
52
|
-
socketStub = {
|
|
52
|
+
socketStub = { emitToDashboardForSelector: sinon_1.default.stub().resolves() };
|
|
53
53
|
job = new SelectorVerificationJob_1.SelectorVerificationJob(prismaStub, socketStub);
|
|
54
54
|
});
|
|
55
55
|
afterEach(() => sinon_1.default.restore());
|
|
@@ -65,9 +65,9 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
65
65
|
},
|
|
66
66
|
]);
|
|
67
67
|
prismaStub.$queryRaw.resolves([
|
|
68
|
-
{ build_id: 'b-1', healed: 0 },
|
|
69
|
-
{ build_id: 'b-2', healed: 0 },
|
|
70
|
-
{ build_id: 'b-3', healed: 0 },
|
|
68
|
+
{ build_id: 'b-1', healed: 0, found: 1 },
|
|
69
|
+
{ build_id: 'b-2', healed: 0, found: 1 },
|
|
70
|
+
{ build_id: 'b-3', healed: 0, found: 1 },
|
|
71
71
|
]);
|
|
72
72
|
prismaStub.selectorState.update.resolves({
|
|
73
73
|
id: 'r-1',
|
|
@@ -84,8 +84,13 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
84
84
|
(0, chai_1.expect)(updateArg.data.status).to.equal('resolved');
|
|
85
85
|
(0, chai_1.expect)(updateArg.data.resolved_at).to.be.instanceOf(Date);
|
|
86
86
|
(0, chai_1.expect)(updateArg.data.clean_builds_count).to.equal(3);
|
|
87
|
-
(0, chai_1.expect)(socketStub.
|
|
88
|
-
(0, chai_1.expect)(socketStub.
|
|
87
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.calledOnce).to.be.true;
|
|
88
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal('selector_resolved');
|
|
89
|
+
// Named, so only the dashboards that may see the selector get it.
|
|
90
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[2]).to.deep.equal({
|
|
91
|
+
strategy: 'xpath',
|
|
92
|
+
selector: '//x',
|
|
93
|
+
});
|
|
89
94
|
}));
|
|
90
95
|
it('updates clean_builds_count and emits PROGRESS when below 3 and value changed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
96
|
prismaStub.selectorState.findMany.resolves([
|
|
@@ -99,8 +104,8 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
99
104
|
},
|
|
100
105
|
]);
|
|
101
106
|
prismaStub.$queryRaw.resolves([
|
|
102
|
-
{ build_id: 'b-1', healed: 0 },
|
|
103
|
-
{ build_id: 'b-2', healed: 1 }, // not clean
|
|
107
|
+
{ build_id: 'b-1', healed: 0, found: 1 },
|
|
108
|
+
{ build_id: 'b-2', healed: 1, found: 1 }, // not clean
|
|
104
109
|
]);
|
|
105
110
|
prismaStub.selectorState.update.resolves({
|
|
106
111
|
id: 'r-1',
|
|
@@ -111,7 +116,11 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
111
116
|
const updateArg = prismaStub.selectorState.update.firstCall.args[0];
|
|
112
117
|
(0, chai_1.expect)(updateArg.data.status).to.be.undefined;
|
|
113
118
|
(0, chai_1.expect)(updateArg.data.clean_builds_count).to.equal(1);
|
|
114
|
-
(0, chai_1.expect)(socketStub.
|
|
119
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[0]).to.equal('selector_progress');
|
|
120
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.firstCall.args[2]).to.deep.equal({
|
|
121
|
+
strategy: 'xpath',
|
|
122
|
+
selector: '//x',
|
|
123
|
+
});
|
|
115
124
|
}));
|
|
116
125
|
it('does not write or emit when clean_builds_count is unchanged and not promoting', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
126
|
prismaStub.selectorState.findMany.resolves([
|
|
@@ -125,12 +134,12 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
125
134
|
},
|
|
126
135
|
]);
|
|
127
136
|
prismaStub.$queryRaw.resolves([
|
|
128
|
-
{ build_id: 'b-1', healed: 0 },
|
|
129
|
-
{ build_id: 'b-2', healed: 1 },
|
|
137
|
+
{ build_id: 'b-1', healed: 0, found: 1 },
|
|
138
|
+
{ build_id: 'b-2', healed: 1, found: 1 },
|
|
130
139
|
]);
|
|
131
140
|
yield job.run();
|
|
132
141
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.be.false;
|
|
133
|
-
(0, chai_1.expect)(socketStub.
|
|
142
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.be.false;
|
|
134
143
|
}));
|
|
135
144
|
it('SQL filters out builds with NULL build_id and only counts findElement(s) commands', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
136
145
|
prismaStub.selectorState.findMany.resolves([
|
|
@@ -160,7 +169,7 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
160
169
|
yield job.run();
|
|
161
170
|
(0, chai_1.expect)(prismaStub.$queryRaw.called).to.be.false;
|
|
162
171
|
(0, chai_1.expect)(prismaStub.selectorState.update.called).to.be.false;
|
|
163
|
-
(0, chai_1.expect)(socketStub.
|
|
172
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.called).to.be.false;
|
|
164
173
|
}));
|
|
165
174
|
it('continues processing remaining rows if one fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
175
|
prismaStub.selectorState.findMany.resolves([
|
|
@@ -183,9 +192,9 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
183
192
|
]);
|
|
184
193
|
prismaStub.$queryRaw.onFirstCall().rejects(new Error('boom'));
|
|
185
194
|
prismaStub.$queryRaw.onSecondCall().resolves([
|
|
186
|
-
{ build_id: 'b-1', healed: 0 },
|
|
187
|
-
{ build_id: 'b-2', healed: 0 },
|
|
188
|
-
{ build_id: 'b-3', healed: 0 },
|
|
195
|
+
{ build_id: 'b-1', healed: 0, found: 1 },
|
|
196
|
+
{ build_id: 'b-2', healed: 0, found: 1 },
|
|
197
|
+
{ build_id: 'b-3', healed: 0, found: 1 },
|
|
189
198
|
]);
|
|
190
199
|
prismaStub.selectorState.update.resolves({
|
|
191
200
|
id: 'r-2',
|
|
@@ -222,9 +231,9 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
222
231
|
},
|
|
223
232
|
]);
|
|
224
233
|
prismaStub.$queryRaw.resolves([
|
|
225
|
-
{ build_id: 'b-1', healed: 0 },
|
|
226
|
-
{ build_id: 'b-2', healed: 0 },
|
|
227
|
-
{ build_id: 'b-3', healed: 0 },
|
|
234
|
+
{ build_id: 'b-1', healed: 0, found: 1 },
|
|
235
|
+
{ build_id: 'b-2', healed: 0, found: 1 },
|
|
236
|
+
{ build_id: 'b-3', healed: 0, found: 1 },
|
|
228
237
|
]);
|
|
229
238
|
prismaStub.selectorState.update.resolves({
|
|
230
239
|
id: 'r-1',
|