@xenon-device-management/xenon 2.15.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 +38 -26
- package/lib/package.json +2 -1
- package/lib/public/assets/{AnnotationOverlay-Btq3dZRI.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-C9OoAgg_.js → ApiKeyGate-GmckX_7r.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-DBC_xczB.js → DeviceMosaicView-DqODzkyo.js} +1 -1
- package/lib/public/assets/{EmptyState-DqWU2-vM.js → EmptyState-7hETKEWb.js} +1 -1
- package/lib/public/assets/{FieldGroup-CveQ3xrA.js → FieldGroup-GOgssFEP.js} +1 -1
- package/lib/public/assets/{FilterMenu-BfKDSfHz.js → FilterMenu-Yvf6RGMd.js} +1 -1
- package/lib/public/assets/{Menu-CtdCybzA.js → Menu-KrPnfdsf.js} +1 -1
- package/lib/public/assets/{Modal-Bsvj1tm2.js → Modal-C6WjZFeI.js} +1 -1
- package/lib/public/assets/{RecordingPage-CRofOiyx.js → RecordingPage-CSRIhZEh.js} +1 -1
- package/lib/public/assets/{RecordingsPage-jNQHb2IR.js → RecordingsPage-CB8ST3nE.js} +1 -1
- package/lib/public/assets/{SegmentedControl-C8HtTha-.js → SegmentedControl-C9o90KId.js} +1 -1
- package/lib/public/assets/{SettingCard-D8K1LZNo.js → SettingCard-CPm3w9l2.js} +1 -1
- package/lib/public/assets/{Table-zNTX92Pn.js → Table-DwIgERRt.js} +1 -1
- package/lib/public/assets/{activity-CJ8LwZq9.js → activity-DrLbHifn.js} +1 -1
- package/lib/public/assets/{ai-settings-CgxQZqrc.js → ai-settings-DU_Ls06P.js} +1 -1
- package/lib/public/assets/{api-keys-ECpo-zTo.js → api-keys-CWYdEzJ_.js} +1 -1
- package/lib/public/assets/{apps-BjGymyf3.js → apps-BqNVdln7.js} +1 -1
- package/lib/public/assets/{arrow-left-BWfCbeHP.js → arrow-left-BtB0_Jg_.js} +1 -1
- package/lib/public/assets/{arrow-right-DC7UGf_n.js → arrow-right-C8NhdtyN.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BOjwybDk.js → arrow-up-right-gMg0ffvv.js} +1 -1
- package/lib/public/assets/{auth-shell-CxMTo9aH.js → auth-shell-CiYZ3N4M.js} +2 -2
- package/lib/public/assets/{builds-page-DOCm4HkA.js → builds-page-EaaqDmLN.js} +1 -1
- package/lib/public/assets/{button-Cg08-EMT.js → button-DDau4Axv.js} +1 -1
- package/lib/public/assets/{calendar-CA1ikLJs.js → calendar-DQ83B1ed.js} +1 -1
- package/lib/public/assets/{check-BMTaH72W.js → check-K1ovTQam.js} +1 -1
- package/lib/public/assets/{chevron-right-B2QOv9Nx.js → chevron-right-Biq00pMt.js} +1 -1
- package/lib/public/assets/{circle-check-CYXBoUqT.js → circle-check-CBv88cH7.js} +1 -1
- package/lib/public/assets/{circle-x-DTwnSAnl.js → circle-x-Bmltailn.js} +1 -1
- package/lib/public/assets/{clock-C8_HViQP.js → clock-BPrld-3h.js} +1 -1
- package/lib/public/assets/{copy-Be7LNMXV.js → copy-t0QB14JI.js} +1 -1
- package/lib/public/assets/{device-explorer-BOqtfGXz.js → device-explorer-BgGZ8AvV.js} +1 -1
- package/lib/public/assets/{download-B56lwxWP.js → download-BroHxNOh.js} +1 -1
- package/lib/public/assets/{forgot-password-CCU6bzLn.js → forgot-password-Bjmf49i6.js} +1 -1
- package/lib/public/assets/{h264Stream-C-OtmIuk.js → h264Stream-DeD__6wa.js} +1 -1
- package/lib/public/assets/{index-CfxsUslp.js → index-72oF89LJ.js} +1 -1
- package/lib/public/assets/{index-B9KQ6LsY.js → index-B0JeuZbg.js} +1 -1
- package/lib/public/assets/index-B0gQkEg0.js +219 -0
- package/lib/public/assets/{input-CZHFjbSg.js → input-DIc4MSC7.js} +1 -1
- package/lib/public/assets/{line-chart-Gl_Dz3X_.js → line-chart-BcWm2vW8.js} +1 -1
- package/lib/public/assets/{list-checks-CCgjeyGY.js → list-checks-B06IJJad.js} +1 -1
- package/lib/public/assets/{lock-CkOwgDcx.js → lock-pVLvnN30.js} +1 -1
- package/lib/public/assets/{login-D5cc4w7M.js → login-CN77vy--.js} +1 -1
- package/lib/public/assets/{maintenance-settings-t7qr3fvX.js → maintenance-settings-CSdzQSkY.js} +1 -1
- package/lib/public/assets/{monitor-DEJixKQO.js → monitor-BtrN5IkI.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CtOrk4M8.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
- package/lib/public/assets/{network-Dii9qdvk.js → network-BSfA8ZxG.js} +1 -1
- package/lib/public/assets/{overview-rolAACrO.js → overview-BfTebCK5.js} +1 -1
- package/lib/public/assets/{page-header-COQZxJfR.js → page-header-D8KUX6Qg.js} +1 -1
- package/lib/public/assets/{play-DrN1Se_R.js → play-Cvc8xzic.js} +1 -1
- package/lib/public/assets/{plus-5aq-9uRx.js → plus-C9Zj84UF.js} +1 -1
- package/lib/public/assets/{profile-page-6i5kY6kF.js → profile-page-Bi27STgs.js} +1 -1
- package/lib/public/assets/{reset-password-sp2IBrc7.js → reset-password-CPsOuwSu.js} +1 -1
- package/lib/public/assets/{runbook-page-DJDjeopU.js → runbook-page-DEQ-DBOX.js} +153 -102
- package/lib/public/assets/{select-Bpyn7rWO.js → select-BWpnhWXj.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BH74vmKv.js → selector-detail-redirect-BefSCSpe.js} +1 -1
- package/lib/public/assets/{selector-health-page-Cw_g218H.js → selector-health-page-MSk96Zz7.js} +1 -1
- package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
- package/lib/public/assets/{settings-C3uyGhyc.js → settings-V6x-cXwy.js} +1 -1
- package/lib/public/assets/{stat-tile-xVqAKIlI.js → stat-tile-Duex-GZD.js} +1 -1
- package/lib/public/assets/{tablet-BxUScAeO.js → tablet-BBcoqBmL.js} +1 -1
- package/lib/public/assets/{teams-B5ljGkY2.js → teams-ts8p5rEh.js} +1 -1
- package/lib/public/assets/{trash-2-B701i8sy.js → trash-2-CZ2ew6AZ.js} +1 -1
- package/lib/public/assets/{upload-C2gr44RS.js → upload-Dv99y_le.js} +1 -1
- package/lib/public/assets/{use-builds-data-NeQOFEoy.js → use-builds-data-DrYC8JCF.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DEzJq5ef.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
- package/lib/public/assets/{users-D_vv_yz4.js → users-D7tKMESH.js} +1 -1
- package/lib/public/assets/{users-MY6eNe6s.js → users-gJ5Y01yo.js} +1 -1
- package/lib/public/assets/{video-off-OK1lNZQn.js → video-off-CqZVuZGs.js} +1 -1
- package/lib/public/assets/{webhook-settings-BU-C9lBf.js → webhook-settings-DI5DSW99.js} +1 -1
- package/lib/public/assets/{zap-DO9ry1iz.js → zap-ClcHFxfo.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +11 -7
- package/lib/src/InternalHttpClient.js +27 -5
- package/lib/src/XenonCapabilityManager.js +10 -3
- package/lib/src/app/openapi/sessions.yaml +7 -5
- package/lib/src/app/routers/auth.js +2 -1
- package/lib/src/app/routers/users.js +2 -1
- package/lib/src/app/ws/nodeSocketRelay.js +77 -11
- package/lib/src/dashboard/commandLogFields.js +42 -0
- package/lib/src/dashboard/event-manager.js +26 -154
- package/lib/src/dashboard/services/failure-analysis-service.js +8 -86
- package/lib/src/dashboard/services/failureCategories.js +264 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
- package/lib/src/device-managers/IOSDeviceManager.js +10 -0
- package/lib/src/device-utils.js +14 -6
- package/lib/src/gateway/forwardToNode.js +4 -11
- package/lib/src/gateway/hubSessionToken.js +27 -4
- package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
- package/lib/src/helpers/outboundProxy.js +219 -0
- package/lib/src/index.js +21 -0
- package/lib/src/interceptors/CommandInterceptor.js +13 -15
- package/lib/src/interceptors/xenonScripts.js +28 -7
- package/lib/src/logger.js +3 -1
- package/lib/src/plugin.js +8 -3
- 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/ApiKeyService.js +16 -5
- package/lib/src/services/InterceptorService.js +6 -2
- package/lib/src/services/OrphanSweeper.js +6 -0
- package/lib/src/services/SelectorStateService.js +11 -7
- package/lib/src/services/SelectorVerificationJob.js +6 -2
- package/lib/src/services/SessionLifecycleService.js +32 -18
- package/lib/src/services/SocketClient.js +11 -0
- package/lib/src/services/SocketServer.js +361 -42
- 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/healing/HealingOrchestrator.js +3 -1
- package/lib/src/services/identity/identityChanges.js +41 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +97 -10
- package/lib/src/services/logcat/deviceLogBook.js +5 -4
- package/lib/src/services/logcat/iosDriverLog.js +178 -0
- package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
- package/lib/src/services/token/JwtKeyService.js +8 -2
- package/lib/src/services/token/mcpTokenTtl.js +27 -0
- package/lib/src/sessions/LocalSession.js +29 -1
- package/lib/src/sessions/RemoteSession.js +12 -2
- package/lib/src/sessions/SessionManager.js +5 -8
- package/lib/test/helpers/driver-log.js +38 -0
- package/lib/test/helpers/fake-http-proxy.js +106 -0
- package/lib/test/helpers/otlp-probe.js +107 -0
- package/lib/test/integration/androidDevices.spec.js +1 -1
- package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
- package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
- package/lib/test/integration/selector-state-flow.spec.js +5 -5
- package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
- package/lib/test/unit/android-adb-retry.spec.js +112 -0
- package/lib/test/unit/command-log-network-capture.spec.js +191 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +59 -11
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/database-check.spec.js +1 -0
- package/lib/test/unit/device-utils.spec.js +13 -4
- package/lib/test/unit/event-log-session-data.spec.js +11 -4
- package/lib/test/unit/failure-analysis.spec.js +22 -8
- package/lib/test/unit/failure-categories.spec.js +24 -22
- package/lib/test/unit/failure-categorization.spec.js +393 -0
- package/lib/test/unit/healing-tier-gate.spec.js +3 -2
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +72 -1
- 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/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/openapi-for-site.spec.js +80 -0
- package/lib/test/unit/outbound-proxy.spec.js +320 -0
- package/lib/test/unit/port-exhaustion.spec.js +2 -1
- package/lib/test/unit/proxy-option.spec.js +358 -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-state-service.spec.js +72 -23
- package/lib/test/unit/selector-verification-clean-build.spec.js +1 -1
- package/lib/test/unit/selector-verification-job.spec.js +15 -6
- 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/session-device-logs-ios.spec.js +180 -0
- package/lib/test/unit/session-device-logs.spec.js +11 -2
- package/lib/test/unit/session-end-failure-analysis.spec.js +5 -5
- package/lib/test/unit/session-log-indexes.spec.js +1 -1
- 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/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 +2 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/schema.json +11 -7
- 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/index-BOxe43p1.js +0 -219
- package/lib/public/assets/session-detail-page-AZJE6XXE.js +0 -4
- package/lib/src/scripts/prepare-prisma.js +0 -89
|
@@ -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);
|
|
@@ -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
|
});
|
|
@@ -59,7 +59,7 @@ describe('SelectorVerificationJob counts only builds where the selector was foun
|
|
|
59
59
|
yield scratch.db.selectorEvent.deleteMany();
|
|
60
60
|
yield scratch.db.selectorState.deleteMany();
|
|
61
61
|
yield scratch.db.sessionLog.deleteMany();
|
|
62
|
-
socket = {
|
|
62
|
+
socket = { emitToDashboardForSelector: sinon_1.default.stub().resolves() };
|
|
63
63
|
yield scratch.db.selectorState.create({
|
|
64
64
|
data: {
|
|
65
65
|
original_strategy: 'xpath',
|
|
@@ -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());
|
|
@@ -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([
|
|
@@ -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([
|
|
@@ -130,7 +139,7 @@ describe('SelectorVerificationJob.run', () => {
|
|
|
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([
|
|
@@ -53,7 +53,7 @@ describe('SelectorVerificationJob catches a heal the live check missed', functio
|
|
|
53
53
|
yield scratch.db.selectorEvent.deleteMany();
|
|
54
54
|
yield scratch.db.selectorState.deleteMany();
|
|
55
55
|
yield scratch.db.sessionLog.deleteMany();
|
|
56
|
-
socket = {
|
|
56
|
+
socket = { emitToDashboardForSelector: sinon_1.default.stub().resolves() };
|
|
57
57
|
}));
|
|
58
58
|
const find = (build, selector, healed, at) => scratch.db.sessionLog.create({
|
|
59
59
|
data: {
|
|
@@ -105,7 +105,7 @@ describe('SelectorVerificationJob catches a heal the live check missed', functio
|
|
|
105
105
|
(0, chai_1.expect)(row).to.include({ status: 'active', regression_count: 1, clean_builds_count: 0 });
|
|
106
106
|
(0, chai_1.expect)(row.fixed_at).to.equal(null);
|
|
107
107
|
(0, chai_1.expect)(yield actionsOf('//pending/broke')).to.deep.equal(['broke_again']);
|
|
108
|
-
(0, chai_1.expect)(socket.
|
|
108
|
+
(0, chai_1.expect)(socket.emitToDashboardForSelector.firstCall.args[0]).to.equal('selector_regressed');
|
|
109
109
|
}));
|
|
110
110
|
it('sends a fixed selector back to fix', () => __awaiter(this, void 0, void 0, function* () {
|
|
111
111
|
yield state('//resolved/broke', 'resolved');
|
|
@@ -0,0 +1,209 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
require("reflect-metadata");
|
|
13
|
+
const chai_1 = require("chai");
|
|
14
|
+
const typedi_1 = require("typedi");
|
|
15
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
16
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
17
|
+
const selector_health_fixture_1 = require("../helpers/selector-health-fixture");
|
|
18
|
+
const SelectorVisibilityResolver_1 = require("../../src/services/selector-health/SelectorVisibilityResolver");
|
|
19
|
+
const KEY = { strategy: 'xpath', selector: '//x' };
|
|
20
|
+
const MEMBER_A = { userId: 'u-a', teamIds: ['team-a'] };
|
|
21
|
+
describe('SelectorVisibilityResolver: who may see a selector, for its live events', () => {
|
|
22
|
+
it('TypeDI builds it, as the socket server gets it (Container.get)', () => {
|
|
23
|
+
// A function-typed constructor parameter would make Container.get throw.
|
|
24
|
+
const restore = (0, container_registration_1.saveRegistrations)(SelectorVisibilityResolver_1.SelectorVisibilityResolver);
|
|
25
|
+
try {
|
|
26
|
+
(0, chai_1.expect)(typedi_1.Container.get(SelectorVisibilityResolver_1.SelectorVisibilityResolver)).to.be.instanceOf(SelectorVisibilityResolver_1.SelectorVisibilityResolver);
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
restore();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
describe('caching', () => {
|
|
33
|
+
let clock;
|
|
34
|
+
let scopes;
|
|
35
|
+
let checks;
|
|
36
|
+
let visible;
|
|
37
|
+
function resolver(over = {}) {
|
|
38
|
+
return new SelectorVisibilityResolver_1.SelectorVisibilityResolver(Object.assign({ sessionScope: (viewer) => __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
var _a;
|
|
40
|
+
scopes.push((_a = viewer.userId) !== null && _a !== void 0 ? _a : '');
|
|
41
|
+
return { OR: [{ device_udid: { in: viewer.teamIds } }] };
|
|
42
|
+
}), healedIn: (key) => __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
checks.push(key.selector);
|
|
44
|
+
return visible;
|
|
45
|
+
}), now: () => clock }, over));
|
|
46
|
+
}
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
clock = 1000000;
|
|
49
|
+
scopes = [];
|
|
50
|
+
checks = [];
|
|
51
|
+
visible = true;
|
|
52
|
+
});
|
|
53
|
+
it('asks once per viewer and selector while the answer is fresh, then again', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
const r = resolver();
|
|
55
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(true);
|
|
56
|
+
visible = false;
|
|
57
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A), 'still the cached answer').to.equal(true);
|
|
58
|
+
(0, chai_1.expect)(checks).to.have.length(1);
|
|
59
|
+
clock += SelectorVisibilityResolver_1.SELECTOR_VISIBILITY_TTL_MS;
|
|
60
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A), 'asked again after the TTL').to.equal(false);
|
|
61
|
+
(0, chai_1.expect)(checks).to.have.length(2);
|
|
62
|
+
}));
|
|
63
|
+
it('concurrent questions share one lookup', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
64
|
+
const r = resolver();
|
|
65
|
+
const answers = yield Promise.all([
|
|
66
|
+
r.canSee(KEY, MEMBER_A),
|
|
67
|
+
r.canSee(KEY, { userId: 'u-a', teamIds: ['team-a'] }),
|
|
68
|
+
r.canSee(KEY, MEMBER_A),
|
|
69
|
+
]);
|
|
70
|
+
(0, chai_1.expect)(answers).to.deep.equal([true, true, true]);
|
|
71
|
+
(0, chai_1.expect)(checks).to.have.length(1);
|
|
72
|
+
(0, chai_1.expect)(scopes).to.have.length(1);
|
|
73
|
+
}));
|
|
74
|
+
it("a viewer's sessions are read once for all their selectors", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
const r = resolver();
|
|
76
|
+
yield r.canSee(KEY, MEMBER_A);
|
|
77
|
+
yield r.canSee({ strategy: 'id', selector: 'other' }, MEMBER_A);
|
|
78
|
+
(0, chai_1.expect)(scopes).to.deep.equal(['u-a']);
|
|
79
|
+
(0, chai_1.expect)(checks).to.deep.equal(['//x', 'other']);
|
|
80
|
+
}));
|
|
81
|
+
it('the same teams in another order are the same viewer; another user or team is not', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
+
const r = resolver();
|
|
83
|
+
yield r.canSee(KEY, { userId: 'u-a', teamIds: ['team-a', 'team-b'] });
|
|
84
|
+
yield r.canSee(KEY, { userId: 'u-a', teamIds: ['team-b', 'team-a'] });
|
|
85
|
+
(0, chai_1.expect)(checks, 'team order').to.have.length(1);
|
|
86
|
+
yield r.canSee(KEY, { userId: 'u-b', teamIds: ['team-a', 'team-b'] });
|
|
87
|
+
yield r.canSee(KEY, { userId: 'u-a', teamIds: ['team-a'] });
|
|
88
|
+
(0, chai_1.expect)(checks, 'another user, other teams').to.have.length(3);
|
|
89
|
+
}));
|
|
90
|
+
it('a selector with no strategy is its own selector, not one with the same text', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
const r = resolver();
|
|
92
|
+
yield r.canSee({ strategy: '', selector: '//x' }, MEMBER_A);
|
|
93
|
+
yield r.canSee({ strategy: 'xpath', selector: '//x' }, MEMBER_A);
|
|
94
|
+
(0, chai_1.expect)(checks).to.have.length(2);
|
|
95
|
+
}));
|
|
96
|
+
it('a lookup that fails answers no, and is asked again next time', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
let fail = true;
|
|
98
|
+
const r = resolver({
|
|
99
|
+
healedIn: (key) => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
|
+
checks.push(key.selector);
|
|
101
|
+
if (fail)
|
|
102
|
+
throw new Error('database gone');
|
|
103
|
+
return true;
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(false);
|
|
107
|
+
fail = false;
|
|
108
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(true);
|
|
109
|
+
(0, chai_1.expect)(checks).to.have.length(2);
|
|
110
|
+
}));
|
|
111
|
+
it("a viewer's sessions that can't be read answer no, and are read again next time", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
|
+
let fail = true;
|
|
113
|
+
const r = resolver({
|
|
114
|
+
sessionScope: (viewer) => __awaiter(void 0, void 0, void 0, function* () {
|
|
115
|
+
var _a;
|
|
116
|
+
scopes.push((_a = viewer.userId) !== null && _a !== void 0 ? _a : '');
|
|
117
|
+
if (fail)
|
|
118
|
+
throw new Error('database gone');
|
|
119
|
+
return undefined;
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(false);
|
|
123
|
+
fail = false;
|
|
124
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(true);
|
|
125
|
+
(0, chai_1.expect)(scopes).to.have.length(2);
|
|
126
|
+
}));
|
|
127
|
+
it('a lookup that hangs answers no after the timeout, and is asked again next time', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
+
let hang = true;
|
|
129
|
+
const r = resolver({
|
|
130
|
+
healedIn: (key) => {
|
|
131
|
+
checks.push(key.selector);
|
|
132
|
+
return hang ? new Promise(() => undefined) : Promise.resolve(true);
|
|
133
|
+
},
|
|
134
|
+
lookupTimeoutMs: 20,
|
|
135
|
+
});
|
|
136
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(false);
|
|
137
|
+
hang = false;
|
|
138
|
+
(0, chai_1.expect)(yield r.canSee(KEY, MEMBER_A)).to.equal(true);
|
|
139
|
+
(0, chai_1.expect)(checks).to.have.length(2);
|
|
140
|
+
}));
|
|
141
|
+
});
|
|
142
|
+
describe("REST's rule, on a real database (Selector Health's own fixture)", () => {
|
|
143
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
144
|
+
const UNPLUGGED = 'sh-phone-unplugged';
|
|
145
|
+
before(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
|
+
yield (0, selector_health_fixture_1.seedSelectorHealth)(scratch.db);
|
|
147
|
+
// Priya's session on a phone that has since gone: no Device row.
|
|
148
|
+
yield scratch.db.session.create({
|
|
149
|
+
data: {
|
|
150
|
+
id: 'sh-s-unplugged',
|
|
151
|
+
device_udid: UNPLUGGED,
|
|
152
|
+
device_name: 'Gone',
|
|
153
|
+
device_platform: 'android',
|
|
154
|
+
desired_capabilities: '{}',
|
|
155
|
+
session_capabilities: '{}',
|
|
156
|
+
node_id: 'localhost',
|
|
157
|
+
has_live_video: false,
|
|
158
|
+
device_version: '14',
|
|
159
|
+
status: 'passed',
|
|
160
|
+
user_id: selector_health_fixture_1.USER.priya,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
yield scratch.db.sessionLog.create({
|
|
164
|
+
data: {
|
|
165
|
+
session_id: 'sh-s-unplugged',
|
|
166
|
+
url: '/element',
|
|
167
|
+
method: 'POST',
|
|
168
|
+
title: 'findElement',
|
|
169
|
+
response: '{}',
|
|
170
|
+
command_name: 'findElement',
|
|
171
|
+
is_healed: true,
|
|
172
|
+
original_strategy: 'xpath',
|
|
173
|
+
original_selector: '//unplugged/only',
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}));
|
|
177
|
+
const xpath = (selector) => ({ strategy: 'xpath', selector });
|
|
178
|
+
const teamA = { userId: selector_health_fixture_1.USER.priya, teamIds: [selector_health_fixture_1.TEAM.a] };
|
|
179
|
+
const teamB = { userId: selector_health_fixture_1.USER.alex, teamIds: [selector_health_fixture_1.TEAM.b] };
|
|
180
|
+
const noTeam = { userId: 'sh-u-nobody', teamIds: [] };
|
|
181
|
+
it("a team's member sees the selectors healed on their team's phone or a shared one, not another team's", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
+
const r = new SelectorVisibilityResolver_1.SelectorVisibilityResolver();
|
|
183
|
+
(0, chai_1.expect)(yield r.canSee({ strategy: 'id', selector: selector_health_fixture_1.SEL.warm }, teamA), 'team A phone').to.equal(true);
|
|
184
|
+
(0, chai_1.expect)(yield r.canSee(xpath(selector_health_fixture_1.SEL.hot), teamA), 'shared phone').to.equal(true);
|
|
185
|
+
(0, chai_1.expect)(yield r.canSee(xpath(selector_health_fixture_1.SEL.bOnly), teamA), "team B's phone").to.equal(false);
|
|
186
|
+
(0, chai_1.expect)(yield r.canSee(xpath(selector_health_fixture_1.SEL.bOnly), teamB)).to.equal(true);
|
|
187
|
+
(0, chai_1.expect)(yield r.canSee({ strategy: 'id', selector: selector_health_fixture_1.SEL.warm }, teamB)).to.equal(false);
|
|
188
|
+
}));
|
|
189
|
+
it('a member of no team sees only what healed on a shared phone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
190
|
+
const r = new SelectorVisibilityResolver_1.SelectorVisibilityResolver();
|
|
191
|
+
(0, chai_1.expect)(yield r.canSee(xpath(selector_health_fixture_1.SEL.hot), noTeam)).to.equal(true);
|
|
192
|
+
(0, chai_1.expect)(yield r.canSee(xpath(selector_health_fixture_1.SEL.muted), noTeam)).to.equal(false);
|
|
193
|
+
(0, chai_1.expect)(yield r.canSee(xpath(selector_health_fixture_1.SEL.mutedB), noTeam)).to.equal(false);
|
|
194
|
+
}));
|
|
195
|
+
it('a heal recorded with no strategy is strategy ""', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
196
|
+
const r = new SelectorVisibilityResolver_1.SelectorVisibilityResolver();
|
|
197
|
+
(0, chai_1.expect)(yield r.canSee({ strategy: '', selector: selector_health_fixture_1.SEL.legacy }, noTeam)).to.equal(true);
|
|
198
|
+
}));
|
|
199
|
+
it("a phone that has gone leaves its sessions' selectors to their owner", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
200
|
+
const r = new SelectorVisibilityResolver_1.SelectorVisibilityResolver();
|
|
201
|
+
(0, chai_1.expect)(yield r.canSee(xpath('//unplugged/only'), teamA), 'Priya owns it').to.equal(true);
|
|
202
|
+
(0, chai_1.expect)(yield r.canSee(xpath('//unplugged/only'), teamB)).to.equal(false);
|
|
203
|
+
}));
|
|
204
|
+
it('an unknown selector is seen by no one', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
205
|
+
const r = new SelectorVisibilityResolver_1.SelectorVisibilityResolver();
|
|
206
|
+
(0, chai_1.expect)(yield r.canSee(xpath('//never/healed'), teamA)).to.equal(false);
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
});
|