@xenon-device-management/xenon 2.9.1 → 2.10.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/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-Dm_URoOL.js → AnnotationOverlay-LzfXiiZJ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BdGOuXDm.js → ApiKeyGate-BzFDp4mK.js} +1 -1
- package/lib/public/assets/{BugReportButton-BuWGEjgw.js → BugReportButton-BpgWvmZo.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-Pu3rJ99D.js → DeviceMosaicView-CCB-egSH.js} +2 -2
- package/lib/public/assets/{EmptyState-syyHElMd.js → EmptyState-DkUVmANo.js} +1 -1
- package/lib/public/assets/{FieldGroup-BZR5ZBVv.js → FieldGroup-mbfnfTql.js} +1 -1
- package/lib/public/assets/{FilterMenu-CsgtrmPb.js → FilterMenu-BH5usz4Y.js} +1 -1
- package/lib/public/assets/{Menu-DP8XOm1A.js → Menu-Cbo9mdTk.js} +1 -1
- package/lib/public/assets/{Modal-Dtd-8uI2.js → Modal-D6XfQHPr.js} +1 -1
- package/lib/public/assets/{RecordingPage-CdAtAmHj.js → RecordingPage-CmFPkWqH.js} +1 -1
- package/lib/public/assets/{RecordingsPage-Br3CKxTe.js → RecordingsPage-CSlKk2qC.js} +1 -1
- package/lib/public/assets/{SegmentedControl-BOaNmUL-.js → SegmentedControl-BFW9Dlqd.js} +1 -1
- package/lib/public/assets/{SettingCard-CQuK9ZZX.js → SettingCard-C4DioTTi.js} +1 -1
- package/lib/public/assets/{Table-CswCefnT.js → Table-DrKjlL00.js} +1 -1
- package/lib/public/assets/{activity-6fhH3u4C.js → activity-C9bE7Jm8.js} +1 -1
- package/lib/public/assets/{ai-settings-CU24L3D4.js → ai-settings-DRYic4oO.js} +1 -1
- package/lib/public/assets/{api-keys-DC3BZDaG.js → api-keys-CaQU0g7c.js} +1 -1
- package/lib/public/assets/{apps-Ce5Z-wNc.js → apps-c1gUFmmK.js} +1 -1
- package/lib/public/assets/{arrow-left-DZJqYn7r.js → arrow-left-DMvAWA8r.js} +1 -1
- package/lib/public/assets/{arrow-right-nIvK8CyN.js → arrow-right-BEQgxmKT.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BfugJj3f.js → arrow-up-right-Ba0e2GeU.js} +1 -1
- package/lib/public/assets/{auth-shell-B1Cz75Vq.js → auth-shell-COODQnc7.js} +2 -2
- package/lib/public/assets/{builds-page-5mlJGjIk.js → builds-page-DZQgWBUs.js} +4 -4
- package/lib/public/assets/{button-D_W88sn-.js → button-WW9j43wR.js} +1 -1
- package/lib/public/assets/{calendar-Dc6pWZS9.js → calendar-COUs9VxF.js} +1 -1
- package/lib/public/assets/{check-BbvtnP8m.js → check-D8ifHIJR.js} +1 -1
- package/lib/public/assets/{chevron-right-CgBUaRCA.js → chevron-right-BjKVUlB_.js} +1 -1
- package/lib/public/assets/{circle-check-BWjEKvA4.js → circle-check-CfP79QE2.js} +1 -1
- package/lib/public/assets/{circle-x-BLUdeRhT.js → circle-x-BZbFCixZ.js} +1 -1
- package/lib/public/assets/{clock-PAhKG1jT.js → clock-BuTm35Qg.js} +1 -1
- package/lib/public/assets/{copy-2CHdAPBf.js → copy-W00zUBwN.js} +1 -1
- package/lib/public/assets/{device-explorer-EyAhEL_S.js → device-explorer-BEpS-Y-_.js} +5 -5
- package/lib/public/assets/{download-B1vKGhGP.js → download-CGz-MS3y.js} +1 -1
- package/lib/public/assets/{forgot-password-NSrrBA5N.js → forgot-password-DG7SqVgU.js} +1 -1
- package/lib/public/assets/{index-C8pvxG1f.css → index-DQa193-A.css} +1 -1
- package/lib/public/assets/{index-BEk1D5-B.js → index-DWHTp1YP.js} +1 -1
- package/lib/public/assets/{index-BxaH84UO.js → index-EkaQM1DK.js} +2 -2
- package/lib/public/assets/{input-cA_lrNIw.js → input-ChaTXeCq.js} +1 -1
- package/lib/public/assets/{keycodes-DZLCZ-uv.js → keycodes-BhbgeW_r.js} +10 -5
- package/lib/public/assets/line-chart-9P3RxScy.js +1 -0
- package/lib/public/assets/list-checks-_23IIEEL.js +11 -0
- package/lib/public/assets/{lock-ChoclF8X.js → lock-B2JaqqRI.js} +1 -1
- package/lib/public/assets/{login-63DiXJ0M.js → login-BEWIfLfC.js} +1 -1
- package/lib/public/assets/{maintenance-settings-A47Y-PYy.js → maintenance-settings-BGJIJQ-k.js} +1 -1
- package/lib/public/assets/{monitor-BYPF43Ds.js → monitor-BrwMY4G0.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-Pcl7inmZ.js → mouse-pointer-2-DsWDMyrY.js} +1 -1
- package/lib/public/assets/{network-ChaLfnWe.js → network-DfIhNVJv.js} +1 -1
- package/lib/public/assets/overview-B1mePY2_.js +6 -0
- package/lib/public/assets/{page-header-bv2AQSMG.js → page-header-DoD3TOzZ.js} +1 -1
- package/lib/public/assets/{plus-u0x2Y5uW.js → plus-BGvWvpBB.js} +1 -1
- package/lib/public/assets/{profile-page-Cvmef2iY.js → profile-page-CvV5aWvl.js} +1 -1
- package/lib/public/assets/{reset-password-DqStVhBo.js → reset-password-CGVd31LR.js} +1 -1
- package/lib/public/assets/{runbook-page-UK0C95QF.js → runbook-page-Bq2cvwb5.js} +1 -1
- package/lib/public/assets/{select-CQ6O804R.js → select-CTcD4jWR.js} +1 -1
- package/lib/public/assets/selector-detail-redirect-19odlD4B.js +1 -0
- package/lib/public/assets/selector-health-page-LqIsLrjW.js +11 -0
- package/lib/public/assets/session-detail-page-DQzb8_fq.js +4 -0
- package/lib/public/assets/{settings-Cva7V4n-.js → settings-C-EnGIqs.js} +1 -1
- package/lib/public/assets/stat-tile-BY0RDTRm.js +1 -0
- package/lib/public/assets/{tablet-BPtlTsKn.js → tablet-B4cFM-5B.js} +1 -1
- package/lib/public/assets/{teams-D4r1UMME.js → teams-BFe-RpYd.js} +1 -1
- package/lib/public/assets/time-D-gjmE1o.js +1 -0
- package/lib/public/assets/{trash-2-CnysPtdR.js → trash-2-CJCKBOP9.js} +1 -1
- package/lib/public/assets/{upload-Uyj4-Cyq.js → upload-DidWkbld.js} +1 -1
- package/lib/public/assets/use-builds-data-fnfxatOd.js +8 -0
- package/lib/public/assets/{use-password-reset-mode-Du-ykGHP.js → use-password-reset-mode-iPen3tmC.js} +1 -1
- package/lib/public/assets/{users-ttkwKBos.js → users-CrcjmRdf.js} +1 -1
- package/lib/public/assets/{users-Cs00GNmy.js → users-DBUv0CJx.js} +1 -1
- package/lib/public/assets/{video-off-BYa4qD8N.js → video-off-eh69-FKB.js} +1 -1
- package/lib/public/assets/view-state-CP9dE1UI.js +1 -0
- package/lib/public/assets/{webhook-settings-DuEnPlet.js → webhook-settings-CQoSshBn.js} +1 -1
- package/lib/public/assets/{zap-CxHn6Vwu.js → zap-CICtWiLn.js} +1 -1
- package/lib/public/index.html +2 -2
- package/lib/schema.json +5 -0
- package/lib/src/app/routers/dashboard.js +86 -40
- package/lib/src/app/routers/selector-health.js +75 -0
- package/lib/src/app/swagger-docs.js +77 -8
- package/lib/src/dashboard/event-manager.js +15 -99
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +6 -65
- package/lib/src/device-managers/ios/IOSStreamService.js +7 -66
- package/lib/src/device-managers/ios/WDAClient.js +65 -86
- package/lib/src/generated/client/edge.js +27 -3
- package/lib/src/generated/client/index-browser.js +24 -0
- package/lib/src/generated/client/index.d.ts +8207 -5283
- package/lib/src/generated/client/index.js +27 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +43 -9
- package/lib/src/generated/client/wasm.js +24 -0
- package/lib/src/prisma.js +2 -0
- package/lib/src/services/CleanupService.js +1 -0
- package/lib/src/services/NotificationService.js +3 -4
- package/lib/src/services/SelectorStateService.js +151 -113
- package/lib/src/services/SelectorVerificationJob.js +14 -9
- package/lib/src/services/metrics/AndroidMetricsSampler.js +94 -0
- package/lib/src/services/metrics/IOSMetricsSampler.js +120 -0
- package/lib/src/services/metrics/SessionMetricsService.js +238 -0
- package/lib/src/services/metrics/androidMetrics.js +94 -0
- package/lib/src/services/metrics/iosMetrics.js +26 -0
- package/lib/src/services/metrics/metricsBody.js +37 -0
- package/lib/src/services/metrics/types.js +19 -0
- package/lib/src/services/selector-health/access.js +66 -0
- package/lib/src/services/selector-health/healingTrend.js +49 -0
- package/lib/src/services/selector-health/people.js +27 -0
- package/lib/src/services/selector-health/selectorDetail.js +198 -0
- package/lib/src/services/selector-health/selectorKeys.js +36 -0
- package/lib/src/services/selector-health/selectorList.js +333 -0
- package/lib/src/services/selector-health/stateView.js +67 -0
- package/lib/test/helpers/scratch-database.js +2 -0
- package/lib/test/helpers/selector-health-fixture.js +216 -0
- package/lib/test/integration/team-visibility-sessions.spec.js +1 -0
- package/lib/test/unit/CleanupService.spec.js +28 -2
- package/lib/test/unit/dashboard-events-metrics.spec.js +146 -0
- package/lib/test/unit/healing-no-cost.spec.js +134 -0
- package/lib/test/unit/healing-state-endpoints.spec.js +70 -0
- package/lib/test/unit/healing-trend.spec.js +118 -0
- package/lib/test/unit/ios-discovery-same-host.spec.js +0 -1
- package/lib/test/unit/ios-no-network-fallback.spec.js +219 -0
- package/lib/test/unit/selector-event-model.spec.js +47 -0
- package/lib/test/unit/selector-health-actions.spec.js +106 -0
- package/lib/test/unit/selector-health-detail.spec.js +131 -0
- package/lib/test/unit/selector-health-list.spec.js +185 -0
- package/lib/test/unit/selector-state-service.spec.js +74 -1
- package/lib/test/unit/selector-verification-job.spec.js +38 -0
- package/lib/test/unit/session-metrics-parse.spec.js +138 -0
- package/lib/test/unit/session-metrics-route.spec.js +140 -0
- package/lib/test/unit/session-metrics-samplers.spec.js +248 -0
- package/lib/test/unit/session-metrics-service.spec.js +215 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20261002120000_session_metric/migration.sql +17 -0
- package/prisma/migrations/20261002130000_selector_event/migration.sql +13 -0
- package/prisma/schema.prisma +43 -9
- package/schema.json +5 -0
- package/lib/public/assets/chevron-left-DQDpR9ne.js +0 -6
- package/lib/public/assets/funnel-C9mzkCSI.js +0 -6
- package/lib/public/assets/layers-BuXMIJz3.js +0 -6
- package/lib/public/assets/list-checks-B8q68tPG.js +0 -6
- package/lib/public/assets/overview-bj_vktQG.js +0 -1
- package/lib/public/assets/selector-detail-page-D-PjU6mC.js +0 -1
- package/lib/public/assets/selector-health-BlICzZlk.css +0 -1
- package/lib/public/assets/selector-health-CVm0cTOU.js +0 -16
- package/lib/public/assets/selector-health-page-BdibBs8g.js +0 -16
- package/lib/public/assets/session-detail-page-BijicGOF.js +0 -4
- package/lib/public/assets/stat-tile-BdIoEoou.js +0 -8
- package/lib/public/assets/time-B8FIgwPE.js +0 -1
- package/lib/src/profiling/AndroidAppProfiler.js +0 -213
- /package/lib/public/assets/{session-detail-page-Bzzc3bMl.css → line-chart-Bzzc3bMl.css} +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
require("reflect-metadata");
|
|
49
|
+
const chai_1 = require("chai");
|
|
50
|
+
const axios_1 = __importDefault(require("axios"));
|
|
51
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
52
|
+
const events_1 = require("events");
|
|
53
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
54
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
55
|
+
const typedi_1 = require("typedi");
|
|
56
|
+
const appium_ios_device_1 = require("appium-ios-device");
|
|
57
|
+
const IOSDiscoveryService_1 = require("../../src/device-managers/ios/IOSDiscoveryService");
|
|
58
|
+
const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
|
|
59
|
+
const WDAClient_1 = require("../../src/device-managers/ios/WDAClient");
|
|
60
|
+
const ownDeviceRow = __importStar(require("../../src/device-managers/ownDeviceRow"));
|
|
61
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
62
|
+
const deviceUtils = __importStar(require("../../src/device-utils"));
|
|
63
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
64
|
+
const PortAllocator_1 = require("../../src/services/PortAllocator");
|
|
65
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
66
|
+
/**
|
|
67
|
+
* Xenon never reaches WebDriverAgent over the network, only through the
|
|
68
|
+
* phone's own forwarded port on 127.0.0.1.
|
|
69
|
+
*
|
|
70
|
+
* An iPhone's network address was meant to come from go-ios `ios info`, which
|
|
71
|
+
* has no such value (its lockdown values carry no IPAddress), so detection
|
|
72
|
+
* asked for it on every pass for nothing. The fallbacks that used it never
|
|
73
|
+
* ran for an iPhone. A simulator's address is the Mac's own, so they sent a
|
|
74
|
+
* simulator's commands to this Mac's port 8100, which can be an iPhone's
|
|
75
|
+
* WebDriverAgent: WDA names no phone.
|
|
76
|
+
*/
|
|
77
|
+
const IP = '192.168.0.104';
|
|
78
|
+
const PHONE = 'test-iphone-00008110-net';
|
|
79
|
+
const ADDRESS = '10.9.8.7';
|
|
80
|
+
describe('iOS: WebDriverAgent only through the phone’s own forwarded port', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
sinon_1.default.stub(process, 'kill');
|
|
83
|
+
});
|
|
84
|
+
afterEach(() => sinon_1.default.restore());
|
|
85
|
+
describe('detection', () => {
|
|
86
|
+
let commands;
|
|
87
|
+
function discovery() {
|
|
88
|
+
var _a;
|
|
89
|
+
const ctx = new PluginContext_1.PluginContext();
|
|
90
|
+
ctx.setContext(Object.assign({}, IPluginArgs_1.DefaultPluginArgs, { bindHostOrIp: IP }), 4724, 'test', '');
|
|
91
|
+
const svc = new IOSDiscoveryService_1.IOSDiscoveryService(ctx);
|
|
92
|
+
// resetTestContainer() stubs this on the prototype in a full run.
|
|
93
|
+
const proto = IOSDiscoveryService_1.IOSDiscoveryService.prototype;
|
|
94
|
+
svc.fetchLocalIOSDevices =
|
|
95
|
+
(_a = proto.fetchLocalIOSDevices.wrappedMethod) !== null && _a !== void 0 ? _a : proto.fetchLocalIOSDevices;
|
|
96
|
+
svc.trackingInitialized = true; // no usbmux listener
|
|
97
|
+
sinon_1.default.stub(svc, 'getConnectedDevices').resolves([PHONE]);
|
|
98
|
+
return svc;
|
|
99
|
+
}
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
commands = [];
|
|
102
|
+
// The go-ios binary is present, and every command it would run is recorded.
|
|
103
|
+
const exists = fs_extra_1.default.existsSync;
|
|
104
|
+
sinon_1.default
|
|
105
|
+
.stub(fs_extra_1.default, 'existsSync')
|
|
106
|
+
.callsFake((p) => String(p).endsWith('goIOS/ios') || exists(p));
|
|
107
|
+
sinon_1.default.stub(child_process_1.default, 'exec').callsFake(((cmd, ...rest) => {
|
|
108
|
+
commands.push(cmd);
|
|
109
|
+
const done = rest.find((a) => typeof a === 'function');
|
|
110
|
+
done === null || done === void 0 ? void 0 : done(null, '{}', '');
|
|
111
|
+
return new events_1.EventEmitter();
|
|
112
|
+
}));
|
|
113
|
+
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({ findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) });
|
|
114
|
+
const real = typedi_1.Container.get.bind(typedi_1.Container);
|
|
115
|
+
sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
|
|
116
|
+
if (token === PortAllocator_1.PortAllocator)
|
|
117
|
+
return { tryAcquire: () => __awaiter(void 0, void 0, void 0, function* () { return 28400; }) };
|
|
118
|
+
if (token === IOSStreamService_1.default)
|
|
119
|
+
return { getStreamStatus: () => undefined };
|
|
120
|
+
return real(token);
|
|
121
|
+
});
|
|
122
|
+
sinon_1.default.stub(deviceUtils, 'getUtilizationTime').resolves(0);
|
|
123
|
+
sinon_1.default.stub(appium_ios_device_1.utilities, 'getOSVersion').resolves('26.5');
|
|
124
|
+
sinon_1.default.stub(appium_ios_device_1.utilities, 'getDeviceName').resolves('iPhone 14 Plus');
|
|
125
|
+
sinon_1.default.stub(appium_ios_device_1.utilities, 'getDeviceInfo').rejects(new Error('no lockdown in tests'));
|
|
126
|
+
});
|
|
127
|
+
const asksForAnAddress = () => commands.filter((c) => /\binfo --udid\b/.test(c));
|
|
128
|
+
it('asks go-ios nothing about a known iPhone on each pass', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
129
|
+
const own = { udid: PHONE, host: `http://${IP}:4724`, platform: 'ios', ip: '' };
|
|
130
|
+
const [phone] = yield discovery().fetchLocalIOSDevices([own]);
|
|
131
|
+
(0, chai_1.expect)(phone.udid).to.equal(PHONE);
|
|
132
|
+
(0, chai_1.expect)(asksForAnAddress()).to.deep.equal([]);
|
|
133
|
+
}));
|
|
134
|
+
it('asks go-ios nothing about the address of an iPhone it adds', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
const phone = yield discovery().getDeviceInfo(PHONE);
|
|
136
|
+
(0, chai_1.expect)(phone.ip).to.equal('');
|
|
137
|
+
(0, chai_1.expect)(asksForAnAddress()).to.deep.equal([]);
|
|
138
|
+
}));
|
|
139
|
+
});
|
|
140
|
+
describe('WDAClient', () => {
|
|
141
|
+
it("retries a failed command on no host but 127.0.0.1, even with the phone's address", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
|
+
const urls = [];
|
|
143
|
+
const refuse = (url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
+
urls.push(url);
|
|
145
|
+
throw Object.assign(new Error(`connect ECONNREFUSED ${url}`), { code: 'ECONNREFUSED' });
|
|
146
|
+
});
|
|
147
|
+
sinon_1.default.stub(axios_1.default, 'post').callsFake(refuse);
|
|
148
|
+
sinon_1.default.stub(axios_1.default, 'get').callsFake(refuse);
|
|
149
|
+
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
150
|
+
findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return ({ udid: PHONE, wdaLocalPort: 28100, ip: ADDRESS }); }),
|
|
151
|
+
});
|
|
152
|
+
const real = typedi_1.Container.get.bind(typedi_1.Container);
|
|
153
|
+
sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
|
|
154
|
+
if (token === IOSStreamService_1.default) {
|
|
155
|
+
return {
|
|
156
|
+
getStreamStatus: () => undefined,
|
|
157
|
+
getWDASessionId: () => undefined,
|
|
158
|
+
setWDASessionId: () => undefined,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return real(token);
|
|
162
|
+
});
|
|
163
|
+
const err = yield new WDAClient_1.WDAClient()
|
|
164
|
+
.sendWDACommand(PHONE, 'post', '/wda/tap', { x: 10, y: 10 })
|
|
165
|
+
.then(() => null, (e) => e);
|
|
166
|
+
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/ECONNREFUSED/);
|
|
167
|
+
(0, chai_1.expect)(urls).to.not.deep.equal([]);
|
|
168
|
+
(0, chai_1.expect)(urls.filter((u) => !u.startsWith('http://127.0.0.1:28100/'))).to.deep.equal([]);
|
|
169
|
+
}));
|
|
170
|
+
});
|
|
171
|
+
describe('the stream watchdog', () => {
|
|
172
|
+
let spawned;
|
|
173
|
+
function fakeProcess(exitCode) {
|
|
174
|
+
return Object.assign(new events_1.EventEmitter(), { exitCode, kill: () => true });
|
|
175
|
+
}
|
|
176
|
+
function streamWith(forwardExitCode) {
|
|
177
|
+
// Bypass the constructor: it starts watchdog intervals.
|
|
178
|
+
const svc = Object.create(IOSStreamService_1.default.prototype);
|
|
179
|
+
svc.sessions = new Map([
|
|
180
|
+
[
|
|
181
|
+
PHONE,
|
|
182
|
+
{
|
|
183
|
+
udid: PHONE,
|
|
184
|
+
status: 'running',
|
|
185
|
+
wdaPort: 28100,
|
|
186
|
+
mjpegPort: 29100,
|
|
187
|
+
wdaProcess: fakeProcess(null),
|
|
188
|
+
forwardWDAProcess: fakeProcess(forwardExitCode),
|
|
189
|
+
forwardMJPEGProcess: fakeProcess(null),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
]);
|
|
193
|
+
return svc;
|
|
194
|
+
}
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
spawned = [];
|
|
197
|
+
sinon_1.default.stub(child_process_1.default, 'spawn').callsFake(((cmd, args) => {
|
|
198
|
+
spawned.push([cmd, ...(args !== null && args !== void 0 ? args : [])].join(' '));
|
|
199
|
+
return fakeProcess(null);
|
|
200
|
+
}));
|
|
201
|
+
// Something answers ready on the phone's address, port 8100; the
|
|
202
|
+
// phone's own forwarded port refuses.
|
|
203
|
+
sinon_1.default.stub(axios_1.default, 'get').callsFake(((url) => __awaiter(void 0, void 0, void 0, function* () {
|
|
204
|
+
if (url.startsWith(`http://${ADDRESS}:`))
|
|
205
|
+
return { status: 200, data: { value: { ready: true } } };
|
|
206
|
+
throw Object.assign(new Error('connect ECONNREFUSED'), { code: 'ECONNREFUSED' });
|
|
207
|
+
})));
|
|
208
|
+
sinon_1.default.stub(ownDeviceRow, 'findOwnDevice').resolves({ udid: PHONE, ip: ADDRESS });
|
|
209
|
+
});
|
|
210
|
+
it('calls a stream whose forward died unresponsive, whatever answers on the network', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
211
|
+
(0, chai_1.expect)(yield streamWith(1).isStreamResponsive(PHONE)).to.equal(false);
|
|
212
|
+
(0, chai_1.expect)(spawned, 'no forward-only restart').to.deep.equal([]);
|
|
213
|
+
}));
|
|
214
|
+
it('calls a stream whose forward is hung unresponsive, whatever answers on the network', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
215
|
+
(0, chai_1.expect)(yield streamWith(null).isStreamResponsive(PHONE)).to.equal(false);
|
|
216
|
+
(0, chai_1.expect)(spawned, 'no forward-only restart').to.deep.equal([]);
|
|
217
|
+
}));
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
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 prisma_1 = require("../../src/prisma");
|
|
15
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
16
|
+
describe('SelectorEvent table', function () {
|
|
17
|
+
this.timeout(90000);
|
|
18
|
+
(0, scratch_database_1.useScratchDatabase)();
|
|
19
|
+
it('keeps who did what to a selector, and why', () => __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const at = (s) => new Date(Date.UTC(2026, 9, 2, 10, 0, s));
|
|
21
|
+
yield prisma_1.prisma.selectorEvent.create({
|
|
22
|
+
data: {
|
|
23
|
+
original_strategy: 'xpath',
|
|
24
|
+
original_selector: '//a',
|
|
25
|
+
action: 'muted',
|
|
26
|
+
user_id: 'u-1',
|
|
27
|
+
reason: 'Screen being redesigned',
|
|
28
|
+
createdAt: at(0),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
yield prisma_1.prisma.selectorEvent.create({
|
|
32
|
+
data: {
|
|
33
|
+
original_strategy: 'xpath',
|
|
34
|
+
original_selector: '//a',
|
|
35
|
+
action: 'unmuted',
|
|
36
|
+
createdAt: at(1),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const rows = yield prisma_1.prisma.selectorEvent.findMany({
|
|
40
|
+
where: { original_strategy: 'xpath', original_selector: '//a' },
|
|
41
|
+
orderBy: { createdAt: 'desc' },
|
|
42
|
+
});
|
|
43
|
+
(0, chai_1.expect)(rows.map((r) => r.action)).to.deep.equal(['unmuted', 'muted']);
|
|
44
|
+
(0, chai_1.expect)(rows[0]).to.include({ user_id: null, reason: null });
|
|
45
|
+
(0, chai_1.expect)(rows[1]).to.include({ user_id: 'u-1', reason: 'Screen being redesigned' });
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
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 supertest_1 = __importDefault(require("supertest"));
|
|
18
|
+
const typedi_1 = require("typedi");
|
|
19
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
20
|
+
const selector_health_fixture_1 = require("../helpers/selector-health-fixture");
|
|
21
|
+
const SelectorStateService_1 = require("../../src/services/SelectorStateService");
|
|
22
|
+
describe('POST /healing/selector/state (real queries)', function () {
|
|
23
|
+
this.timeout(90000);
|
|
24
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
25
|
+
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
yield (0, selector_health_fixture_1.seedSelectorHealth)(scratch.db);
|
|
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, { emitToDashboard: () => undefined }));
|
|
29
|
+
}));
|
|
30
|
+
after(() => {
|
|
31
|
+
typedi_1.Container.remove(SelectorStateService_1.SelectorStateService);
|
|
32
|
+
});
|
|
33
|
+
const act = (auth, body) => (0, supertest_1.default)((0, selector_health_fixture_1.selectorHealthApp)(auth)).post('/healing/selector/state').send(body);
|
|
34
|
+
it('lets a member mute a selector they can see, recording them and their reason', () => __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
const res = yield act(selector_health_fixture_1.MEMBER_A, {
|
|
36
|
+
original_strategy: 'id',
|
|
37
|
+
original_selector: selector_health_fixture_1.SEL.warm,
|
|
38
|
+
action: 'mute',
|
|
39
|
+
reason: ' Moving to the new cart ',
|
|
40
|
+
});
|
|
41
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
42
|
+
const state = yield scratch.db.selectorState.findUnique({
|
|
43
|
+
where: {
|
|
44
|
+
original_strategy_original_selector: {
|
|
45
|
+
original_strategy: 'id',
|
|
46
|
+
original_selector: selector_health_fixture_1.SEL.warm,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
(0, chai_1.expect)(state === null || state === void 0 ? void 0 : state.status).to.equal('muted');
|
|
51
|
+
const events = yield scratch.db.selectorEvent.findMany({
|
|
52
|
+
where: { original_selector: selector_health_fixture_1.SEL.warm },
|
|
53
|
+
});
|
|
54
|
+
(0, chai_1.expect)(events.map((e) => [e.action, e.user_id, e.reason])).to.deep.equal([
|
|
55
|
+
['muted', selector_health_fixture_1.USER.priya, 'Moving to the new cart'],
|
|
56
|
+
]);
|
|
57
|
+
}));
|
|
58
|
+
it("refuses a member another team's selector, as if it didn't exist", () => __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
const res = yield act(selector_health_fixture_1.MEMBER_A, {
|
|
60
|
+
original_strategy: 'xpath',
|
|
61
|
+
original_selector: selector_health_fixture_1.SEL.bOnly,
|
|
62
|
+
action: 'mark_fixed',
|
|
63
|
+
});
|
|
64
|
+
(0, chai_1.expect)(res.status).to.equal(404);
|
|
65
|
+
(0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found', message: 'Selector not found' });
|
|
66
|
+
(0, chai_1.expect)(yield scratch.db.selectorState.count({ where: { original_selector: selector_health_fixture_1.SEL.bOnly } })).to.equal(0);
|
|
67
|
+
}));
|
|
68
|
+
it('lets an admin act on any selector', () => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const res = yield act(selector_health_fixture_1.ADMIN, {
|
|
70
|
+
original_strategy: 'xpath',
|
|
71
|
+
original_selector: selector_health_fixture_1.SEL.bOnly,
|
|
72
|
+
action: 'mark_fixed',
|
|
73
|
+
});
|
|
74
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
75
|
+
(0, chai_1.expect)(res.body.state.status).to.equal('pending');
|
|
76
|
+
}));
|
|
77
|
+
it('refuses a caller without the sessions scope', () => __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
const res = yield act(selector_health_fixture_1.READ_ONLY_A, {
|
|
79
|
+
original_strategy: 'xpath',
|
|
80
|
+
original_selector: selector_health_fixture_1.SEL.hot,
|
|
81
|
+
action: 'mute',
|
|
82
|
+
});
|
|
83
|
+
(0, chai_1.expect)(res.status).to.equal(403);
|
|
84
|
+
}));
|
|
85
|
+
it('marks fixed a selector recorded with no strategy', () => __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
const res = yield act(selector_health_fixture_1.MEMBER_A, {
|
|
87
|
+
original_strategy: '',
|
|
88
|
+
original_selector: selector_health_fixture_1.SEL.legacy,
|
|
89
|
+
action: 'mark_fixed',
|
|
90
|
+
});
|
|
91
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
92
|
+
const state = yield scratch.db.selectorState.findFirst({
|
|
93
|
+
where: { original_selector: selector_health_fixture_1.SEL.legacy },
|
|
94
|
+
});
|
|
95
|
+
(0, chai_1.expect)(state).to.include({ original_strategy: '', status: 'pending' });
|
|
96
|
+
}));
|
|
97
|
+
it('refuses a reason over 500 characters', () => __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
const res = yield act(selector_health_fixture_1.MEMBER_A, {
|
|
99
|
+
original_strategy: 'xpath',
|
|
100
|
+
original_selector: selector_health_fixture_1.SEL.hot,
|
|
101
|
+
action: 'mute',
|
|
102
|
+
reason: 'x'.repeat(501),
|
|
103
|
+
});
|
|
104
|
+
(0, chai_1.expect)(res.status).to.equal(400);
|
|
105
|
+
}));
|
|
106
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
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 supertest_1 = __importDefault(require("supertest"));
|
|
18
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
19
|
+
const selector_health_fixture_1 = require("../helpers/selector-health-fixture");
|
|
20
|
+
const selectorDetail_1 = require("../../src/services/selector-health/selectorDetail");
|
|
21
|
+
describe('selector panel: the query', () => {
|
|
22
|
+
it('needs a selector, and reads the rest with defaults', () => {
|
|
23
|
+
(0, chai_1.expect)((0, selectorDetail_1.parseSelectorDetailQuery)({})).to.equal(null);
|
|
24
|
+
(0, chai_1.expect)((0, selectorDetail_1.parseSelectorDetailQuery)({ selector: '//a' })).to.deep.equal({
|
|
25
|
+
strategy: '',
|
|
26
|
+
selector: '//a',
|
|
27
|
+
days: 30,
|
|
28
|
+
tz: 0,
|
|
29
|
+
});
|
|
30
|
+
(0, chai_1.expect)((0, selectorDetail_1.parseSelectorDetailQuery)({ strategy: 'xpath', selector: '//a', days: '7', tz: '330' })).to.deep.equal({ strategy: 'xpath', selector: '//a', days: 7, tz: 330 });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('GET /healing/selectors/detail (real queries)', function () {
|
|
34
|
+
this.timeout(90000);
|
|
35
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
36
|
+
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
yield (0, selector_health_fixture_1.seedSelectorHealth)(scratch.db);
|
|
38
|
+
}));
|
|
39
|
+
const detail = (auth, query) => (0, supertest_1.default)((0, selector_health_fixture_1.selectorHealthApp)(auth)).get('/healing/selectors/detail').query(query);
|
|
40
|
+
it('answers everything the panel shows', () => __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
const res = yield detail(selector_health_fixture_1.ADMIN, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.hot, days: '30', tz: '0' });
|
|
42
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
43
|
+
const b = res.body;
|
|
44
|
+
(0, chai_1.expect)(b).to.deep.include({
|
|
45
|
+
strategy: 'xpath',
|
|
46
|
+
selector: selector_health_fixture_1.SEL.hot,
|
|
47
|
+
days: 30,
|
|
48
|
+
heals: 4,
|
|
49
|
+
sessions: 2,
|
|
50
|
+
timeSpentMs: 18000,
|
|
51
|
+
canAct: true,
|
|
52
|
+
});
|
|
53
|
+
(0, chai_1.expect)(b.suggestions.map((s) => [
|
|
54
|
+
s.selector,
|
|
55
|
+
s.strategy,
|
|
56
|
+
s.count,
|
|
57
|
+
s.share,
|
|
58
|
+
s.methods,
|
|
59
|
+
])).to.deep.equal([
|
|
60
|
+
[selector_health_fixture_1.FIX.hot, 'xpath', 3, 0.75, ['Visual AI']],
|
|
61
|
+
[selector_health_fixture_1.FIX.hotLlm, 'accessibility id', 1, 0.25, ['LLM']],
|
|
62
|
+
]);
|
|
63
|
+
(0, chai_1.expect)(b.suggestions[0].averageConfidence).to.be.closeTo(0.9, 1e-9);
|
|
64
|
+
(0, chai_1.expect)(b.platforms).to.deep.equal([{ name: 'android', count: 4 }]);
|
|
65
|
+
(0, chai_1.expect)(b.builds).to.deep.equal([
|
|
66
|
+
{ id: selector_health_fixture_1.BUILD.id, name: selector_health_fixture_1.BUILD.name, count: 3 },
|
|
67
|
+
{ id: null, name: 'No build', count: 1 },
|
|
68
|
+
]);
|
|
69
|
+
(0, chai_1.expect)(b.devices).to.deep.equal([{ udid: selector_health_fixture_1.PHONE.shared, name: 'Shared Pixel', count: 4 }]);
|
|
70
|
+
(0, chai_1.expect)(b.recent).to.have.length(4);
|
|
71
|
+
(0, chai_1.expect)(b.recent[0]).to.include({
|
|
72
|
+
sessionId: 'sh-s-shared-1',
|
|
73
|
+
buildId: selector_health_fixture_1.BUILD.id,
|
|
74
|
+
device: 'Shared Pixel',
|
|
75
|
+
method: 'Visual AI',
|
|
76
|
+
healedSelector: selector_health_fixture_1.FIX.hot,
|
|
77
|
+
});
|
|
78
|
+
(0, chai_1.expect)(b.recent[3]).to.include({ sessionId: 'sh-s-shared-2', buildId: null, method: 'LLM' });
|
|
79
|
+
(0, chai_1.expect)(b.daily).to.have.length(31);
|
|
80
|
+
(0, chai_1.expect)(b.daily.reduce((s, d) => s + d.heals, 0)).to.equal(4);
|
|
81
|
+
(0, chai_1.expect)(b.state).to.include({ status: 'active', brokeAgain: 2 });
|
|
82
|
+
(0, chai_1.expect)(b.activity).to.deep.equal([]);
|
|
83
|
+
}));
|
|
84
|
+
it('shows who did what, newest first, naming nobody for a user who is gone', () => __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const b = (yield detail(selector_health_fixture_1.ADMIN, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.fixed })).body;
|
|
86
|
+
(0, chai_1.expect)(b.activity.map((a) => [a.action, a.by])).to.deep.equal([
|
|
87
|
+
['verified', null],
|
|
88
|
+
['marked_fixed', { id: selector_health_fixture_1.USER.gone, name: null }],
|
|
89
|
+
]);
|
|
90
|
+
(0, chai_1.expect)(b.state).to.deep.include({ status: 'resolved', fixedBy: { id: selector_health_fixture_1.USER.gone, name: null } });
|
|
91
|
+
}));
|
|
92
|
+
it("gives a member a muted selector's reason, and who muted it", () => __awaiter(this, void 0, void 0, function* () {
|
|
93
|
+
const b = (yield detail(selector_health_fixture_1.MEMBER_A, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.muted })).body;
|
|
94
|
+
(0, chai_1.expect)(b.activity).to.have.length(1);
|
|
95
|
+
(0, chai_1.expect)(b.activity[0]).to.deep.include({
|
|
96
|
+
action: 'muted',
|
|
97
|
+
by: { id: selector_health_fixture_1.USER.priya, name: 'Priya' },
|
|
98
|
+
reason: 'Screen being redesigned',
|
|
99
|
+
});
|
|
100
|
+
(0, chai_1.expect)(b.state).to.deep.include({
|
|
101
|
+
mutedBy: { id: selector_health_fixture_1.USER.priya, name: 'Priya' },
|
|
102
|
+
muteReason: 'Screen being redesigned',
|
|
103
|
+
});
|
|
104
|
+
}));
|
|
105
|
+
it("answers another team's selector exactly as one that doesn't exist", () => __awaiter(this, void 0, void 0, function* () {
|
|
106
|
+
const hidden = yield detail(selector_health_fixture_1.MEMBER_A, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.bOnly });
|
|
107
|
+
const missing = yield detail(selector_health_fixture_1.MEMBER_A, { strategy: 'xpath', selector: '//nothing/here' });
|
|
108
|
+
(0, chai_1.expect)(hidden.status).to.equal(404);
|
|
109
|
+
(0, chai_1.expect)(hidden.body).to.deep.equal({ error: 'not_found', message: 'Selector not found' });
|
|
110
|
+
(0, chai_1.expect)(missing.status).to.equal(404);
|
|
111
|
+
(0, chai_1.expect)(missing.body).to.deep.equal(hidden.body);
|
|
112
|
+
}));
|
|
113
|
+
it('opens a heal recorded with no strategy, and a selector full of punctuation', () => __awaiter(this, void 0, void 0, function* () {
|
|
114
|
+
(0, chai_1.expect)((yield detail(selector_health_fixture_1.MEMBER_A, { strategy: '', selector: selector_health_fixture_1.SEL.legacy })).body.heals).to.equal(1);
|
|
115
|
+
(0, chai_1.expect)((yield detail(selector_health_fixture_1.MEMBER_A, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.odd })).body.heals).to.equal(1);
|
|
116
|
+
}));
|
|
117
|
+
it("counts days in the caller's time zone", () => __awaiter(this, void 0, void 0, function* () {
|
|
118
|
+
const b = (yield detail(selector_health_fixture_1.ADMIN, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.hot, days: '7', tz: '330' }))
|
|
119
|
+
.body;
|
|
120
|
+
(0, chai_1.expect)(b.daily).to.have.length(8);
|
|
121
|
+
for (const d of b.daily)
|
|
122
|
+
(0, chai_1.expect)((d.t + 330 * 60000) % selector_health_fixture_1.DAY).to.equal(0);
|
|
123
|
+
}));
|
|
124
|
+
it('needs a selector', () => __awaiter(this, void 0, void 0, function* () {
|
|
125
|
+
(0, chai_1.expect)((yield detail(selector_health_fixture_1.ADMIN, {})).status).to.equal(400);
|
|
126
|
+
}));
|
|
127
|
+
it('says a read-only caller cannot act', () => __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
const b = (yield detail(selector_health_fixture_1.READ_ONLY_A, { strategy: 'xpath', selector: selector_health_fixture_1.SEL.hot })).body;
|
|
129
|
+
(0, chai_1.expect)(b.canAct).to.equal(false);
|
|
130
|
+
}));
|
|
131
|
+
});
|