@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
|
@@ -82,7 +82,10 @@ describe('password-reset links', () => {
|
|
|
82
82
|
(0, chai_1.expect)(logged.join('\n')).to.not.include(RAW);
|
|
83
83
|
}));
|
|
84
84
|
it('with SMTP, emails the user and returns no link', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
-
restore = withConfig({
|
|
85
|
+
restore = withConfig({
|
|
86
|
+
smtpUrl: 'smtp://mail.example.com:587',
|
|
87
|
+
publicUrl: 'https://xenon.example.com',
|
|
88
|
+
});
|
|
86
89
|
sinon_1.default.stub(prisma_1.prisma.user, 'findUnique').resolves(member);
|
|
87
90
|
sinon_1.default
|
|
88
91
|
.stub(typedi_1.Container.get(PasswordResetService_1.PasswordResetService), 'createToken')
|
|
@@ -164,8 +167,11 @@ describe('password-reset links', () => {
|
|
|
164
167
|
(0, chai_1.expect)(r.status).to.equal(204);
|
|
165
168
|
(0, chai_1.expect)(create.called).to.equal(false);
|
|
166
169
|
}));
|
|
167
|
-
it('still mints and sends when SMTP
|
|
168
|
-
restore = withConfig({
|
|
170
|
+
it('still mints and sends when SMTP and XENON_PUBLIC_URL are configured', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
|
+
restore = withConfig({
|
|
172
|
+
smtpUrl: 'smtp://mail.example.com:587',
|
|
173
|
+
publicUrl: 'https://xenon.example.com',
|
|
174
|
+
});
|
|
169
175
|
sinon_1.default
|
|
170
176
|
.stub(typedi_1.Container.get(UserService_1.UserService), 'findByEmail')
|
|
171
177
|
.resolves({ id: 'u1', email: 'u@x.local', name: 'U', status: 'ACTIVE' });
|
|
@@ -181,7 +187,7 @@ describe('password-reset links', () => {
|
|
|
181
187
|
});
|
|
182
188
|
describe('token placement (never in a URL the server sees)', () => {
|
|
183
189
|
it('puts the token in the fragment, which browsers never send', () => {
|
|
184
|
-
const link = (0, passwordResetLink_1.buildResetLink)(
|
|
190
|
+
const link = (0, passwordResetLink_1.buildResetLink)('http://lab:4723', RAW);
|
|
185
191
|
(0, chai_1.expect)(link).to.equal(`http://lab:4723/xenon/reset-password#${RAW}`);
|
|
186
192
|
(0, chai_1.expect)(new URL(link).pathname).to.not.include(RAW);
|
|
187
193
|
});
|
|
@@ -0,0 +1,270 @@
|
|
|
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 scratch_database_1 = require("../helpers/scratch-database");
|
|
20
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
21
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
22
|
+
const page_sources_1 = require("../helpers/page-sources");
|
|
23
|
+
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
24
|
+
const HealEtalonService_1 = require("../../src/services/healing/HealEtalonService");
|
|
25
|
+
const ResilioTreeHealingProvider_1 = require("../../src/services/healing/ResilioTreeHealingProvider");
|
|
26
|
+
const HealingOrchestrator_1 = require("../../src/services/healing/HealingOrchestrator");
|
|
27
|
+
const resilioPath_1 = require("../../src/services/healing/resilioPath");
|
|
28
|
+
const CommandInterceptor_1 = require("../../src/interceptors/CommandInterceptor");
|
|
29
|
+
/**
|
|
30
|
+
* Self-healing's first tier, Resilio, finds an element by the path it had
|
|
31
|
+
* through the screen's element tree when its selector last worked. Through
|
|
32
|
+
* 2.14 it never found anything: the database dropped the path when it saved
|
|
33
|
+
* the selector's fingerprint, and the provider gives up without one. Its
|
|
34
|
+
* locators were also lowercase (an HTML parse of the page source), which the
|
|
35
|
+
* drivers' XPath doesn't match, and it answered with the nearest element
|
|
36
|
+
* however far it was, so with a path it would have healed to the wrong
|
|
37
|
+
* button whenever the right one was gone.
|
|
38
|
+
*
|
|
39
|
+
* Here the path is learnt from a found element, stored in a scratch
|
|
40
|
+
* database, and used on a changed screen. resiliotree runs for real; the
|
|
41
|
+
* driver is a fake that runs each XPath on the page source.
|
|
42
|
+
*/
|
|
43
|
+
const FORM_A = page_sources_1.android.page(page_sources_1.android.layout(0, 'login_form', '[0,200][1080,1400]', page_sources_1.android.field(0, 'username', 'Username', '[40,240][1040,360]') +
|
|
44
|
+
page_sources_1.android.field(1, 'password', 'Password', '[40,400][1040,520]') +
|
|
45
|
+
page_sources_1.android.button(2, 'forgot', 'Forgot password?', '[40,560][1040,680]') +
|
|
46
|
+
page_sources_1.android.button(3, 'login', 'Log in', '[40,720][1040,840]')));
|
|
47
|
+
// The buttons moved into a new row, after a new "Sign up" button.
|
|
48
|
+
const FORM_MOVED = page_sources_1.android.page(page_sources_1.android.layout(0, 'login_form', '[0,200][1080,1400]', page_sources_1.android.field(0, 'username', 'Username', '[40,240][1040,360]') +
|
|
49
|
+
page_sources_1.android.field(1, 'password', 'Password', '[40,400][1040,520]') +
|
|
50
|
+
page_sources_1.android.layout(2, 'actions', '[0,560][1080,1100]', page_sources_1.android.button(0, 'signup', 'Sign up', '[40,560][1040,680]') +
|
|
51
|
+
page_sources_1.android.button(1, 'forgot', 'Forgot password?', '[40,720][1040,840]') +
|
|
52
|
+
page_sources_1.android.button(2, 'login', 'Log in', '[40,880][1040,1000]'))));
|
|
53
|
+
// The button is another one now: new id, new text.
|
|
54
|
+
const FORM_RENAMED = page_sources_1.android.page(page_sources_1.android.layout(0, 'login_form', '[0,200][1080,1400]', page_sources_1.android.field(0, 'username', 'Username', '[40,240][1040,360]') +
|
|
55
|
+
page_sources_1.android.field(1, 'password', 'Password', '[40,400][1040,520]') +
|
|
56
|
+
page_sources_1.android.button(2, 'forgot', 'Forgot password?', '[40,560][1040,680]') +
|
|
57
|
+
page_sources_1.android.button(3, 'sign_in', 'Sign in', '[40,720][1040,840]')));
|
|
58
|
+
// The button is gone.
|
|
59
|
+
const FORM_GONE = page_sources_1.android.page(page_sources_1.android.layout(0, 'login_form', '[0,200][1080,1400]', page_sources_1.android.field(0, 'username', 'Username', '[40,240][1040,360]') +
|
|
60
|
+
page_sources_1.android.field(1, 'password', 'Password', '[40,400][1040,520]') +
|
|
61
|
+
page_sources_1.android.button(2, 'forgot', 'Forgot password?', '[40,560][1040,680]')));
|
|
62
|
+
// Two buttons the path can't tell apart.
|
|
63
|
+
const FORM_TWICE = page_sources_1.android.page(page_sources_1.android.layout(0, 'login_form', '[0,200][1080,1400]', page_sources_1.android.layout(0, '', '[0,200][1080,700]', page_sources_1.android.button(0, 'login', 'Log in', '[40,720][1040,840]')) +
|
|
64
|
+
page_sources_1.android.layout(1, '', '[0,700][1080,1400]', page_sources_1.android.button(0, 'login', 'Log in', '[40,720][1040,840]'))));
|
|
65
|
+
// A positional XPath, as an inspector writes one: the second button of the form.
|
|
66
|
+
const POSITIONAL = "//*[@resource-id='com.example.shop:id/login_form']/android.widget.Button[2]";
|
|
67
|
+
describe('Resilio healing, from the path a selector had when it worked', function () {
|
|
68
|
+
this.timeout(90000);
|
|
69
|
+
(0, scratch_database_1.useScratchDatabase)();
|
|
70
|
+
let etalons;
|
|
71
|
+
let restoreStore;
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
// The etalon service on the Prisma store (the scratch database), whatever
|
|
74
|
+
// store this process picked.
|
|
75
|
+
etalons = typedi_1.Container.get(HealEtalonService_1.HealEtalonService);
|
|
76
|
+
const service = etalons;
|
|
77
|
+
const before = service.store;
|
|
78
|
+
service.store = new prisma_store_1.PrismaHealEtalonStore();
|
|
79
|
+
restoreStore = () => {
|
|
80
|
+
service.store = before;
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
afterEach(() => {
|
|
84
|
+
restoreStore();
|
|
85
|
+
sinon_1.default.restore();
|
|
86
|
+
});
|
|
87
|
+
describe('the fingerprint store', () => {
|
|
88
|
+
const store = () => new prisma_store_1.PrismaHealEtalonStore();
|
|
89
|
+
const fingerprint = (selector, path) => ({
|
|
90
|
+
selector,
|
|
91
|
+
strategy: 'xpath',
|
|
92
|
+
attributes: { 'resource-id': 'com.example.shop:id/login' },
|
|
93
|
+
nodeName: 'android.widget.Button',
|
|
94
|
+
path,
|
|
95
|
+
lastSeen: Date.now(),
|
|
96
|
+
});
|
|
97
|
+
const PATH = { format: 'page-source-1', nodes: [{ tag: 'hierarchy', id: '', index: 0 }] };
|
|
98
|
+
it("keeps a selector's path", () => __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
var _a;
|
|
100
|
+
yield store().saveSignature(fingerprint('//keep-path', PATH));
|
|
101
|
+
(0, chai_1.expect)((_a = (yield store().getSignature('//keep-path'))) === null || _a === void 0 ? void 0 : _a.path).to.deep.equal(PATH);
|
|
102
|
+
}));
|
|
103
|
+
it('keeps the path when a later fingerprint comes without one', () => __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
var _a;
|
|
105
|
+
yield store().saveSignature(fingerprint('//keep-old-path', PATH));
|
|
106
|
+
yield store().saveSignature(fingerprint('//keep-old-path'));
|
|
107
|
+
(0, chai_1.expect)((_a = (yield store().getSignature('//keep-old-path'))) === null || _a === void 0 ? void 0 : _a.path).to.deep.equal(PATH);
|
|
108
|
+
}));
|
|
109
|
+
});
|
|
110
|
+
/** Learn `selector` as the interceptor does after a find, and wait for it. */
|
|
111
|
+
function learn(driver, selector) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
var _a;
|
|
114
|
+
const found = yield driver.findElement('xpath', selector);
|
|
115
|
+
typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).triggerLearning(driver, ['xpath', selector], found, `learn-${selector}`);
|
|
116
|
+
for (let i = 0; i < 100; i++) {
|
|
117
|
+
const signature = yield etalons.getSignature(selector);
|
|
118
|
+
if (((_a = signature === null || signature === void 0 ? void 0 : signature.path) === null || _a === void 0 ? void 0 : _a.format) === 'page-source-1')
|
|
119
|
+
return signature;
|
|
120
|
+
yield new Promise((r) => setTimeout(r, 20));
|
|
121
|
+
}
|
|
122
|
+
throw new Error(`no path was learnt for ${selector}`);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const heal = (driver, selector, pageSource) => new ResilioTreeHealingProvider_1.ResilioTreeHealingProvider(etalons).heal({
|
|
126
|
+
sessionId: 'resilio-session',
|
|
127
|
+
driver,
|
|
128
|
+
strategy: 'xpath',
|
|
129
|
+
selector,
|
|
130
|
+
pageSource,
|
|
131
|
+
});
|
|
132
|
+
it('finds an Android element that moved in the tree', () => __awaiter(this, void 0, void 0, function* () {
|
|
133
|
+
const driver = (0, page_sources_1.pageDriver)(FORM_A);
|
|
134
|
+
yield learn(driver, POSITIONAL);
|
|
135
|
+
driver.show(FORM_MOVED);
|
|
136
|
+
const healed = yield heal(driver, POSITIONAL, FORM_MOVED);
|
|
137
|
+
(0, chai_1.expect)(healed, 'healed').to.not.equal(null);
|
|
138
|
+
(0, chai_1.expect)(healed.id).to.equal('el:com.example.shop:id/login');
|
|
139
|
+
(0, chai_1.expect)(healed.confidence).to.be.greaterThan(0.9);
|
|
140
|
+
// The locator it suggests finds that element on the new screen.
|
|
141
|
+
const again = yield driver.findElement('xpath', healed.recommendedSelector);
|
|
142
|
+
(0, chai_1.expect)(again.ELEMENT).to.equal('el:com.example.shop:id/login');
|
|
143
|
+
}));
|
|
144
|
+
it('finds an iPhone element that moved in the tree', () => __awaiter(this, void 0, void 0, function* () {
|
|
145
|
+
const before = page_sources_1.ios.page(page_sources_1.ios.group(page_sources_1.ios.field('username', 'Username', 120) +
|
|
146
|
+
page_sources_1.ios.button('forgot', 'Forgot password?', 200) +
|
|
147
|
+
page_sources_1.ios.button('login', 'Log in', 260)));
|
|
148
|
+
const after = page_sources_1.ios.page(page_sources_1.ios.group(page_sources_1.ios.field('username', 'Username', 120) +
|
|
149
|
+
page_sources_1.ios.group(page_sources_1.ios.button('signup', 'Sign up', 200) +
|
|
150
|
+
page_sources_1.ios.button('forgot', 'Forgot password?', 260) +
|
|
151
|
+
page_sources_1.ios.button('login', 'Log in', 320))));
|
|
152
|
+
const selector = '//XCUIElementTypeOther/XCUIElementTypeButton[2]';
|
|
153
|
+
const driver = (0, page_sources_1.pageDriver)(before);
|
|
154
|
+
yield learn(driver, selector);
|
|
155
|
+
driver.show(after);
|
|
156
|
+
const healed = yield heal(driver, selector, after);
|
|
157
|
+
(0, chai_1.expect)(healed === null || healed === void 0 ? void 0 : healed.id).to.equal('el:login');
|
|
158
|
+
const again = yield driver.findElement('xpath', healed.recommendedSelector);
|
|
159
|
+
(0, chai_1.expect)(again.ELEMENT).to.equal('el:login');
|
|
160
|
+
}));
|
|
161
|
+
it('leaves a renamed element to the next tier rather than guess', () => __awaiter(this, void 0, void 0, function* () {
|
|
162
|
+
const driver = (0, page_sources_1.pageDriver)(FORM_A);
|
|
163
|
+
yield learn(driver, POSITIONAL);
|
|
164
|
+
driver.show(FORM_RENAMED);
|
|
165
|
+
(0, chai_1.expect)(yield heal(driver, POSITIONAL, FORM_RENAMED)).to.equal(null);
|
|
166
|
+
}));
|
|
167
|
+
it('does not take a neighbour when the element is gone', () => __awaiter(this, void 0, void 0, function* () {
|
|
168
|
+
const driver = (0, page_sources_1.pageDriver)(FORM_A);
|
|
169
|
+
yield learn(driver, POSITIONAL);
|
|
170
|
+
driver.show(FORM_GONE);
|
|
171
|
+
(0, chai_1.expect)(yield heal(driver, POSITIONAL, FORM_GONE)).to.equal(null);
|
|
172
|
+
}));
|
|
173
|
+
it('does not pick one of two elements it cannot tell apart', () => __awaiter(this, void 0, void 0, function* () {
|
|
174
|
+
const driver = (0, page_sources_1.pageDriver)(FORM_A);
|
|
175
|
+
yield learn(driver, POSITIONAL);
|
|
176
|
+
driver.show(FORM_TWICE);
|
|
177
|
+
(0, chai_1.expect)(yield heal(driver, POSITIONAL, FORM_TWICE)).to.equal(null);
|
|
178
|
+
}));
|
|
179
|
+
it('finds an element that holds others, such as a list row, after it moved', () => __awaiter(this, void 0, void 0, function* () {
|
|
180
|
+
const row = (index) => page_sources_1.android.row(index, 'logout_row', '[0,900][1080,1040]', page_sources_1.android.image(0, 'logout_icon', 'Log out icon', '[40,920][140,1020]') +
|
|
181
|
+
page_sources_1.android.text(1, 'logout_label', 'Log out', '[160,940][600,1000]'));
|
|
182
|
+
const before = page_sources_1.android.page(page_sources_1.android.layout(0, 'settings', '[0,200][1080,2000]', page_sources_1.android.text(0, 'account', 'Account', '[40,220][600,300]') + row(1)));
|
|
183
|
+
const after = page_sources_1.android.page(page_sources_1.android.layout(0, 'settings', '[0,200][1080,2000]', page_sources_1.android.text(0, 'account', 'Account', '[40,220][600,300]') +
|
|
184
|
+
page_sources_1.android.layout(1, 'danger_zone', '[0,800][1080,1200]', row(0))));
|
|
185
|
+
const selector = "//*[@resource-id='com.example.shop:id/settings']/android.widget.LinearLayout[1]";
|
|
186
|
+
const driver = (0, page_sources_1.pageDriver)(before);
|
|
187
|
+
yield learn(driver, selector);
|
|
188
|
+
driver.show(after);
|
|
189
|
+
const healed = yield heal(driver, selector, after);
|
|
190
|
+
(0, chai_1.expect)(healed === null || healed === void 0 ? void 0 : healed.id).to.equal('el:com.example.shop:id/logout_row');
|
|
191
|
+
}));
|
|
192
|
+
it('answers with the element it matched, not another one its locator also finds', () => __awaiter(this, void 0, void 0, function* () {
|
|
193
|
+
const cart = page_sources_1.android.button(1, 'cart', 'Cart', '[800,40][1040,140]');
|
|
194
|
+
const before = page_sources_1.android.page(page_sources_1.android.layout(0, 'toolbar', '[0,0][1080,200]', page_sources_1.android.text(0, 'title', 'Shop', '[40,40][600,140]') + cart));
|
|
195
|
+
// A badge whose description contains "Cart" now comes before the button.
|
|
196
|
+
const after = page_sources_1.android.page(page_sources_1.android.layout(0, 'toolbar', '[0,0][1080,200]', page_sources_1.android.text(0, 'title', 'Shop', '[40,40][600,140]') +
|
|
197
|
+
page_sources_1.android.image(1, 'badge', 'Cart badge, 3 items', '[700,40][780,140]') +
|
|
198
|
+
page_sources_1.android.layout(2, 'actions', '[800,0][1080,200]', page_sources_1.android.button(0, 'cart', 'Cart', '[800,40][1040,140]'))));
|
|
199
|
+
const selector = "//*[@resource-id='com.example.shop:id/toolbar']/android.widget.Button[1]";
|
|
200
|
+
const driver = (0, page_sources_1.pageDriver)(before);
|
|
201
|
+
yield learn(driver, selector);
|
|
202
|
+
driver.show(after);
|
|
203
|
+
const healed = yield heal(driver, selector, after);
|
|
204
|
+
(0, chai_1.expect)(healed === null || healed === void 0 ? void 0 : healed.id).to.equal('el:com.example.shop:id/cart');
|
|
205
|
+
const matches = yield driver.findElements('xpath', healed.recommendedSelector);
|
|
206
|
+
(0, chai_1.expect)(matches.map((m) => m.ELEMENT)).to.deep.equal(['el:com.example.shop:id/cart']);
|
|
207
|
+
}));
|
|
208
|
+
it('does not heal to an element whose text contradicts the selector', () => __awaiter(this, void 0, void 0, function* () {
|
|
209
|
+
const dialog = (label) => page_sources_1.android.page(page_sources_1.android.layout(0, 'dialog', '[0,600][1080,1400]', page_sources_1.android.text(0, 'message', 'Discard this draft?', '[40,640][1040,800]') +
|
|
210
|
+
page_sources_1.android.button(1, 'button1', label, '[600,1200][1040,1340]')));
|
|
211
|
+
const selector = "//android.widget.Button[@text='OK']";
|
|
212
|
+
const driver = (0, page_sources_1.pageDriver)(dialog('OK'));
|
|
213
|
+
yield learn(driver, selector);
|
|
214
|
+
driver.show(dialog('Delete'));
|
|
215
|
+
(0, chai_1.expect)(yield heal(driver, selector, dialog('Delete'))).to.equal(null);
|
|
216
|
+
}));
|
|
217
|
+
it('learns no path from an element that shares only its size with the one found', () => {
|
|
218
|
+
// The screen changed between the find and the learning: the button found
|
|
219
|
+
// (name "gone") isn't on it, and another button only has its height.
|
|
220
|
+
const page = page_sources_1.ios.page(page_sources_1.ios.group(page_sources_1.ios.button('next', 'Next', 300)));
|
|
221
|
+
const learnt = (0, resilioPath_1.findLearntElement)(page, 'XCUIElementTypeButton', [
|
|
222
|
+
{ name: 'name', value: 'gone' },
|
|
223
|
+
{ name: 'label', value: 'Gone' },
|
|
224
|
+
{ name: 'height', value: '44' },
|
|
225
|
+
]);
|
|
226
|
+
(0, chai_1.expect)(learnt).to.equal(null);
|
|
227
|
+
});
|
|
228
|
+
it('learns the element that has the identity the driver read', () => {
|
|
229
|
+
const page = page_sources_1.ios.page(page_sources_1.ios.group(page_sources_1.ios.button('next', 'Next', 300)));
|
|
230
|
+
const learnt = (0, resilioPath_1.findLearntElement)(page, 'XCUIElementTypeButton', [
|
|
231
|
+
{ name: 'name', value: 'next' },
|
|
232
|
+
{ name: 'height', value: '44' },
|
|
233
|
+
]);
|
|
234
|
+
(0, chai_1.expect)(learnt === null || learnt === void 0 ? void 0 : learnt.getAttribute('name')).to.equal('next');
|
|
235
|
+
});
|
|
236
|
+
it('ignores a path an older Xenon stored, and learns it again', () => __awaiter(this, void 0, void 0, function* () {
|
|
237
|
+
const selector = "//*[@resource-id='com.example.shop:id/login']";
|
|
238
|
+
yield etalons.saveSignature('xpath', selector, { nodeName: 'android.widget.Button' }, {
|
|
239
|
+
nodes: [{ tag: 'html', id: '', index: 0 }],
|
|
240
|
+
});
|
|
241
|
+
const driver = (0, page_sources_1.pageDriver)(FORM_A);
|
|
242
|
+
(0, chai_1.expect)(yield heal(driver, selector, FORM_MOVED)).to.equal(null);
|
|
243
|
+
const learnt = yield learn(driver, selector);
|
|
244
|
+
(0, chai_1.expect)(learnt.path.format).to.equal('page-source-1');
|
|
245
|
+
}));
|
|
246
|
+
it('learns the healed element’s path after a Fuzzy XML heal', () => __awaiter(this, void 0, void 0, function* () {
|
|
247
|
+
const driver = (0, page_sources_1.pageDriver)(FORM_MOVED);
|
|
248
|
+
const save = sinon_1.default.spy(etalons, 'saveSignature');
|
|
249
|
+
const orchestrator = new HealingOrchestrator_1.HealingOrchestrator(etalons);
|
|
250
|
+
// Only Fuzzy XML (tier 2), which finds the button by the words in the selector.
|
|
251
|
+
const healed = yield orchestrator.attemptHealing('fuzzy-session', Object.assign(driver, { getScreenshot: () => __awaiter(this, void 0, void 0, function* () { return 'aGVsbG8='; }) }), 'xpath', "//android.widget.Button[@text='Log in' and @resource-id='com.example.shop:id/log_in']", [2]);
|
|
252
|
+
(0, chai_1.expect)(healed === null || healed === void 0 ? void 0 : healed.id).to.equal('el:com.example.shop:id/login');
|
|
253
|
+
(0, chai_1.expect)(save.called, 'fingerprint saved').to.equal(true);
|
|
254
|
+
const path = save.lastCall.args[3];
|
|
255
|
+
(0, chai_1.expect)(path === null || path === void 0 ? void 0 : path.format).to.equal('page-source-1');
|
|
256
|
+
(0, chai_1.expect)(path.nodes[path.nodes.length - 1].id).to.equal('com.example.shop:id/login');
|
|
257
|
+
}));
|
|
258
|
+
});
|
|
259
|
+
describe('The in-memory fingerprint store', () => {
|
|
260
|
+
(0, loki_stores_1.useLokiStores)();
|
|
261
|
+
it('keeps the path when a later fingerprint comes without one', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
262
|
+
var _a;
|
|
263
|
+
const store = device_store_1.DeviceStoreFactory.getHealEtalonStore();
|
|
264
|
+
const PATH = { format: 'page-source-1', nodes: [{ tag: 'hierarchy', id: '', index: 0 }] };
|
|
265
|
+
const fingerprint = { selector: '//loki-path', strategy: 'xpath', attributes: {} };
|
|
266
|
+
yield store.saveSignature(Object.assign(Object.assign({}, fingerprint), { nodeName: 'android.widget.Button', path: PATH }));
|
|
267
|
+
yield store.saveSignature(Object.assign(Object.assign({}, fingerprint), { nodeName: 'android.widget.Button', path: undefined }));
|
|
268
|
+
(0, chai_1.expect)((_a = (yield store.getSignature('//loki-path'))) === null || _a === void 0 ? void 0 : _a.path).to.deep.equal(PATH);
|
|
269
|
+
}));
|
|
270
|
+
});
|