@delacour/warden-e2e 0.5.0 → 0.7.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/{chunk-j9b17ade.mjs → chunk-empqr9n0.mjs} +1366 -55
- package/index.mjs +34 -4
- package/package.json +2 -2
- package/preload.mjs +4008 -335
- package/types/core/src/affected/affected.schema.d.ts +93 -0
- package/types/core/src/allocate.d.ts +2 -1
- package/types/core/src/android-keyguard.d.ts +34 -0
- package/types/core/src/batch/preset.schema.d.ts +1 -0
- package/types/core/src/biometrics.d.ts +15 -0
- package/types/core/src/builds/builds.defaults.d.ts +15 -0
- package/types/core/src/builds/config.d.ts +5 -1
- package/types/core/src/builds/config.schema.d.ts +182 -0
- package/types/core/src/builds/define-config.d.ts +104 -0
- package/types/core/src/builds/install-device.d.ts +37 -0
- package/types/core/src/builds/install.d.ts +17 -3
- package/types/core/src/builds/kinds.d.ts +21 -3
- package/types/core/src/builds/lock.d.ts +38 -0
- package/types/core/src/builds/resolve.d.ts +12 -2
- package/types/core/src/conditions/conditions.config.d.ts +117 -0
- package/types/core/src/conditions/conditions.d.ts +22 -0
- package/types/core/src/conditions/conditions.types.d.ts +141 -0
- package/types/core/src/conditions/inject-state.d.ts +13 -0
- package/types/core/src/conditions/launch-settings.d.ts +14 -0
- package/types/core/src/conditions/network.presets.d.ts +42 -0
- package/types/core/src/conditions/text-size.d.ts +19 -0
- package/types/core/src/golden/android-biometrics.d.ts +35 -0
- package/types/core/src/ios-runner/classify.d.ts +7 -0
- package/types/core/src/ios-runner/runner-build.types.d.ts +76 -0
- package/types/core/src/netproxy/control.d.ts +75 -0
- package/types/core/src/netproxy/decide.d.ts +22 -0
- package/types/core/src/netproxy/handle-file.d.ts +12 -0
- package/types/core/src/netproxy/lease-proxy.d.ts +53 -0
- package/types/core/src/netproxy/netproxy.main.d.ts +40 -0
- package/types/core/src/netproxy/pipe.d.ts +37 -0
- package/types/core/src/netproxy/proxy.d.ts +48 -0
- package/types/core/src/netproxy/shaping.d.ts +43 -0
- package/types/core/src/providers/android.d.ts +36 -1
- package/types/core/src/types.d.ts +47 -0
- package/types/e2e/src/alert-buttons.d.ts +9 -0
- package/types/e2e/src/app.d.ts +3 -1
- package/types/e2e/src/conditions/condition-events.d.ts +63 -0
- package/types/e2e/src/conditions/device-conditions.d.ts +170 -0
- package/types/e2e/src/conditions/geo.d.ts +15 -0
- package/types/e2e/src/conditions/gpx.d.ts +8 -0
- package/types/e2e/src/conditions/lease-recorder.d.ts +27 -0
- package/types/e2e/src/conditions/lease-store.d.ts +9 -0
- package/types/e2e/src/conditions/location-plan.d.ts +17 -0
- package/types/e2e/src/conditions/location-scenarios.data.d.ts +17 -0
- package/types/e2e/src/conditions/route-ticker.d.ts +53 -0
- package/types/e2e/src/conditions/scheduler.d.ts +83 -0
- package/types/e2e/src/device.d.ts +57 -3
- package/types/e2e/src/driver/android/android-app.d.ts +4 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +19 -0
- package/types/e2e/src/driver/android/android-clock.d.ts +28 -0
- package/types/e2e/src/driver/android/android-device.d.ts +8 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +17 -0
- package/types/e2e/src/driver/android/android-network.d.ts +66 -0
- package/types/e2e/src/driver/android/android-phone.d.ts +47 -0
- package/types/e2e/src/driver/android/android-settings.d.ts +34 -0
- package/types/e2e/src/driver/android/android-state.d.ts +10 -0
- package/types/e2e/src/driver/clock-recorder.d.ts +9 -0
- package/types/e2e/src/driver/device-settings.d.ts +10 -0
- package/types/e2e/src/driver/driver.types.d.ts +176 -1
- package/types/e2e/src/driver/ios/device/capabilities.d.ts +3 -0
- package/types/e2e/src/driver/ios/device/device-driver.d.ts +49 -0
- package/types/e2e/src/driver/ios/device/devicectl.d.ts +38 -0
- package/types/e2e/src/driver/ios/device/runner-protocol.d.ts +21 -0
- package/types/e2e/src/driver/ios/hid-recovery.d.ts +15 -0
- package/types/e2e/src/driver/ios/inject.d.ts +110 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +29 -0
- package/types/e2e/src/driver/ios/ios-network.d.ts +46 -0
- package/types/e2e/src/driver/ios/ios-settings.d.ts +44 -0
- package/types/e2e/src/driver/ios/simctl.d.ts +41 -1
- package/types/e2e/src/driver/ios/type-budget.d.ts +6 -1
- package/types/e2e/src/driver/network-proxy.d.ts +52 -0
- package/types/e2e/src/driver/open.d.ts +20 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +11 -0
- package/types/e2e/src/driver/requirements.d.ts +14 -0
- package/types/e2e/src/driver/typing.d.ts +1 -1
- package/types/e2e/src/env.d.ts +32 -0
- package/types/e2e/src/fixture-session.d.ts +4 -0
- package/types/e2e/src/fixtures.d.ts +6 -1
- package/types/e2e/src/index.d.ts +2 -0
- package/types/e2e/src/runner/scope.d.ts +1 -0
- package/types/e2e/src/runner/test.d.ts +6 -0
- package/types/e2e/src/screen/field.d.ts +5 -3
- package/types/e2e/src/screen/locator.d.ts +3 -2
- package/types/e2e/src/traced-device.d.ts +2 -1
|
@@ -943,7 +943,7 @@ async function focusAndClear(driver, sleep, point, entry, method = "auto") {
|
|
|
943
943
|
return prepared;
|
|
944
944
|
return prepared.data.pending === "selection" ? pressKeys(driver, sleep, "Backspace", 1) : ok(undefined);
|
|
945
945
|
}
|
|
946
|
-
async function focusClearType(driver, sleep, point, entry, text, method = "auto", env = process.env, via = "
|
|
946
|
+
async function focusClearType(driver, sleep, point, entry, text, method = "auto", env = process.env, via = "type") {
|
|
947
947
|
const wantsSetText = method === "setText" || method === "auto" && setTextOptedIn(env);
|
|
948
948
|
if (wantsSetText && entry !== undefined && fieldContent(entry).kind === "text" && canSetText(driver)) {
|
|
949
949
|
const focused = await driver.tap(point);
|
|
@@ -2107,7 +2107,7 @@ function describeUntil(until) {
|
|
|
2107
2107
|
}
|
|
2108
2108
|
function fillViaOf(locator, via) {
|
|
2109
2109
|
if (via === undefined || via === "paste" || via === "type")
|
|
2110
|
-
return via ?? "
|
|
2110
|
+
return via ?? "type";
|
|
2111
2111
|
throw new E2eError2("INVALID_ARGUMENT", `${locator}: fill via must be "paste" or "type", got ${JSON.stringify(via)}`);
|
|
2112
2112
|
}
|
|
2113
2113
|
function viaFor(attempt, via) {
|
|
@@ -3076,6 +3076,9 @@ function describeDevices(targets) {
|
|
|
3076
3076
|
}
|
|
3077
3077
|
return [...seen.values()].join(", ");
|
|
3078
3078
|
}
|
|
3079
|
+
function sameDeviceName(served, wanted) {
|
|
3080
|
+
return wanted !== undefined && served.replaceAll("+", " ") === wanted.replaceAll("+", " ");
|
|
3081
|
+
}
|
|
3079
3082
|
function normalizeWsUrl(wsUrl, metroUrl) {
|
|
3080
3083
|
const ws = new URL(wsUrl);
|
|
3081
3084
|
const metro = new URL(metroUrl);
|
|
@@ -3094,7 +3097,11 @@ function selectTarget(targets, query, metroUrl) {
|
|
|
3094
3097
|
(t) => t.reactNative?.logicalDeviceId === query.logicalDeviceId || urlDevice(t) === query.logicalDeviceId,
|
|
3095
3098
|
"logicalDeviceId"
|
|
3096
3099
|
],
|
|
3097
|
-
[
|
|
3100
|
+
[
|
|
3101
|
+
query.deviceName,
|
|
3102
|
+
(t) => t.deviceName !== undefined && sameDeviceName(t.deviceName, query.deviceName),
|
|
3103
|
+
"deviceName"
|
|
3104
|
+
]
|
|
3098
3105
|
];
|
|
3099
3106
|
for (const [value, matches, label] of wanted) {
|
|
3100
3107
|
if (!value)
|
|
@@ -4390,7 +4397,9 @@ var DRIVER_CODES = new Set([
|
|
|
4390
4397
|
"E_HID",
|
|
4391
4398
|
"E_IO",
|
|
4392
4399
|
"E_BAD_REQUEST",
|
|
4393
|
-
"E_INTERNAL"
|
|
4400
|
+
"E_INTERNAL",
|
|
4401
|
+
"E_DEVICE_LOCKED",
|
|
4402
|
+
"E_REFUSED"
|
|
4394
4403
|
]);
|
|
4395
4404
|
function isDriverCode(code) {
|
|
4396
4405
|
return DRIVER_CODES.has(code);
|
|
@@ -4914,6 +4923,22 @@ function isHidLost(error) {
|
|
|
4914
4923
|
const hidInvalidated = /\bE_HID\b/.test(error.message) && /dtuhidd connection[\s\S]*invalidated/.test(error.message);
|
|
4915
4924
|
return hidInvalidated || /\bE_NOT_BOOTED\b/.test(error.message);
|
|
4916
4925
|
}
|
|
4926
|
+
function isRunnerLost(error) {
|
|
4927
|
+
if (!(error instanceof E2eError2) || error.code !== "ACTION_FAILED" && error.code !== "DRIVER")
|
|
4928
|
+
return false;
|
|
4929
|
+
return /\bE_HELPER_CRASHED\b/.test(error.message);
|
|
4930
|
+
}
|
|
4931
|
+
function isRecoverableLoss(error, deviceKind) {
|
|
4932
|
+
return deviceKind === "physical" ? isRunnerLost(error) : isHidLost(error);
|
|
4933
|
+
}
|
|
4934
|
+
var RUNNER_RESTART_MS = 180000;
|
|
4935
|
+
async function restartRunnerHost(deps, argv, udid) {
|
|
4936
|
+
if (argv === undefined || argv.length === 0)
|
|
4937
|
+
return err("WARDEN_IOS_RUNNER_RESTART is not set");
|
|
4938
|
+
const cmd = argv.map((arg) => arg.replaceAll("{udid}", udid));
|
|
4939
|
+
const res = await deps.exec(cmd, { timeoutMs: RUNNER_RESTART_MS });
|
|
4940
|
+
return res.exitCode === 0 ? ok(undefined) : err(execError(cmd, res));
|
|
4941
|
+
}
|
|
4917
4942
|
async function rebootSim(deps, udid) {
|
|
4918
4943
|
const down = ["xcrun", "simctl", "shutdown", udid];
|
|
4919
4944
|
const shutdown = await deps.exec(down, { timeoutMs: SHUTDOWN_MS });
|
|
@@ -5042,8 +5067,274 @@ function traceDriver(driver, options) {
|
|
|
5042
5067
|
return wrap(driver, "driver.");
|
|
5043
5068
|
}
|
|
5044
5069
|
|
|
5045
|
-
// ../../packages/
|
|
5070
|
+
// ../../packages/core/src/conditions/conditions.config.ts
|
|
5046
5071
|
import { z as z7 } from "zod";
|
|
5072
|
+
|
|
5073
|
+
// ../../packages/core/src/duration.ts
|
|
5074
|
+
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
5075
|
+
function parseDuration(input) {
|
|
5076
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(input.trim());
|
|
5077
|
+
if (!match?.[1])
|
|
5078
|
+
return { success: false, error: `invalid duration "${input}" (use e.g. 30s, 10m, 2h)` };
|
|
5079
|
+
return { success: true, data: Math.round(Number(match[1]) * (UNIT_MS[match[2] ?? "s"] ?? 1000)) };
|
|
5080
|
+
}
|
|
5081
|
+
|
|
5082
|
+
// ../../packages/core/src/conditions/conditions.types.ts
|
|
5083
|
+
var PERMISSION_NAMES = [
|
|
5084
|
+
"location",
|
|
5085
|
+
"locationAlways",
|
|
5086
|
+
"camera",
|
|
5087
|
+
"photos",
|
|
5088
|
+
"photosAdd",
|
|
5089
|
+
"contacts",
|
|
5090
|
+
"contactsLimited",
|
|
5091
|
+
"notifications",
|
|
5092
|
+
"microphone",
|
|
5093
|
+
"calendar",
|
|
5094
|
+
"reminders",
|
|
5095
|
+
"motion",
|
|
5096
|
+
"mediaLibrary",
|
|
5097
|
+
"siri"
|
|
5098
|
+
];
|
|
5099
|
+
var TEXT_SIZE_NAMES = [
|
|
5100
|
+
"extra-small",
|
|
5101
|
+
"small",
|
|
5102
|
+
"medium",
|
|
5103
|
+
"large",
|
|
5104
|
+
"extra-large",
|
|
5105
|
+
"extra-extra-large",
|
|
5106
|
+
"extra-extra-extra-large",
|
|
5107
|
+
"accessibility-medium",
|
|
5108
|
+
"accessibility-large",
|
|
5109
|
+
"accessibility-extra-large",
|
|
5110
|
+
"accessibility-extra-extra-large",
|
|
5111
|
+
"accessibility-extra-extra-extra-large"
|
|
5112
|
+
];
|
|
5113
|
+
var CONDITION_AREAS = [
|
|
5114
|
+
"network",
|
|
5115
|
+
"location",
|
|
5116
|
+
"clock",
|
|
5117
|
+
"permission",
|
|
5118
|
+
"locale",
|
|
5119
|
+
"a11y",
|
|
5120
|
+
"battery"
|
|
5121
|
+
];
|
|
5122
|
+
|
|
5123
|
+
// ../../packages/core/src/conditions/network.presets.ts
|
|
5124
|
+
var NETWORK_PRESET_NAMES = ["edge", "3g", "4g", "lte", "wifi", "slow", "lossy"];
|
|
5125
|
+
var NETWORK_PRESETS = {
|
|
5126
|
+
edge: {
|
|
5127
|
+
emu: { speed: "200:240", delay: "175:225" },
|
|
5128
|
+
shaping: { latencyMs: 400, jitterMs: 25, downKbps: 240, upKbps: 200, lossPct: 0 }
|
|
5129
|
+
},
|
|
5130
|
+
"3g": {
|
|
5131
|
+
emu: { speed: "330:780", delay: "40:60" },
|
|
5132
|
+
shaping: { latencyMs: 100, jitterMs: 10, downKbps: 780, upKbps: 330, lossPct: 0 }
|
|
5133
|
+
},
|
|
5134
|
+
"4g": {
|
|
5135
|
+
emu: { speed: "9000:9000", delay: "70:100" },
|
|
5136
|
+
shaping: { latencyMs: 170, jitterMs: 15, downKbps: 9000, upKbps: 9000, lossPct: 0 }
|
|
5137
|
+
},
|
|
5138
|
+
lte: {
|
|
5139
|
+
emu: { speed: "10000:50000", delay: "20:30" },
|
|
5140
|
+
shaping: { latencyMs: 50, jitterMs: 5, downKbps: 50000, upKbps: 1e4, lossPct: 0 }
|
|
5141
|
+
},
|
|
5142
|
+
wifi: {
|
|
5143
|
+
emu: { speed: "33000:40000", delay: "0:1" },
|
|
5144
|
+
shaping: { latencyMs: 2, jitterMs: 0, downKbps: 40000, upKbps: 33000, lossPct: 0 }
|
|
5145
|
+
},
|
|
5146
|
+
slow: {
|
|
5147
|
+
emu: { speed: "400:400", delay: "900:1100" },
|
|
5148
|
+
shaping: { latencyMs: 2000, jitterMs: 100, downKbps: 400, upKbps: 400, lossPct: 0 }
|
|
5149
|
+
},
|
|
5150
|
+
lossy: {
|
|
5151
|
+
emu: { speed: "1000:1000", delay: "200:300" },
|
|
5152
|
+
shaping: { latencyMs: 500, jitterMs: 50, downKbps: 1000, upKbps: 1000, lossPct: 10 }
|
|
5153
|
+
}
|
|
5154
|
+
};
|
|
5155
|
+
var UNSHAPED = { latencyMs: 0, jitterMs: 0, downKbps: 0, upKbps: 0, lossPct: 0 };
|
|
5156
|
+
var UNSHAPED_EMU = { speed: "full", delay: "none" };
|
|
5157
|
+
var nonNegative = (value) => value === undefined || !Number.isFinite(value) ? 0 : Math.max(0, value);
|
|
5158
|
+
function customEmu(shaping) {
|
|
5159
|
+
const { downKbps, upKbps, latencyMs, jitterMs } = shaping;
|
|
5160
|
+
const speed = downKbps === 0 && upKbps === 0 ? UNSHAPED_EMU.speed : `${upKbps || downKbps}:${downKbps || upKbps}`;
|
|
5161
|
+
const half = latencyMs / 2;
|
|
5162
|
+
const min = Math.max(0, Math.round(half - jitterMs));
|
|
5163
|
+
const max = Math.round(half + jitterMs);
|
|
5164
|
+
const delay = max === 0 ? UNSHAPED_EMU.delay : `${min}:${max}`;
|
|
5165
|
+
return { speed, delay };
|
|
5166
|
+
}
|
|
5167
|
+
function resolveNetwork(condition) {
|
|
5168
|
+
switch (condition.kind) {
|
|
5169
|
+
case "offline":
|
|
5170
|
+
return { online: false };
|
|
5171
|
+
case "online":
|
|
5172
|
+
return { online: true, shaping: UNSHAPED, emu: UNSHAPED_EMU };
|
|
5173
|
+
case "preset":
|
|
5174
|
+
return { online: true, ...NETWORK_PRESETS[condition.preset] };
|
|
5175
|
+
case "flap":
|
|
5176
|
+
return resolveNetwork(condition.base ?? { kind: "online" });
|
|
5177
|
+
case "custom": {
|
|
5178
|
+
const shaping = {
|
|
5179
|
+
latencyMs: nonNegative(condition.latencyMs),
|
|
5180
|
+
jitterMs: nonNegative(condition.jitterMs),
|
|
5181
|
+
downKbps: nonNegative(condition.downKbps),
|
|
5182
|
+
upKbps: nonNegative(condition.upKbps),
|
|
5183
|
+
lossPct: Math.min(100, nonNegative(condition.lossPct))
|
|
5184
|
+
};
|
|
5185
|
+
return { online: true, shaping, emu: customEmu(shaping) };
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
// ../../packages/core/src/conditions/text-size.ts
|
|
5191
|
+
var TEXT_SIZE_SCALE = {
|
|
5192
|
+
"extra-small": 0.82,
|
|
5193
|
+
small: 0.88,
|
|
5194
|
+
medium: 0.94,
|
|
5195
|
+
large: 1,
|
|
5196
|
+
"extra-large": 1.12,
|
|
5197
|
+
"extra-extra-large": 1.24,
|
|
5198
|
+
"extra-extra-extra-large": 1.35,
|
|
5199
|
+
"accessibility-medium": 1.6,
|
|
5200
|
+
"accessibility-large": 1.9,
|
|
5201
|
+
"accessibility-extra-large": 2.35,
|
|
5202
|
+
"accessibility-extra-extra-large": 2.75,
|
|
5203
|
+
"accessibility-extra-extra-extra-large": 3.1
|
|
5204
|
+
};
|
|
5205
|
+
var ANDROID_MAX_FONT_SCALE = 2;
|
|
5206
|
+
var ANDROID_MIN_FONT_SCALE = 0.5;
|
|
5207
|
+
var isName = (v) => TEXT_SIZE_NAMES.includes(v);
|
|
5208
|
+
function resolveTextSize(size) {
|
|
5209
|
+
if (typeof size === "number") {
|
|
5210
|
+
if (!Number.isFinite(size) || size <= 0)
|
|
5211
|
+
return err(`text size must be a positive scale, got ${String(size)}`);
|
|
5212
|
+
let best = TEXT_SIZE_NAMES[0];
|
|
5213
|
+
for (const name of TEXT_SIZE_NAMES) {
|
|
5214
|
+
if (Math.abs(TEXT_SIZE_SCALE[name] - size) < Math.abs(TEXT_SIZE_SCALE[best] - size))
|
|
5215
|
+
best = name;
|
|
5216
|
+
}
|
|
5217
|
+
return ok({ category: best, scale: size });
|
|
5218
|
+
}
|
|
5219
|
+
if (!isName(size))
|
|
5220
|
+
return err(`unknown text size "${String(size)}" (use ${TEXT_SIZE_NAMES.join(", ")} or a scale like 1.3)`);
|
|
5221
|
+
return ok({ category: size, scale: TEXT_SIZE_SCALE[size] });
|
|
5222
|
+
}
|
|
5223
|
+
var androidFontScale = (scale) => Math.min(ANDROID_MAX_FONT_SCALE, Math.max(ANDROID_MIN_FONT_SCALE, Math.round(scale * 100) / 100));
|
|
5224
|
+
function appliedA11y(op, applied) {
|
|
5225
|
+
return Object.fromEntries(Object.entries(op).filter(([key]) => applied.includes(key)));
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
// ../../packages/core/src/conditions/conditions.config.ts
|
|
5229
|
+
var NETWORK_WORDS = ["online", "offline", ...NETWORK_PRESET_NAMES];
|
|
5230
|
+
var nonNegative2 = z7.number().nonnegative().finite();
|
|
5231
|
+
var customNetworkSchema = z7.object({
|
|
5232
|
+
latencyMs: nonNegative2.optional(),
|
|
5233
|
+
jitterMs: nonNegative2.optional(),
|
|
5234
|
+
downKbps: nonNegative2.optional(),
|
|
5235
|
+
upKbps: nonNegative2.optional(),
|
|
5236
|
+
lossPct: z7.number().min(0).max(100).optional()
|
|
5237
|
+
}).strict();
|
|
5238
|
+
var isoDate = z7.string().refine((v) => !Number.isNaN(Date.parse(v)), { message: "invalid date (use ISO 8601)" });
|
|
5239
|
+
var offsetDuration = z7.string().refine((v) => parseDuration(v).success, { message: "invalid duration (use e.g. 2d)" });
|
|
5240
|
+
var textSize = z7.union([z7.enum(TEXT_SIZE_NAMES), z7.number().positive().finite()]);
|
|
5241
|
+
var a11yConfigSchema = z7.object({
|
|
5242
|
+
textSize: textSize.optional(),
|
|
5243
|
+
boldText: z7.boolean().optional(),
|
|
5244
|
+
contrast: z7.enum(["increased", "normal"]).optional(),
|
|
5245
|
+
reduceMotion: z7.boolean().optional()
|
|
5246
|
+
}).strict();
|
|
5247
|
+
var conditionsConfigSchema = z7.object({
|
|
5248
|
+
network: z7.union([z7.enum(NETWORK_WORDS), customNetworkSchema]).optional(),
|
|
5249
|
+
location: z7.union([
|
|
5250
|
+
z7.enum(["city-run", "city-bike", "freeway"]),
|
|
5251
|
+
z7.object({
|
|
5252
|
+
lat: z7.number().min(-90).max(90),
|
|
5253
|
+
lon: z7.number().min(-180).max(180),
|
|
5254
|
+
altM: z7.number().finite().optional()
|
|
5255
|
+
}).strict()
|
|
5256
|
+
]).optional(),
|
|
5257
|
+
clock: z7.union([
|
|
5258
|
+
isoDate,
|
|
5259
|
+
z7.object({ at: isoDate, frozen: z7.boolean().optional() }).strict(),
|
|
5260
|
+
z7.object({ offset: offsetDuration }).strict()
|
|
5261
|
+
]).optional(),
|
|
5262
|
+
permissions: z7.partialRecord(z7.enum(PERMISSION_NAMES), z7.enum(["granted", "denied", "unset"])).optional(),
|
|
5263
|
+
locale: z7.string().min(1).optional(),
|
|
5264
|
+
timezone: z7.string().min(1).optional(),
|
|
5265
|
+
a11y: a11yConfigSchema.optional()
|
|
5266
|
+
}).strict();
|
|
5267
|
+
var isWord = (v) => NETWORK_WORDS.includes(v);
|
|
5268
|
+
function toNetworkCondition(input) {
|
|
5269
|
+
if (typeof input === "string") {
|
|
5270
|
+
if (!isWord(input))
|
|
5271
|
+
return err(`unknown network "${input}" (use ${NETWORK_WORDS.join(", ")})`);
|
|
5272
|
+
if (input === "online" || input === "offline")
|
|
5273
|
+
return ok({ kind: input });
|
|
5274
|
+
return ok({ kind: "preset", preset: input });
|
|
5275
|
+
}
|
|
5276
|
+
if ("kind" in input)
|
|
5277
|
+
return ok(input);
|
|
5278
|
+
return ok({ kind: "custom", ...input });
|
|
5279
|
+
}
|
|
5280
|
+
function toLocation(input) {
|
|
5281
|
+
if (typeof input === "string")
|
|
5282
|
+
return { kind: "scenario", name: input };
|
|
5283
|
+
return { kind: "point", lat: input.lat, lon: input.lon, ...input.altM === undefined ? {} : { altM: input.altM } };
|
|
5284
|
+
}
|
|
5285
|
+
function toClock(input) {
|
|
5286
|
+
if (typeof input === "string")
|
|
5287
|
+
return { kind: "fixed", at: new Date(input).toISOString() };
|
|
5288
|
+
if ("offset" in input) {
|
|
5289
|
+
const ms = parseDuration(input.offset);
|
|
5290
|
+
return { kind: "offset", ms: ms.success ? ms.data : 0 };
|
|
5291
|
+
}
|
|
5292
|
+
const at = new Date(input.at).toISOString();
|
|
5293
|
+
return input.frozen === true ? { kind: "frozen", at } : { kind: "fixed", at };
|
|
5294
|
+
}
|
|
5295
|
+
function normalizeConditions(config) {
|
|
5296
|
+
const conditions = {};
|
|
5297
|
+
if (config.network !== undefined) {
|
|
5298
|
+
const network = toNetworkCondition(config.network);
|
|
5299
|
+
if (!network.success)
|
|
5300
|
+
return network;
|
|
5301
|
+
conditions.network = network.data;
|
|
5302
|
+
}
|
|
5303
|
+
if (config.location !== undefined)
|
|
5304
|
+
conditions.location = toLocation(config.location);
|
|
5305
|
+
if (config.clock !== undefined)
|
|
5306
|
+
conditions.clock = toClock(config.clock);
|
|
5307
|
+
if (config.permissions !== undefined)
|
|
5308
|
+
conditions.permissions = config.permissions;
|
|
5309
|
+
if (config.locale !== undefined)
|
|
5310
|
+
conditions.locale = config.locale;
|
|
5311
|
+
if (config.timezone !== undefined)
|
|
5312
|
+
conditions.timezone = config.timezone;
|
|
5313
|
+
if (config.a11y !== undefined) {
|
|
5314
|
+
const a11y = { ...config.a11y };
|
|
5315
|
+
if (config.a11y.textSize !== undefined) {
|
|
5316
|
+
const size = resolveTextSize(config.a11y.textSize);
|
|
5317
|
+
if (!size.success)
|
|
5318
|
+
return size;
|
|
5319
|
+
a11y.fontScale = size.data.scale;
|
|
5320
|
+
}
|
|
5321
|
+
conditions.a11y = a11y;
|
|
5322
|
+
}
|
|
5323
|
+
return ok({ conditions });
|
|
5324
|
+
}
|
|
5325
|
+
function parseConditionsEnv(raw) {
|
|
5326
|
+
let json;
|
|
5327
|
+
try {
|
|
5328
|
+
json = JSON.parse(raw);
|
|
5329
|
+
} catch {
|
|
5330
|
+
return err("must be JSON");
|
|
5331
|
+
}
|
|
5332
|
+
const parsed = conditionsConfigSchema.safeParse(json);
|
|
5333
|
+
return parsed.success ? ok(parsed.data) : err(parsed.error.issues.map((i) => i.message).join("; "));
|
|
5334
|
+
}
|
|
5335
|
+
|
|
5336
|
+
// ../../packages/e2e/src/env.ts
|
|
5337
|
+
import { z as z8 } from "zod";
|
|
5047
5338
|
var ENV_NAMES = {
|
|
5048
5339
|
udid: "WARDEN_UDID",
|
|
5049
5340
|
deviceName: "WARDEN_DEVICE_NAME",
|
|
@@ -5060,57 +5351,78 @@ var ENV_NAMES = {
|
|
|
5060
5351
|
helperDir: "WARDEN_HELPER_DIR",
|
|
5061
5352
|
simd: "WARDEN_SIMD",
|
|
5062
5353
|
simdSocket: "WARDEN_SIMD_SOCKET",
|
|
5354
|
+
iosRunnerSocket: "WARDEN_IOS_RUNNER_SOCKET",
|
|
5063
5355
|
logTimestamps: "WARDEN_LOG_TIMESTAMPS",
|
|
5356
|
+
deviceKind: "WARDEN_DEVICE_KIND",
|
|
5357
|
+
iosRunnerRestart: "WARDEN_IOS_RUNNER_RESTART",
|
|
5064
5358
|
afterReboot: "WARDEN_AFTER_REBOOT",
|
|
5065
5359
|
version: "WARDEN_VERSION",
|
|
5066
5360
|
bin: "WARDEN_BIN",
|
|
5067
5361
|
perf: "WARDEN_PERF",
|
|
5068
|
-
perfBudgets: "WARDEN_PERF_BUDGETS"
|
|
5362
|
+
perfBudgets: "WARDEN_PERF_BUDGETS",
|
|
5363
|
+
conditions: "WARDEN_CONDITIONS",
|
|
5364
|
+
leaseId: "WARDEN_LEASE_ID",
|
|
5365
|
+
netproxyCommand: "WARDEN_NETPROXY_COMMAND",
|
|
5366
|
+
biometrics: "WARDEN_BIOMETRICS"
|
|
5069
5367
|
};
|
|
5070
5368
|
var DEFAULT_TIMEOUT_MS4 = 120000;
|
|
5071
5369
|
var DEFAULT_ACTION_TIMEOUT_MS = 30000;
|
|
5072
5370
|
var DEFAULT_ASSERTION_TIMEOUT_MS = 5000;
|
|
5073
|
-
var toggle =
|
|
5074
|
-
var millis =
|
|
5075
|
-
var launchArgs =
|
|
5371
|
+
var toggle = z8.enum(["1", "true", "0", "false"]).transform((v) => v === "1" || v === "true");
|
|
5372
|
+
var millis = z8.string().regex(/^\d+$/, "must be a whole number of milliseconds").transform(Number).pipe(z8.number().int().positive());
|
|
5373
|
+
var launchArgs = z8.string().transform((raw, ctx) => {
|
|
5076
5374
|
try {
|
|
5077
|
-
const parsed =
|
|
5375
|
+
const parsed = z8.array(z8.string()).safeParse(JSON.parse(raw));
|
|
5078
5376
|
if (parsed.success)
|
|
5079
5377
|
return parsed.data;
|
|
5080
5378
|
} catch {}
|
|
5081
5379
|
ctx.addIssue({ code: "custom", message: "must be a JSON array of strings" });
|
|
5082
|
-
return
|
|
5380
|
+
return z8.NEVER;
|
|
5381
|
+
});
|
|
5382
|
+
var conditions = z8.string().transform((raw, ctx) => {
|
|
5383
|
+
const parsed = parseConditionsEnv(raw);
|
|
5384
|
+
if (parsed.success)
|
|
5385
|
+
return parsed.data;
|
|
5386
|
+
ctx.addIssue({ code: "custom", message: parsed.error });
|
|
5387
|
+
return z8.NEVER;
|
|
5083
5388
|
});
|
|
5084
5389
|
var REQUIRED = "required when WARDEN_UDID is set";
|
|
5085
|
-
var schema =
|
|
5086
|
-
udid:
|
|
5087
|
-
deviceName:
|
|
5088
|
-
platform:
|
|
5089
|
-
bundleId:
|
|
5090
|
-
appPath:
|
|
5091
|
-
metroUrl:
|
|
5390
|
+
var schema = z8.object({
|
|
5391
|
+
udid: z8.string().min(1),
|
|
5392
|
+
deviceName: z8.string().optional(),
|
|
5393
|
+
platform: z8.enum(["ios", "android"]).default("ios"),
|
|
5394
|
+
bundleId: z8.string({ error: REQUIRED }).min(1, REQUIRED),
|
|
5395
|
+
appPath: z8.string().optional(),
|
|
5396
|
+
metroUrl: z8.string().optional(),
|
|
5092
5397
|
launchArgs: launchArgs.default([]),
|
|
5093
|
-
out:
|
|
5398
|
+
out: z8.string().optional(),
|
|
5094
5399
|
timeout: millis.default(DEFAULT_TIMEOUT_MS4),
|
|
5095
5400
|
actionTimeout: millis.default(DEFAULT_ACTION_TIMEOUT_MS),
|
|
5096
5401
|
assertionTimeout: millis.default(DEFAULT_ASSERTION_TIMEOUT_MS),
|
|
5097
|
-
specsDir:
|
|
5098
|
-
helperDir:
|
|
5099
|
-
simd:
|
|
5100
|
-
simdSocket:
|
|
5402
|
+
specsDir: z8.string().optional(),
|
|
5403
|
+
helperDir: z8.string().optional(),
|
|
5404
|
+
simd: z8.string().optional(),
|
|
5405
|
+
simdSocket: z8.string().optional(),
|
|
5406
|
+
iosRunnerSocket: z8.string().optional(),
|
|
5101
5407
|
logTimestamps: toggle.default(false),
|
|
5408
|
+
deviceKind: z8.enum(["physical"]).optional(),
|
|
5409
|
+
iosRunnerRestart: launchArgs.optional(),
|
|
5102
5410
|
afterReboot: launchArgs.optional(),
|
|
5103
|
-
version:
|
|
5104
|
-
bin:
|
|
5105
|
-
perf:
|
|
5106
|
-
perfBudgets:
|
|
5411
|
+
version: z8.string().optional(),
|
|
5412
|
+
bin: z8.string().optional(),
|
|
5413
|
+
perf: z8.string().optional(),
|
|
5414
|
+
perfBudgets: z8.string().optional(),
|
|
5415
|
+
conditions: conditions.optional(),
|
|
5416
|
+
leaseId: z8.string().min(1).optional(),
|
|
5417
|
+
netproxyCommand: launchArgs.pipe(z8.array(z8.string()).min(1)).optional(),
|
|
5418
|
+
biometrics: toggle.optional()
|
|
5107
5419
|
});
|
|
5108
5420
|
function defined2(values) {
|
|
5109
5421
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
5110
5422
|
}
|
|
5111
5423
|
function parseArgv(raw) {
|
|
5112
5424
|
try {
|
|
5113
|
-
const parsed =
|
|
5425
|
+
const parsed = z8.array(z8.string()).min(1).safeParse(JSON.parse(raw));
|
|
5114
5426
|
if (parsed.success)
|
|
5115
5427
|
return parsed.data;
|
|
5116
5428
|
} catch {}
|
|
@@ -5135,9 +5447,16 @@ function toEnv(v, now) {
|
|
|
5135
5447
|
helperDir: v.helperDir,
|
|
5136
5448
|
simd: v.simd,
|
|
5137
5449
|
simdSocket: v.simdSocket,
|
|
5450
|
+
iosRunnerSocket: v.iosRunnerSocket,
|
|
5138
5451
|
wardenVersion: v.version,
|
|
5139
5452
|
perfBudgets: v.perfBudgets
|
|
5140
5453
|
}),
|
|
5454
|
+
...v.deviceKind === undefined ? {} : { deviceKind: v.deviceKind },
|
|
5455
|
+
...v.iosRunnerRestart === undefined ? {} : { iosRunnerRestart: v.iosRunnerRestart },
|
|
5456
|
+
...v.conditions === undefined ? {} : { conditions: v.conditions },
|
|
5457
|
+
...v.leaseId === undefined ? {} : { leaseId: v.leaseId },
|
|
5458
|
+
...v.netproxyCommand === undefined ? {} : { netproxyCommand: v.netproxyCommand },
|
|
5459
|
+
...v.biometrics === true ? { biometrics: true } : {},
|
|
5141
5460
|
...v.afterReboot === undefined ? {} : { afterReboot: v.afterReboot },
|
|
5142
5461
|
...v.bin === undefined ? {} : { wardenBin: parseArgv(v.bin) },
|
|
5143
5462
|
...v.perf === undefined ? {} : {
|
|
@@ -5170,25 +5489,25 @@ function readEnv(env, opts = {}) {
|
|
|
5170
5489
|
// ../../packages/e2e/src/runner/results.ts
|
|
5171
5490
|
import { appendFileSync, mkdirSync } from "fs";
|
|
5172
5491
|
import { dirname as dirname2 } from "path";
|
|
5173
|
-
import { z as
|
|
5174
|
-
var testStatusSchema =
|
|
5175
|
-
var testResultSchema =
|
|
5176
|
-
file:
|
|
5177
|
-
title:
|
|
5492
|
+
import { z as z9 } from "zod";
|
|
5493
|
+
var testStatusSchema = z9.enum(["passed", "failed", "skipped", "timed-out"]);
|
|
5494
|
+
var testResultSchema = z9.object({
|
|
5495
|
+
file: z9.string(),
|
|
5496
|
+
title: z9.array(z9.string()),
|
|
5178
5497
|
status: testStatusSchema,
|
|
5179
|
-
durationMs:
|
|
5180
|
-
startedAt:
|
|
5181
|
-
only:
|
|
5182
|
-
error:
|
|
5183
|
-
message:
|
|
5184
|
-
code:
|
|
5185
|
-
stack:
|
|
5498
|
+
durationMs: z9.number(),
|
|
5499
|
+
startedAt: z9.number(),
|
|
5500
|
+
only: z9.boolean().optional(),
|
|
5501
|
+
error: z9.object({
|
|
5502
|
+
message: z9.string(),
|
|
5503
|
+
code: z9.string().optional(),
|
|
5504
|
+
stack: z9.string().optional()
|
|
5186
5505
|
}).optional(),
|
|
5187
|
-
artifacts:
|
|
5188
|
-
device:
|
|
5189
|
-
udid:
|
|
5190
|
-
platform:
|
|
5191
|
-
name:
|
|
5506
|
+
artifacts: z9.object({ dir: z9.string(), files: z9.array(z9.string()) }).optional(),
|
|
5507
|
+
device: z9.object({
|
|
5508
|
+
udid: z9.string(),
|
|
5509
|
+
platform: z9.enum(["ios", "android"]),
|
|
5510
|
+
name: z9.string().optional()
|
|
5192
5511
|
})
|
|
5193
5512
|
});
|
|
5194
5513
|
function openResults(path) {
|
|
@@ -5260,6 +5579,8 @@ async function runStep(attempt, kind, label, fn, now = Date.now) {
|
|
|
5260
5579
|
}
|
|
5261
5580
|
|
|
5262
5581
|
// ../../packages/e2e/src/session.ts
|
|
5582
|
+
var ENROLL_TRIES = 5;
|
|
5583
|
+
var ENROLL_RETRY_MS = 500;
|
|
5263
5584
|
function devtoolsTarget(env) {
|
|
5264
5585
|
return { appId: env.bundleId, ...env.deviceName === undefined ? {} : { deviceName: env.deviceName } };
|
|
5265
5586
|
}
|
|
@@ -5366,10 +5687,27 @@ function createSession(opts = {}) {
|
|
|
5366
5687
|
await sleep(WARM_INTERVAL_MS);
|
|
5367
5688
|
}
|
|
5368
5689
|
}
|
|
5690
|
+
async function recoverRunner(deps, name) {
|
|
5691
|
+
if (hidRecovered) {
|
|
5692
|
+
note(`iPhone ${name}: the runner connection was lost again after a restart; check the phone and retry`);
|
|
5693
|
+
return false;
|
|
5694
|
+
}
|
|
5695
|
+
hidRecovered = true;
|
|
5696
|
+
await resetDevice();
|
|
5697
|
+
const restarted = await restartRunnerHost(deps, resolved.iosRunnerRestart, resolved.udid);
|
|
5698
|
+
if (!restarted.success) {
|
|
5699
|
+
note(`iPhone ${name}: runner connection lost, restart failed: ${restarted.error}`);
|
|
5700
|
+
return false;
|
|
5701
|
+
}
|
|
5702
|
+
note(`iPhone ${name}: runner connection lost, runner host restarted; running the test again`);
|
|
5703
|
+
return true;
|
|
5704
|
+
}
|
|
5369
5705
|
async function recoverHid() {
|
|
5370
5706
|
if (hid === undefined || closing !== undefined)
|
|
5371
5707
|
return false;
|
|
5372
5708
|
const name = resolved.deviceName ?? resolved.udid;
|
|
5709
|
+
if (resolved.deviceKind === "physical")
|
|
5710
|
+
return recoverRunner(hid, name);
|
|
5373
5711
|
if (hidRecovered) {
|
|
5374
5712
|
note(`sim ${name}: HID connection lost again after a reboot; \`warden release <id> --shutdown\` and retry`);
|
|
5375
5713
|
return false;
|
|
@@ -5391,6 +5729,24 @@ function createSession(opts = {}) {
|
|
|
5391
5729
|
note(`sim ${name}: HID connection lost (Device Hub quit?), rebooted; running the test again`);
|
|
5392
5730
|
return true;
|
|
5393
5731
|
}
|
|
5732
|
+
async function reenroll(driver) {
|
|
5733
|
+
if (resolved.biometrics !== true || resolved.platform !== "ios" || resolved.deviceKind === "physical")
|
|
5734
|
+
return driver;
|
|
5735
|
+
const sleep = hid?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
5736
|
+
for (let attempt = 1;attempt <= ENROLL_TRIES; attempt += 1) {
|
|
5737
|
+
const state = await driver.biometrics("enroll");
|
|
5738
|
+
if (!state.success) {
|
|
5739
|
+
note(`biometrics enroll failed: ${state.error.code}: ${state.error.message}`);
|
|
5740
|
+
return driver;
|
|
5741
|
+
}
|
|
5742
|
+
if (state.data.enrolled)
|
|
5743
|
+
return driver;
|
|
5744
|
+
if (attempt < ENROLL_TRIES)
|
|
5745
|
+
await sleep(ENROLL_RETRY_MS);
|
|
5746
|
+
}
|
|
5747
|
+
note(`biometrics enroll did not take after ${ENROLL_TRIES} attempts`);
|
|
5748
|
+
return driver;
|
|
5749
|
+
}
|
|
5394
5750
|
const session = {
|
|
5395
5751
|
env: resolved,
|
|
5396
5752
|
out,
|
|
@@ -5423,7 +5779,7 @@ function createSession(opts = {}) {
|
|
|
5423
5779
|
throw new E2eError2("DRIVER", `${code}: ${message}`);
|
|
5424
5780
|
}
|
|
5425
5781
|
current = cacheObservations(traceDriver(result.data, { trace: () => attempt?.trace }));
|
|
5426
|
-
return current;
|
|
5782
|
+
return reenroll(current);
|
|
5427
5783
|
}, (cause) => {
|
|
5428
5784
|
opened = undefined;
|
|
5429
5785
|
throw new E2eError2("DRIVER", cause instanceof Error ? cause.message : String(cause));
|
|
@@ -5579,8 +5935,962 @@ function getSession(opts = {}) {
|
|
|
5579
5935
|
return session;
|
|
5580
5936
|
}
|
|
5581
5937
|
|
|
5938
|
+
// ../../packages/e2e/src/driver/ios/device/capabilities.ts
|
|
5939
|
+
var IOS_DEVICE_CAPABILITIES = {
|
|
5940
|
+
pinch: false,
|
|
5941
|
+
maxPointers: 0,
|
|
5942
|
+
unicodeText: true,
|
|
5943
|
+
nativeSettle: false,
|
|
5944
|
+
alerts: true,
|
|
5945
|
+
reachability: true,
|
|
5946
|
+
cheapHitTest: false
|
|
5947
|
+
};
|
|
5948
|
+
|
|
5949
|
+
// ../../packages/e2e/src/conditions/device-conditions.ts
|
|
5950
|
+
import { resolve as resolve3 } from "path";
|
|
5951
|
+
|
|
5952
|
+
// ../../packages/e2e/src/driver/unwrap.ts
|
|
5953
|
+
var TIMEOUTS = new Set(["E_TIMEOUT", "E_APP_NOT_RESPONDING"]);
|
|
5954
|
+
var INFRASTRUCTURE = new Set([
|
|
5955
|
+
"E_HELPER_MISSING",
|
|
5956
|
+
"E_HELPER_CRASHED",
|
|
5957
|
+
"E_NOT_BOOTED",
|
|
5958
|
+
"E_UNKNOWN_DEVICE",
|
|
5959
|
+
"E_AX_UNAVAILABLE",
|
|
5960
|
+
"E_APP_UNAVAILABLE",
|
|
5961
|
+
"E_INTERNAL",
|
|
5962
|
+
"E_DEVICE_LOCKED"
|
|
5963
|
+
]);
|
|
5964
|
+
function codeFor(code) {
|
|
5965
|
+
if (TIMEOUTS.has(code))
|
|
5966
|
+
return "TIMEOUT";
|
|
5967
|
+
if (INFRASTRUCTURE.has(code))
|
|
5968
|
+
return "DRIVER";
|
|
5969
|
+
return "ACTION_FAILED";
|
|
5970
|
+
}
|
|
5971
|
+
function toE2eError(error, action) {
|
|
5972
|
+
return new E2eError2(codeFor(error.code), `${action} failed: ${error.code}: ${error.message}`);
|
|
5973
|
+
}
|
|
5974
|
+
function aborted2(action, signal) {
|
|
5975
|
+
const reason = signal.reason;
|
|
5976
|
+
const detail = reason instanceof Error ? `: ${reason.message}` : "";
|
|
5977
|
+
return new E2eError2("TIMEOUT", `${action} aborted (test deadline reached or cancelled)${detail}`);
|
|
5978
|
+
}
|
|
5979
|
+
async function settled(call, action, signal) {
|
|
5980
|
+
if (signal === undefined)
|
|
5981
|
+
return call();
|
|
5982
|
+
if (signal.aborted)
|
|
5983
|
+
throw aborted2(action, signal);
|
|
5984
|
+
let onAbort;
|
|
5985
|
+
const abort = new Promise((_, reject) => {
|
|
5986
|
+
onAbort = () => reject(aborted2(action, signal));
|
|
5987
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5988
|
+
});
|
|
5989
|
+
try {
|
|
5990
|
+
return await Promise.race([call(), abort]);
|
|
5991
|
+
} finally {
|
|
5992
|
+
if (onAbort !== undefined)
|
|
5993
|
+
signal.removeEventListener("abort", onAbort);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
async function unwrap(call, action, signal) {
|
|
5997
|
+
const result = await settled(call, action, signal);
|
|
5998
|
+
if (!result.success)
|
|
5999
|
+
throw toE2eError(result.error, action);
|
|
6000
|
+
return result.data;
|
|
6001
|
+
}
|
|
6002
|
+
|
|
6003
|
+
// ../../packages/e2e/src/conditions/condition-events.ts
|
|
6004
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
6005
|
+
import { dirname as dirname3 } from "path";
|
|
6006
|
+
function openConditionSink(path) {
|
|
6007
|
+
let ready = false;
|
|
6008
|
+
return (event) => {
|
|
6009
|
+
if (!ready) {
|
|
6010
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
6011
|
+
ready = true;
|
|
6012
|
+
}
|
|
6013
|
+
appendFileSync2(path, `${JSON.stringify(event)}
|
|
6014
|
+
`);
|
|
6015
|
+
};
|
|
6016
|
+
}
|
|
6017
|
+
var KNOWN_AREAS = new Set([
|
|
6018
|
+
"network",
|
|
6019
|
+
"location",
|
|
6020
|
+
"clock",
|
|
6021
|
+
"faults",
|
|
6022
|
+
"permission",
|
|
6023
|
+
"locale",
|
|
6024
|
+
"a11y",
|
|
6025
|
+
"battery"
|
|
6026
|
+
]);
|
|
6027
|
+
|
|
6028
|
+
// ../../packages/e2e/src/conditions/geo.ts
|
|
6029
|
+
var EARTH_RADIUS_M = 6371008.8;
|
|
6030
|
+
var rad = (deg) => deg * Math.PI / 180;
|
|
6031
|
+
var deg = (r) => r * 180 / Math.PI;
|
|
6032
|
+
function haversineM(a, b) {
|
|
6033
|
+
const dLat = rad(b.lat - a.lat);
|
|
6034
|
+
const dLon = rad(b.lon - a.lon);
|
|
6035
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLon / 2) ** 2;
|
|
6036
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
6037
|
+
}
|
|
6038
|
+
function interpolate(a, b, f) {
|
|
6039
|
+
if (f <= 0)
|
|
6040
|
+
return a;
|
|
6041
|
+
if (f >= 1)
|
|
6042
|
+
return b;
|
|
6043
|
+
const delta = haversineM(a, b) / EARTH_RADIUS_M;
|
|
6044
|
+
if (delta < 0.000000000001)
|
|
6045
|
+
return a;
|
|
6046
|
+
const sinDelta = Math.sin(delta);
|
|
6047
|
+
const wa = Math.sin((1 - f) * delta) / sinDelta;
|
|
6048
|
+
const wb = Math.sin(f * delta) / sinDelta;
|
|
6049
|
+
const lat1 = rad(a.lat);
|
|
6050
|
+
const lon1 = rad(a.lon);
|
|
6051
|
+
const lat2 = rad(b.lat);
|
|
6052
|
+
const lon2 = rad(b.lon);
|
|
6053
|
+
const x = wa * Math.cos(lat1) * Math.cos(lon1) + wb * Math.cos(lat2) * Math.cos(lon2);
|
|
6054
|
+
const y = wa * Math.cos(lat1) * Math.sin(lon1) + wb * Math.cos(lat2) * Math.sin(lon2);
|
|
6055
|
+
const z = wa * Math.sin(lat1) + wb * Math.sin(lat2);
|
|
6056
|
+
return { lat: deg(Math.atan2(z, Math.hypot(x, y))), lon: deg(Math.atan2(y, x)) };
|
|
6057
|
+
}
|
|
6058
|
+
function routeSteps(points, speedMps, intervalS) {
|
|
6059
|
+
const first = points[0];
|
|
6060
|
+
if (!first)
|
|
6061
|
+
return [];
|
|
6062
|
+
if (points.length === 1)
|
|
6063
|
+
return [first];
|
|
6064
|
+
const stride = Math.max(speedMps * intervalS, 0.01);
|
|
6065
|
+
const steps = [first];
|
|
6066
|
+
let carried = 0;
|
|
6067
|
+
for (let i = 1;i < points.length; i++) {
|
|
6068
|
+
const from = points[i - 1];
|
|
6069
|
+
const to = points[i];
|
|
6070
|
+
const leg = haversineM(from, to);
|
|
6071
|
+
let at = stride - carried;
|
|
6072
|
+
while (at < leg - 0.000001) {
|
|
6073
|
+
steps.push(interpolate(from, to, at / leg));
|
|
6074
|
+
at += stride;
|
|
6075
|
+
}
|
|
6076
|
+
carried = leg - (at - stride);
|
|
6077
|
+
}
|
|
6078
|
+
const last = points[points.length - 1];
|
|
6079
|
+
if (haversineM(steps[steps.length - 1], last) > 0.001)
|
|
6080
|
+
steps.push(last);
|
|
6081
|
+
else
|
|
6082
|
+
steps[steps.length - 1] = last;
|
|
6083
|
+
return steps;
|
|
6084
|
+
}
|
|
6085
|
+
function formatCoord(n) {
|
|
6086
|
+
const fixed = n.toFixed(7);
|
|
6087
|
+
return fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
|
|
6088
|
+
}
|
|
6089
|
+
|
|
6090
|
+
// ../../packages/e2e/src/conditions/route-ticker.ts
|
|
6091
|
+
var DEFAULT_SPEED_MPS = 20;
|
|
6092
|
+
var DEFAULT_INTERVAL_S = 1;
|
|
6093
|
+
var MAX_CONSECUTIVE_FAILURES = 3;
|
|
6094
|
+
var realTickerClock = {
|
|
6095
|
+
now: () => Date.now(),
|
|
6096
|
+
sleep: (ms, signal) => new Promise((resolve) => {
|
|
6097
|
+
if (signal.aborted)
|
|
6098
|
+
return resolve();
|
|
6099
|
+
const done = () => {
|
|
6100
|
+
clearTimeout(timer);
|
|
6101
|
+
signal.removeEventListener("abort", done);
|
|
6102
|
+
resolve();
|
|
6103
|
+
};
|
|
6104
|
+
const timer = setTimeout(done, ms);
|
|
6105
|
+
signal.addEventListener("abort", done);
|
|
6106
|
+
})
|
|
6107
|
+
};
|
|
6108
|
+
function startRouteTicker(route, deps) {
|
|
6109
|
+
const controller = new AbortController;
|
|
6110
|
+
const { signal } = controller;
|
|
6111
|
+
const startedAt = deps.now();
|
|
6112
|
+
const intervalMs = (route.intervalS ?? DEFAULT_INTERVAL_S) * 1000;
|
|
6113
|
+
const steps = routeSteps(route.points, route.speedMps ?? DEFAULT_SPEED_MPS, route.intervalS ?? DEFAULT_INTERVAL_S);
|
|
6114
|
+
function* playlist() {
|
|
6115
|
+
do
|
|
6116
|
+
yield* steps;
|
|
6117
|
+
while (route.loop && steps.length > 0);
|
|
6118
|
+
}
|
|
6119
|
+
const total = steps.length;
|
|
6120
|
+
const pace = async (done) => {
|
|
6121
|
+
if (!route.loop && done >= total)
|
|
6122
|
+
return false;
|
|
6123
|
+
await deps.sleep(Math.max(0, startedAt + done * intervalMs - deps.now()), signal);
|
|
6124
|
+
return true;
|
|
6125
|
+
};
|
|
6126
|
+
const run = async () => {
|
|
6127
|
+
const count = { ticks: 0, failures: 0, index: 0 };
|
|
6128
|
+
let lastError = "";
|
|
6129
|
+
const sendOne = async (step) => {
|
|
6130
|
+
const failure = await deps.fix(step).catch((e) => e instanceof Error ? e.message : String(e));
|
|
6131
|
+
if (!failure) {
|
|
6132
|
+
count.failures = 0;
|
|
6133
|
+
count.ticks++;
|
|
6134
|
+
return true;
|
|
6135
|
+
}
|
|
6136
|
+
lastError = failure;
|
|
6137
|
+
count.failures++;
|
|
6138
|
+
return count.failures < MAX_CONSECUTIVE_FAILURES;
|
|
6139
|
+
};
|
|
6140
|
+
for (const step of playlist()) {
|
|
6141
|
+
if (signal.aborted)
|
|
6142
|
+
return { ticks: count.ticks, stopped: "aborted" };
|
|
6143
|
+
if (!await sendOne(step))
|
|
6144
|
+
return { ticks: count.ticks, stopped: "failed", error: lastError };
|
|
6145
|
+
count.index++;
|
|
6146
|
+
if (!await pace(count.index))
|
|
6147
|
+
break;
|
|
6148
|
+
}
|
|
6149
|
+
return { ticks: count.ticks, stopped: signal.aborted ? "aborted" : "finished" };
|
|
6150
|
+
};
|
|
6151
|
+
return { stop: () => controller.abort(), done: run() };
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
// ../../packages/e2e/src/conditions/scheduler.ts
|
|
6155
|
+
var MAX_CONSECUTIVE_FAILURES2 = 3;
|
|
6156
|
+
var ONLINE = { kind: "online" };
|
|
6157
|
+
var OFFLINE = { kind: "offline" };
|
|
6158
|
+
function seededRng(seed) {
|
|
6159
|
+
let a = seed >>> 0;
|
|
6160
|
+
return () => {
|
|
6161
|
+
a = a + 1831565813 >>> 0;
|
|
6162
|
+
let t = a;
|
|
6163
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
6164
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
6165
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
6166
|
+
};
|
|
6167
|
+
}
|
|
6168
|
+
var messageOf = (e) => e instanceof Error ? e.message : String(e);
|
|
6169
|
+
async function attempt(run, what) {
|
|
6170
|
+
if (!run)
|
|
6171
|
+
return `target cannot apply ${what}`;
|
|
6172
|
+
try {
|
|
6173
|
+
const res = await run();
|
|
6174
|
+
return res.success ? undefined : res.error.message;
|
|
6175
|
+
} catch (e) {
|
|
6176
|
+
return messageOf(e);
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6179
|
+
function core() {
|
|
6180
|
+
const controller = new AbortController;
|
|
6181
|
+
return { controller, signal: controller.signal, status: { state: "running", transitions: 0 }, transitions: 0 };
|
|
6182
|
+
}
|
|
6183
|
+
function invalid(message) {
|
|
6184
|
+
return {
|
|
6185
|
+
stop: () => {
|
|
6186
|
+
return;
|
|
6187
|
+
},
|
|
6188
|
+
done: Promise.resolve({ transitions: 0, stopped: "failed", error: message }),
|
|
6189
|
+
current: () => ({ state: "failed", transitions: 0, error: message })
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
6192
|
+
function conclude(state, end, error) {
|
|
6193
|
+
const { transitions } = state;
|
|
6194
|
+
if (error !== undefined) {
|
|
6195
|
+
state.status = { state: "failed", transitions, error };
|
|
6196
|
+
return { transitions, stopped: "failed", error };
|
|
6197
|
+
}
|
|
6198
|
+
state.status = { state: end === "finished" ? "finished" : "stopped", transitions };
|
|
6199
|
+
return { transitions, stopped: end };
|
|
6200
|
+
}
|
|
6201
|
+
function applier(target, deps, state) {
|
|
6202
|
+
const net = target.network?.bind(target);
|
|
6203
|
+
const loc = target.location?.bind(target);
|
|
6204
|
+
const tally = { failures: 0, lastError: "" };
|
|
6205
|
+
const record = async (run, what) => {
|
|
6206
|
+
const failure = await attempt(run, what);
|
|
6207
|
+
if (failure) {
|
|
6208
|
+
tally.failures++;
|
|
6209
|
+
tally.lastError = failure;
|
|
6210
|
+
return false;
|
|
6211
|
+
}
|
|
6212
|
+
tally.failures = 0;
|
|
6213
|
+
state.transitions++;
|
|
6214
|
+
return true;
|
|
6215
|
+
};
|
|
6216
|
+
return {
|
|
6217
|
+
tally,
|
|
6218
|
+
async network(condition) {
|
|
6219
|
+
const landed = await record(net && (() => net(condition)), "network");
|
|
6220
|
+
if (landed)
|
|
6221
|
+
deps.sink?.({ type: "condition", area: "network", value: condition, at: deps.now() });
|
|
6222
|
+
return landed;
|
|
6223
|
+
},
|
|
6224
|
+
async location(op) {
|
|
6225
|
+
const landed = await record(loc && (() => loc(op)), "location");
|
|
6226
|
+
if (landed)
|
|
6227
|
+
deps.sink?.({ type: "condition", area: "location", value: op, at: deps.now() });
|
|
6228
|
+
return landed;
|
|
6229
|
+
}
|
|
6230
|
+
};
|
|
6231
|
+
}
|
|
6232
|
+
function startFlap(target, options, deps) {
|
|
6233
|
+
if (!(options.upMs > 0 && options.downMs > 0 && Number.isFinite(options.upMs + options.downMs))) {
|
|
6234
|
+
return invalid("flap needs upMs and downMs above 0");
|
|
6235
|
+
}
|
|
6236
|
+
if (options.base?.kind === "flap")
|
|
6237
|
+
return invalid("flap base cannot itself be a flap");
|
|
6238
|
+
const base = options.base ?? ONLINE;
|
|
6239
|
+
const rng = deps.rng ?? seededRng(options.seed ?? 1);
|
|
6240
|
+
const jitter = (options.jitterPct ?? 0) / 100;
|
|
6241
|
+
const state = core();
|
|
6242
|
+
const { signal } = state;
|
|
6243
|
+
const apply = applier(target, deps, state);
|
|
6244
|
+
const stretch = (ms) => Math.max(1, ms * (1 + (rng() * 2 - 1) * jitter));
|
|
6245
|
+
const runPhase = async (phase, skipApply, deadline) => {
|
|
6246
|
+
const landed = skipApply || await apply.network(phase === "up" ? base : OFFLINE);
|
|
6247
|
+
state.status = { state: "running", transitions: state.transitions, phase };
|
|
6248
|
+
const next = deadline + stretch(phase === "up" ? options.upMs : options.downMs);
|
|
6249
|
+
await deps.sleep(Math.max(0, next - deps.now()), signal);
|
|
6250
|
+
return [landed && phase === "up", next];
|
|
6251
|
+
};
|
|
6252
|
+
const run = async () => {
|
|
6253
|
+
let phase = "up";
|
|
6254
|
+
let atBase = false;
|
|
6255
|
+
let deadline = deps.now();
|
|
6256
|
+
let skipApply = options.applyInitial === false;
|
|
6257
|
+
while (!signal.aborted && apply.tally.failures < MAX_CONSECUTIVE_FAILURES2) {
|
|
6258
|
+
[atBase, deadline] = await runPhase(phase, skipApply, deadline);
|
|
6259
|
+
skipApply = false;
|
|
6260
|
+
phase = phase === "up" ? "down" : "up";
|
|
6261
|
+
}
|
|
6262
|
+
const { failures, lastError } = apply.tally;
|
|
6263
|
+
if (!atBase)
|
|
6264
|
+
await apply.network(base);
|
|
6265
|
+
return conclude(state, "aborted", failures >= MAX_CONSECUTIVE_FAILURES2 ? lastError : undefined);
|
|
6266
|
+
};
|
|
6267
|
+
return { stop: () => state.controller.abort(), done: run(), current: () => state.status };
|
|
6268
|
+
}
|
|
6269
|
+
function startTimeline(target, steps, deps) {
|
|
6270
|
+
if (steps.some((s) => !(s.atMs >= 0) || !Number.isFinite(s.atMs)))
|
|
6271
|
+
return invalid("timeline atMs must be 0 or more");
|
|
6272
|
+
const ordered = [...steps].sort((a, b) => a.atMs - b.atMs);
|
|
6273
|
+
const startedAt = deps.now();
|
|
6274
|
+
const state = core();
|
|
6275
|
+
const { signal } = state;
|
|
6276
|
+
const apply = applier(target, deps, state);
|
|
6277
|
+
const runStep = async (step) => {
|
|
6278
|
+
const errors = [];
|
|
6279
|
+
if (step.network && !await apply.network(step.network))
|
|
6280
|
+
errors.push(apply.tally.lastError);
|
|
6281
|
+
if (step.location && !await apply.location(step.location))
|
|
6282
|
+
errors.push(apply.tally.lastError);
|
|
6283
|
+
return errors.length > 0 ? errors.join("; ") : undefined;
|
|
6284
|
+
};
|
|
6285
|
+
const run = async () => {
|
|
6286
|
+
let stepFailures = 0;
|
|
6287
|
+
let lastError = "";
|
|
6288
|
+
for (const [index, step] of ordered.entries()) {
|
|
6289
|
+
await deps.sleep(Math.max(0, startedAt + step.atMs - deps.now()), signal);
|
|
6290
|
+
if (signal.aborted)
|
|
6291
|
+
break;
|
|
6292
|
+
state.status = { state: "running", transitions: state.transitions, step: index };
|
|
6293
|
+
const problem = await runStep(step);
|
|
6294
|
+
stepFailures = problem === undefined ? 0 : stepFailures + 1;
|
|
6295
|
+
lastError = problem ?? lastError;
|
|
6296
|
+
if (stepFailures >= MAX_CONSECUTIVE_FAILURES2)
|
|
6297
|
+
return conclude(state, "aborted", lastError);
|
|
6298
|
+
}
|
|
6299
|
+
return conclude(state, signal.aborted ? "aborted" : "finished");
|
|
6300
|
+
};
|
|
6301
|
+
return { stop: () => state.controller.abort(), done: run(), current: () => state.status };
|
|
6302
|
+
}
|
|
6303
|
+
|
|
6304
|
+
// ../../packages/e2e/src/conditions/device-conditions.ts
|
|
6305
|
+
var ONLINE2 = { kind: "online" };
|
|
6306
|
+
var REAL_CLOCK = { kind: "real" };
|
|
6307
|
+
function a11yOpOf(config) {
|
|
6308
|
+
const { fontScale: _scale, ...op } = config;
|
|
6309
|
+
return op;
|
|
6310
|
+
}
|
|
6311
|
+
var stderrLog = (line) => {
|
|
6312
|
+
process.stderr.write(`${line}
|
|
6313
|
+
`);
|
|
6314
|
+
};
|
|
6315
|
+
function invalid2(message) {
|
|
6316
|
+
return new E2eError2("INVALID_ARGUMENT", message);
|
|
6317
|
+
}
|
|
6318
|
+
function durationMs(value, what) {
|
|
6319
|
+
if (typeof value === "number") {
|
|
6320
|
+
if (!Number.isFinite(value) || value < 0)
|
|
6321
|
+
throw invalid2(`${what} must be 0 or more, got ${String(value)}`);
|
|
6322
|
+
return value;
|
|
6323
|
+
}
|
|
6324
|
+
const parsed = parseDuration(value);
|
|
6325
|
+
if (!parsed.success)
|
|
6326
|
+
throw invalid2(`${what}: ${parsed.error}`);
|
|
6327
|
+
return parsed.data;
|
|
6328
|
+
}
|
|
6329
|
+
function network(input) {
|
|
6330
|
+
const condition = toNetworkCondition(input);
|
|
6331
|
+
if (!condition.success)
|
|
6332
|
+
throw invalid2(condition.error);
|
|
6333
|
+
return condition.data;
|
|
6334
|
+
}
|
|
6335
|
+
function instant(at) {
|
|
6336
|
+
const date = at instanceof Date ? new Date(at.getTime()) : new Date(at);
|
|
6337
|
+
if (Number.isNaN(date.getTime()))
|
|
6338
|
+
throw invalid2(`invalid time ${JSON.stringify(at)} (use an ISO 8601 string, a Date or epoch ms)`);
|
|
6339
|
+
return date;
|
|
6340
|
+
}
|
|
6341
|
+
function validatePoint(point) {
|
|
6342
|
+
const ok = Number.isFinite(point.lat) && Number.isFinite(point.lon) && Math.abs(point.lat) <= 90 && Math.abs(point.lon) <= 180;
|
|
6343
|
+
if (!ok)
|
|
6344
|
+
throw invalid2(`invalid location ${point.lat},${point.lon} (lat within 90, lon within 180)`);
|
|
6345
|
+
}
|
|
6346
|
+
var SCENARIOS = ["city-run", "city-bike", "freeway"];
|
|
6347
|
+
function locationOf(input) {
|
|
6348
|
+
if ("kind" in input)
|
|
6349
|
+
return input;
|
|
6350
|
+
validatePoint(input);
|
|
6351
|
+
return { kind: "point", lat: input.lat, lon: input.lon };
|
|
6352
|
+
}
|
|
6353
|
+
var registry = new WeakMap;
|
|
6354
|
+
function registerConditions(device, internals) {
|
|
6355
|
+
registry.set(device, internals);
|
|
6356
|
+
}
|
|
6357
|
+
function conditionsOf(device) {
|
|
6358
|
+
return registry.get(device);
|
|
6359
|
+
}
|
|
6360
|
+
function conditionSinkFor(session) {
|
|
6361
|
+
return session.results === undefined ? undefined : openConditionSink(session.results.path);
|
|
6362
|
+
}
|
|
6363
|
+
function createDeviceConditions(deps) {
|
|
6364
|
+
const { driver, env } = deps;
|
|
6365
|
+
const ticker = deps.tickerClock ?? realTickerClock;
|
|
6366
|
+
const log = deps.log ?? stderrLog;
|
|
6367
|
+
const normalized = normalizeConditions(env.conditions ?? {});
|
|
6368
|
+
if (!normalized.success)
|
|
6369
|
+
throw new E2eError2("SESSION", `invalid conditions: ${normalized.error}`);
|
|
6370
|
+
const baseline = normalized.data.conditions;
|
|
6371
|
+
let currentNetwork = ONLINE2;
|
|
6372
|
+
let currentFaults = [];
|
|
6373
|
+
let currentClock = REAL_CLOCK;
|
|
6374
|
+
let clockSetAt = ticker.now();
|
|
6375
|
+
let networkDirty = false;
|
|
6376
|
+
let locationDirty = false;
|
|
6377
|
+
let clockDirty = false;
|
|
6378
|
+
let permissionsDirty = false;
|
|
6379
|
+
let localeDirty = false;
|
|
6380
|
+
let a11yDirty = false;
|
|
6381
|
+
let batteryDirty = false;
|
|
6382
|
+
const permissionStates = new Map;
|
|
6383
|
+
let permissionBundle;
|
|
6384
|
+
let currentLocale = {};
|
|
6385
|
+
let currentA11y = {};
|
|
6386
|
+
let flap;
|
|
6387
|
+
const runs = new Set;
|
|
6388
|
+
const recorder = deps.recorder;
|
|
6389
|
+
const emit = (event) => {
|
|
6390
|
+
deps.sink?.({ ...event, type: "condition", at: ticker.now() });
|
|
6391
|
+
};
|
|
6392
|
+
async function call(what, run) {
|
|
6393
|
+
const result = await settled(run, what, deps.signal());
|
|
6394
|
+
if (result.success)
|
|
6395
|
+
return result.data;
|
|
6396
|
+
const error = toE2eError(result.error, what);
|
|
6397
|
+
if (result.error.code === "E_NETWORK_RELAUNCH") {
|
|
6398
|
+
throw new E2eError2(error.code, `${error.message}
|
|
6399
|
+
the app runs without the warden network dylib: declare \`conditions.network\` for the suite so it launches with it, or pass { relaunch: true }`);
|
|
6400
|
+
}
|
|
6401
|
+
throw error;
|
|
6402
|
+
}
|
|
6403
|
+
const target = (options) => ({
|
|
6404
|
+
network: async (condition) => {
|
|
6405
|
+
const result = await driver.network(condition, options?.relaunch === undefined ? undefined : { relaunch: options.relaunch });
|
|
6406
|
+
if (result.success)
|
|
6407
|
+
recorder?.record("network", condition);
|
|
6408
|
+
return result;
|
|
6409
|
+
},
|
|
6410
|
+
location: async (op) => {
|
|
6411
|
+
const result = await driver.location(op);
|
|
6412
|
+
if (result.success)
|
|
6413
|
+
recorder?.record("location", op);
|
|
6414
|
+
return result;
|
|
6415
|
+
}
|
|
6416
|
+
});
|
|
6417
|
+
function track(scheduler) {
|
|
6418
|
+
const run = {
|
|
6419
|
+
async stop() {
|
|
6420
|
+
scheduler.stop();
|
|
6421
|
+
const outcome = await scheduler.done;
|
|
6422
|
+
runs.delete(run);
|
|
6423
|
+
if (flap?.run === run)
|
|
6424
|
+
flap = undefined;
|
|
6425
|
+
return outcome;
|
|
6426
|
+
},
|
|
6427
|
+
done: scheduler.done,
|
|
6428
|
+
status: () => scheduler.current()
|
|
6429
|
+
};
|
|
6430
|
+
runs.add(run);
|
|
6431
|
+
return run;
|
|
6432
|
+
}
|
|
6433
|
+
async function stopFlap() {
|
|
6434
|
+
const running = flap;
|
|
6435
|
+
if (running === undefined)
|
|
6436
|
+
return;
|
|
6437
|
+
await running.run.stop();
|
|
6438
|
+
}
|
|
6439
|
+
async function applyNetwork(condition, options) {
|
|
6440
|
+
await stopFlap();
|
|
6441
|
+
networkDirty = true;
|
|
6442
|
+
const opts = options?.relaunch === undefined ? undefined : { relaunch: options.relaunch };
|
|
6443
|
+
const state = await call("network", () => driver.network(condition, opts));
|
|
6444
|
+
currentNetwork = condition;
|
|
6445
|
+
recorder?.record("network", condition);
|
|
6446
|
+
emit({ area: "network", value: condition });
|
|
6447
|
+
return state;
|
|
6448
|
+
}
|
|
6449
|
+
async function startFlapRun(options, netOptions) {
|
|
6450
|
+
const upMs = durationMs(options.up, "flap up");
|
|
6451
|
+
const downMs = durationMs(options.down, "flap down");
|
|
6452
|
+
if (!(upMs > 0 && downMs > 0))
|
|
6453
|
+
throw invalid2("flap needs up and down above 0");
|
|
6454
|
+
const base = options.base === undefined ? ONLINE2 : network(options.base);
|
|
6455
|
+
if (base.kind === "flap")
|
|
6456
|
+
throw invalid2("flap base cannot itself be a flap");
|
|
6457
|
+
await applyNetwork(base, netOptions);
|
|
6458
|
+
const scheduler = startFlap(target(netOptions), {
|
|
6459
|
+
upMs,
|
|
6460
|
+
downMs,
|
|
6461
|
+
base,
|
|
6462
|
+
applyInitial: false,
|
|
6463
|
+
...options.jitterPct === undefined ? {} : { jitterPct: options.jitterPct },
|
|
6464
|
+
...options.seed === undefined ? {} : { seed: options.seed }
|
|
6465
|
+
}, { ...ticker, ...deps.sink === undefined ? {} : { sink: deps.sink } });
|
|
6466
|
+
const run = track(scheduler);
|
|
6467
|
+
flap = { run, options };
|
|
6468
|
+
currentNetwork = {
|
|
6469
|
+
kind: "flap",
|
|
6470
|
+
upMs,
|
|
6471
|
+
downMs,
|
|
6472
|
+
base,
|
|
6473
|
+
...options.jitterPct === undefined ? {} : { jitterPct: options.jitterPct }
|
|
6474
|
+
};
|
|
6475
|
+
emit({ area: "network", value: currentNetwork });
|
|
6476
|
+
return run;
|
|
6477
|
+
}
|
|
6478
|
+
async function setNetwork(input, options) {
|
|
6479
|
+
const condition = network(input);
|
|
6480
|
+
if (condition.kind === "flap") {
|
|
6481
|
+
await startFlapRun({
|
|
6482
|
+
up: condition.upMs,
|
|
6483
|
+
down: condition.downMs,
|
|
6484
|
+
...condition.jitterPct === undefined ? {} : { jitterPct: condition.jitterPct },
|
|
6485
|
+
...condition.base === undefined ? {} : { base: condition.base }
|
|
6486
|
+
}, options);
|
|
6487
|
+
return { online: true, condition, needsProxy: [] };
|
|
6488
|
+
}
|
|
6489
|
+
return applyNetwork(condition, options);
|
|
6490
|
+
}
|
|
6491
|
+
async function restoreNetwork(previous, previousFlap, options) {
|
|
6492
|
+
if (previous.kind === "flap" && previousFlap !== undefined)
|
|
6493
|
+
await startFlapRun(previousFlap, options);
|
|
6494
|
+
else
|
|
6495
|
+
await applyNetwork(previous, options);
|
|
6496
|
+
}
|
|
6497
|
+
async function resetNetwork() {
|
|
6498
|
+
await stopFlap();
|
|
6499
|
+
if (currentFaults.length > 0)
|
|
6500
|
+
await setFaults([]);
|
|
6501
|
+
const goal = baseline.network ?? ONLINE2;
|
|
6502
|
+
await applyNetwork(goal);
|
|
6503
|
+
networkDirty = false;
|
|
6504
|
+
recorder?.settle("network", goal, goal.kind === "online");
|
|
6505
|
+
}
|
|
6506
|
+
async function setFaults(rules) {
|
|
6507
|
+
networkDirty = true;
|
|
6508
|
+
const result = await call("network faults", () => driver.networkFaults(rules));
|
|
6509
|
+
currentFaults = rules;
|
|
6510
|
+
recorder?.record("network", currentNetwork);
|
|
6511
|
+
emit({ area: "faults", value: { kind: "faults", rules } });
|
|
6512
|
+
return result;
|
|
6513
|
+
}
|
|
6514
|
+
const networkApi = {
|
|
6515
|
+
set: setNetwork,
|
|
6516
|
+
async during(condition, fn, options) {
|
|
6517
|
+
const previous = currentNetwork;
|
|
6518
|
+
const previousFlap = flap?.options;
|
|
6519
|
+
await setNetwork(condition, options);
|
|
6520
|
+
let result;
|
|
6521
|
+
try {
|
|
6522
|
+
result = await fn();
|
|
6523
|
+
} catch (error) {
|
|
6524
|
+
await restoreNetwork(previous, previousFlap, options).catch((restoreError) => log(`[warden-e2e] network.during: restoring the previous network failed: ${String(restoreError instanceof Error ? restoreError.message : restoreError)}`));
|
|
6525
|
+
throw error;
|
|
6526
|
+
}
|
|
6527
|
+
await restoreNetwork(previous, previousFlap, options);
|
|
6528
|
+
return result;
|
|
6529
|
+
},
|
|
6530
|
+
flap: (options) => startFlapRun(options),
|
|
6531
|
+
fault: (rules) => setFaults("match" in rules ? [rules] : rules),
|
|
6532
|
+
async status() {
|
|
6533
|
+
return { condition: currentNetwork, faults: currentFaults, flapping: flap !== undefined };
|
|
6534
|
+
},
|
|
6535
|
+
reset: resetNetwork
|
|
6536
|
+
};
|
|
6537
|
+
async function applyLocation(op) {
|
|
6538
|
+
locationDirty = true;
|
|
6539
|
+
await call("location", () => driver.location(op));
|
|
6540
|
+
recorder?.record("location", op);
|
|
6541
|
+
emit({ area: "location", value: op });
|
|
6542
|
+
}
|
|
6543
|
+
const locationApi = {
|
|
6544
|
+
async set(point) {
|
|
6545
|
+
validatePoint(point);
|
|
6546
|
+
await applyLocation({
|
|
6547
|
+
kind: "point",
|
|
6548
|
+
lat: point.lat,
|
|
6549
|
+
lon: point.lon,
|
|
6550
|
+
...point.altM === undefined ? {} : { altM: point.altM }
|
|
6551
|
+
});
|
|
6552
|
+
},
|
|
6553
|
+
async route(points, options = {}) {
|
|
6554
|
+
const pace = {
|
|
6555
|
+
...options.speedMps === undefined ? {} : { speedMps: options.speedMps }
|
|
6556
|
+
};
|
|
6557
|
+
if ("gpx" in points) {
|
|
6558
|
+
await applyLocation({ kind: "gpx", path: resolve3(points.gpx), ...pace });
|
|
6559
|
+
return;
|
|
6560
|
+
}
|
|
6561
|
+
if (points.length === 0)
|
|
6562
|
+
throw invalid2("a route needs at least one point");
|
|
6563
|
+
for (const point of points)
|
|
6564
|
+
validatePoint(point);
|
|
6565
|
+
await applyLocation({
|
|
6566
|
+
kind: "route",
|
|
6567
|
+
points,
|
|
6568
|
+
...pace,
|
|
6569
|
+
...options.intervalS === undefined ? {} : { intervalS: options.intervalS },
|
|
6570
|
+
...options.loop === undefined ? {} : { loop: options.loop }
|
|
6571
|
+
});
|
|
6572
|
+
},
|
|
6573
|
+
async scenario(name) {
|
|
6574
|
+
if (!SCENARIOS.includes(name))
|
|
6575
|
+
throw invalid2(`unknown scenario "${String(name)}" (use ${SCENARIOS.join(", ")})`);
|
|
6576
|
+
await applyLocation({ kind: "scenario", name });
|
|
6577
|
+
},
|
|
6578
|
+
clear: () => applyLocation({ kind: "clear" })
|
|
6579
|
+
};
|
|
6580
|
+
async function applyClock(condition) {
|
|
6581
|
+
const op = driver.clock;
|
|
6582
|
+
clockDirty = true;
|
|
6583
|
+
if (typeof op !== "function") {
|
|
6584
|
+
throw toE2eError({ code: "E_UNSUPPORTED", message: "this driver has no clock condition", retryable: false }, "clock");
|
|
6585
|
+
}
|
|
6586
|
+
await call("clock", () => op.call(driver, condition));
|
|
6587
|
+
currentClock = condition;
|
|
6588
|
+
clockSetAt = ticker.now();
|
|
6589
|
+
emit({ area: "clock", value: condition });
|
|
6590
|
+
}
|
|
6591
|
+
function clockFor(at, frozen) {
|
|
6592
|
+
const iso = instant(at).toISOString();
|
|
6593
|
+
return frozen === true ? { kind: "frozen", at: iso } : { kind: "fixed", at: iso };
|
|
6594
|
+
}
|
|
6595
|
+
function advanced(by) {
|
|
6596
|
+
switch (currentClock.kind) {
|
|
6597
|
+
case "real":
|
|
6598
|
+
return { kind: "offset", ms: by };
|
|
6599
|
+
case "offset":
|
|
6600
|
+
return { kind: "offset", ms: currentClock.ms + by };
|
|
6601
|
+
case "frozen":
|
|
6602
|
+
return { kind: "frozen", at: new Date(Date.parse(currentClock.at) + by).toISOString() };
|
|
6603
|
+
case "fixed": {
|
|
6604
|
+
const now = Date.parse(currentClock.at) + (ticker.now() - clockSetAt);
|
|
6605
|
+
return { kind: "fixed", at: new Date(now + by).toISOString() };
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
const clockApi = {
|
|
6610
|
+
async set(at, options) {
|
|
6611
|
+
await applyClock(clockFor(at, options?.frozen));
|
|
6612
|
+
},
|
|
6613
|
+
async advance(by) {
|
|
6614
|
+
await applyClock(advanced(durationMs(by, "advance")));
|
|
6615
|
+
},
|
|
6616
|
+
async during(at, fn, options) {
|
|
6617
|
+
const previous = currentClock;
|
|
6618
|
+
await applyClock(clockFor(at, options?.frozen));
|
|
6619
|
+
let result;
|
|
6620
|
+
try {
|
|
6621
|
+
result = await fn();
|
|
6622
|
+
} catch (error) {
|
|
6623
|
+
await applyClock(previous).catch((restoreError) => log(`[warden-e2e] clock.during: restoring the previous clock failed: ${String(restoreError instanceof Error ? restoreError.message : restoreError)}`));
|
|
6624
|
+
throw error;
|
|
6625
|
+
}
|
|
6626
|
+
await applyClock(previous);
|
|
6627
|
+
return result;
|
|
6628
|
+
},
|
|
6629
|
+
async reset() {
|
|
6630
|
+
await applyClock(baseline.clock ?? REAL_CLOCK);
|
|
6631
|
+
clockDirty = false;
|
|
6632
|
+
},
|
|
6633
|
+
status: () => currentClock
|
|
6634
|
+
};
|
|
6635
|
+
function settingOp(name) {
|
|
6636
|
+
const op = driver[name];
|
|
6637
|
+
if (typeof op !== "function") {
|
|
6638
|
+
throw toE2eError({ code: "E_UNSUPPORTED", message: `this driver has no ${name} op`, retryable: false }, name);
|
|
6639
|
+
}
|
|
6640
|
+
return op.bind(driver);
|
|
6641
|
+
}
|
|
6642
|
+
const recordPermissions = (bundleId) => {
|
|
6643
|
+
const value = {
|
|
6644
|
+
grants: [...permissionStates].map(([permission, state]) => ({ bundleId, permission, state }))
|
|
6645
|
+
};
|
|
6646
|
+
recorder?.record("permission", value);
|
|
6647
|
+
emit({ area: "permission", value });
|
|
6648
|
+
};
|
|
6649
|
+
async function applyPermissions(grants, options) {
|
|
6650
|
+
permissionsDirty = true;
|
|
6651
|
+
const op = settingOp("permissions");
|
|
6652
|
+
const state = await call("permissions", () => op(grants, options ?? { bundleId: env.bundleId }));
|
|
6653
|
+
for (const { permission, state: applied } of state.applied)
|
|
6654
|
+
permissionStates.set(permission, applied);
|
|
6655
|
+
permissionBundle = state.bundleId;
|
|
6656
|
+
recordPermissions(state.bundleId);
|
|
6657
|
+
return state;
|
|
6658
|
+
}
|
|
6659
|
+
const permissionsApi = {
|
|
6660
|
+
set: applyPermissions,
|
|
6661
|
+
async reset(options) {
|
|
6662
|
+
const baselinePermissions = baseline.permissions ?? {};
|
|
6663
|
+
const back = {};
|
|
6664
|
+
for (const name of permissionStates.keys())
|
|
6665
|
+
back[name] = baselinePermissions[name] ?? "unset";
|
|
6666
|
+
if (Object.keys(back).length === 0)
|
|
6667
|
+
return;
|
|
6668
|
+
await applyPermissions(back, options ?? (permissionBundle ? { bundleId: permissionBundle } : undefined));
|
|
6669
|
+
permissionsDirty = false;
|
|
6670
|
+
permissionStates.clear();
|
|
6671
|
+
recorder?.settle("permission", { grants: [] }, true);
|
|
6672
|
+
}
|
|
6673
|
+
};
|
|
6674
|
+
function recordLocale() {
|
|
6675
|
+
const empty = currentLocale.locale === undefined && currentLocale.timezone === undefined;
|
|
6676
|
+
if (empty)
|
|
6677
|
+
recorder?.settle("locale", currentLocale, true);
|
|
6678
|
+
else
|
|
6679
|
+
recorder?.record("locale", currentLocale);
|
|
6680
|
+
emit({ area: "locale", value: currentLocale });
|
|
6681
|
+
}
|
|
6682
|
+
async function applyTimezone(tz) {
|
|
6683
|
+
localeDirty = true;
|
|
6684
|
+
const op = settingOp("timezone");
|
|
6685
|
+
const previous = currentLocale.previousTimezone;
|
|
6686
|
+
const state = await call("timezone", () => op(tz, previous === undefined ? undefined : { previous }));
|
|
6687
|
+
const { timezone: _gone, ...rest } = currentLocale;
|
|
6688
|
+
currentLocale = {
|
|
6689
|
+
...rest,
|
|
6690
|
+
...tz === null ? {} : { timezone: tz },
|
|
6691
|
+
...previous ?? state.previous ? { previousTimezone: previous ?? state.previous } : {}
|
|
6692
|
+
};
|
|
6693
|
+
recordLocale();
|
|
6694
|
+
return state;
|
|
6695
|
+
}
|
|
6696
|
+
async function applyLocale(tag, options) {
|
|
6697
|
+
localeDirty = true;
|
|
6698
|
+
const op = settingOp("locale");
|
|
6699
|
+
const state = await call("locale", () => op(tag, options ?? { bundleId: env.bundleId }));
|
|
6700
|
+
const { locale: _gone, ...rest } = currentLocale;
|
|
6701
|
+
currentLocale = {
|
|
6702
|
+
...rest,
|
|
6703
|
+
...tag === null ? {} : { locale: tag },
|
|
6704
|
+
...state.bundleId === undefined ? {} : { bundleId: state.bundleId }
|
|
6705
|
+
};
|
|
6706
|
+
recordLocale();
|
|
6707
|
+
return state;
|
|
6708
|
+
}
|
|
6709
|
+
function checkedA11y(op) {
|
|
6710
|
+
if (op === null)
|
|
6711
|
+
return;
|
|
6712
|
+
if (op.textSize !== undefined) {
|
|
6713
|
+
const size = resolveTextSize(op.textSize);
|
|
6714
|
+
if (!size.success)
|
|
6715
|
+
throw invalid2(size.error);
|
|
6716
|
+
}
|
|
6717
|
+
if (Object.keys(op).length === 0)
|
|
6718
|
+
throw invalid2("a11y needs at least one of textSize, boldText, contrast, reduceMotion");
|
|
6719
|
+
}
|
|
6720
|
+
async function applyA11y(op) {
|
|
6721
|
+
checkedA11y(op);
|
|
6722
|
+
a11yDirty = true;
|
|
6723
|
+
const run = settingOp("a11y");
|
|
6724
|
+
const state = await call("a11y", () => run(op));
|
|
6725
|
+
const resolved = op?.textSize === undefined ? undefined : resolveTextSize(op.textSize);
|
|
6726
|
+
currentA11y = op === null ? {} : {
|
|
6727
|
+
...currentA11y,
|
|
6728
|
+
...appliedA11y(op, state.applied),
|
|
6729
|
+
...resolved?.success ? { fontScale: resolved.data.scale } : {}
|
|
6730
|
+
};
|
|
6731
|
+
if (Object.keys(currentA11y).length === 0)
|
|
6732
|
+
recorder?.settle("a11y", currentA11y, true);
|
|
6733
|
+
else
|
|
6734
|
+
recorder?.record("a11y", currentA11y);
|
|
6735
|
+
emit({ area: "a11y", value: currentA11y });
|
|
6736
|
+
return state;
|
|
6737
|
+
}
|
|
6738
|
+
async function applyBattery(op) {
|
|
6739
|
+
batteryDirty = true;
|
|
6740
|
+
const run = settingOp("battery");
|
|
6741
|
+
const state = await call("battery", () => run(op));
|
|
6742
|
+
const value = op === null ? {} : { ...op, cosmetic: state.cosmetic };
|
|
6743
|
+
if (op === null)
|
|
6744
|
+
recorder?.settle("battery", value, true);
|
|
6745
|
+
else
|
|
6746
|
+
recorder?.record("battery", value);
|
|
6747
|
+
emit({ area: "battery", value });
|
|
6748
|
+
return state;
|
|
6749
|
+
}
|
|
6750
|
+
async function startTimelineRun(steps) {
|
|
6751
|
+
const converted = steps.map((step, index) => {
|
|
6752
|
+
const atMs = durationMs(step.at, `timeline step ${index} at`);
|
|
6753
|
+
const converted = { atMs };
|
|
6754
|
+
if (step.network !== undefined) {
|
|
6755
|
+
const condition = network(step.network);
|
|
6756
|
+
if (condition.kind === "flap")
|
|
6757
|
+
throw invalid2(`timeline step ${index}: use device.network.flap for a flap`);
|
|
6758
|
+
converted.network = condition;
|
|
6759
|
+
}
|
|
6760
|
+
if (step.location !== undefined)
|
|
6761
|
+
converted.location = locationOf(step.location);
|
|
6762
|
+
return converted;
|
|
6763
|
+
});
|
|
6764
|
+
networkDirty = true;
|
|
6765
|
+
locationDirty = true;
|
|
6766
|
+
const scheduler = startTimeline(target(), converted, {
|
|
6767
|
+
...ticker,
|
|
6768
|
+
...deps.sink === undefined ? {} : { sink: deps.sink }
|
|
6769
|
+
});
|
|
6770
|
+
const status = scheduler.current();
|
|
6771
|
+
if (status.state === "failed")
|
|
6772
|
+
throw invalid2(status.error);
|
|
6773
|
+
const run = track(scheduler);
|
|
6774
|
+
const last = converted.at(-1);
|
|
6775
|
+
if (last?.network !== undefined)
|
|
6776
|
+
currentNetwork = last.network;
|
|
6777
|
+
return run;
|
|
6778
|
+
}
|
|
6779
|
+
async function stopRuns() {
|
|
6780
|
+
const failures = [];
|
|
6781
|
+
for (const run of [...runs]) {
|
|
6782
|
+
const outcome = await run.stop();
|
|
6783
|
+
if (outcome.stopped === "failed")
|
|
6784
|
+
failures.push(outcome.error);
|
|
6785
|
+
}
|
|
6786
|
+
if (failures.length > 0) {
|
|
6787
|
+
throw new E2eError2("ACTION_FAILED", `a network flap or timeline stopped on its own after failing: ${failures[0]}`);
|
|
6788
|
+
}
|
|
6789
|
+
}
|
|
6790
|
+
const declared = baseline.network !== undefined || baseline.location !== undefined || baseline.clock !== undefined || baseline.permissions !== undefined || baseline.locale !== undefined || baseline.timezone !== undefined || baseline.a11y !== undefined;
|
|
6791
|
+
const internals = {
|
|
6792
|
+
declared,
|
|
6793
|
+
dirty: () => networkDirty || locationDirty || clockDirty || permissionsDirty || localeDirty || a11yDirty || batteryDirty || runs.size > 0,
|
|
6794
|
+
wantsInject: () => baseline.network !== undefined || baseline.clock !== undefined || currentNetwork.kind !== "online" || currentClock.kind !== "real",
|
|
6795
|
+
async applyBaseline() {
|
|
6796
|
+
if (baseline.permissions !== undefined) {
|
|
6797
|
+
for (const [name, state] of Object.entries(baseline.permissions))
|
|
6798
|
+
permissionStates.set(name, state);
|
|
6799
|
+
await applyPermissions(baseline.permissions);
|
|
6800
|
+
}
|
|
6801
|
+
if (baseline.timezone !== undefined)
|
|
6802
|
+
await applyTimezone(baseline.timezone);
|
|
6803
|
+
if (baseline.locale !== undefined)
|
|
6804
|
+
await applyLocale(baseline.locale);
|
|
6805
|
+
if (baseline.a11y !== undefined)
|
|
6806
|
+
await applyA11y(a11yOpOf(baseline.a11y));
|
|
6807
|
+
if (baseline.clock !== undefined)
|
|
6808
|
+
await applyClock(baseline.clock);
|
|
6809
|
+
if (baseline.location !== undefined)
|
|
6810
|
+
await applyLocation(baseline.location);
|
|
6811
|
+
if (baseline.network !== undefined)
|
|
6812
|
+
await applyNetwork(baseline.network, { relaunch: true });
|
|
6813
|
+
networkDirty = false;
|
|
6814
|
+
locationDirty = false;
|
|
6815
|
+
clockDirty = false;
|
|
6816
|
+
permissionsDirty = false;
|
|
6817
|
+
localeDirty = false;
|
|
6818
|
+
a11yDirty = false;
|
|
6819
|
+
batteryDirty = false;
|
|
6820
|
+
},
|
|
6821
|
+
async teardown() {
|
|
6822
|
+
const errors = [];
|
|
6823
|
+
const attempt = async (run) => {
|
|
6824
|
+
try {
|
|
6825
|
+
await run();
|
|
6826
|
+
} catch (error) {
|
|
6827
|
+
errors.push(error);
|
|
6828
|
+
}
|
|
6829
|
+
};
|
|
6830
|
+
await attempt(stopRuns);
|
|
6831
|
+
if (networkDirty)
|
|
6832
|
+
await attempt(resetNetwork);
|
|
6833
|
+
if (locationDirty) {
|
|
6834
|
+
await attempt(async () => {
|
|
6835
|
+
const goal = baseline.location ?? { kind: "clear" };
|
|
6836
|
+
await applyLocation(goal);
|
|
6837
|
+
locationDirty = false;
|
|
6838
|
+
recorder?.settle("location", goal, goal.kind === "clear");
|
|
6839
|
+
});
|
|
6840
|
+
}
|
|
6841
|
+
if (clockDirty)
|
|
6842
|
+
await attempt(clockApi.reset);
|
|
6843
|
+
if (permissionsDirty)
|
|
6844
|
+
await attempt(() => permissionsApi.reset());
|
|
6845
|
+
if (localeDirty) {
|
|
6846
|
+
await attempt(async () => {
|
|
6847
|
+
await applyTimezone(baseline.timezone ?? null);
|
|
6848
|
+
await applyLocale(baseline.locale ?? null);
|
|
6849
|
+
localeDirty = false;
|
|
6850
|
+
});
|
|
6851
|
+
}
|
|
6852
|
+
if (a11yDirty) {
|
|
6853
|
+
await attempt(async () => {
|
|
6854
|
+
await applyA11y(null);
|
|
6855
|
+
if (baseline.a11y !== undefined)
|
|
6856
|
+
await applyA11y(a11yOpOf(baseline.a11y));
|
|
6857
|
+
a11yDirty = false;
|
|
6858
|
+
});
|
|
6859
|
+
}
|
|
6860
|
+
if (batteryDirty) {
|
|
6861
|
+
await attempt(async () => {
|
|
6862
|
+
await applyBattery(null);
|
|
6863
|
+
batteryDirty = false;
|
|
6864
|
+
});
|
|
6865
|
+
}
|
|
6866
|
+
if (errors.length > 0)
|
|
6867
|
+
throw errors[0];
|
|
6868
|
+
}
|
|
6869
|
+
};
|
|
6870
|
+
return {
|
|
6871
|
+
network: networkApi,
|
|
6872
|
+
location: locationApi,
|
|
6873
|
+
clock: clockApi,
|
|
6874
|
+
permissions: permissionsApi,
|
|
6875
|
+
timezone: applyTimezone,
|
|
6876
|
+
locale: applyLocale,
|
|
6877
|
+
a11y: applyA11y,
|
|
6878
|
+
battery: applyBattery,
|
|
6879
|
+
memoryWarning: async (options) => {
|
|
6880
|
+
const op = settingOp("memoryWarning");
|
|
6881
|
+
await call("memoryWarning", () => op(options ?? { bundleId: env.bundleId }));
|
|
6882
|
+
},
|
|
6883
|
+
push: async (payload, options) => {
|
|
6884
|
+
const op = settingOp("push");
|
|
6885
|
+
await call("push", () => op(payload, options ?? { bundleId: env.bundleId }));
|
|
6886
|
+
},
|
|
6887
|
+
timeline: startTimelineRun,
|
|
6888
|
+
internals
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
|
|
5582
6892
|
// ../../packages/e2e/src/perf/device-perf.ts
|
|
5583
|
-
import { copyFileSync, existsSync as existsSync3, mkdirSync as
|
|
6893
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync } from "fs";
|
|
5584
6894
|
import { join as join2 } from "path";
|
|
5585
6895
|
|
|
5586
6896
|
// ../../packages/e2e/src/perf/budgets.ts
|
|
@@ -5677,7 +6987,7 @@ function saveReport(deps, result, report, sessionId) {
|
|
|
5677
6987
|
if (attempt === undefined)
|
|
5678
6988
|
return sessionReport;
|
|
5679
6989
|
const out = join2(attempt, PERF_DIR);
|
|
5680
|
-
|
|
6990
|
+
mkdirSync4(out, { recursive: true });
|
|
5681
6991
|
const sessionDir = typeof result.dir === "string" ? result.dir : undefined;
|
|
5682
6992
|
const write = (name, from, fallback) => {
|
|
5683
6993
|
if (from !== undefined && existsSync3(from))
|
|
@@ -5795,9 +7105,9 @@ function specIdOf(file, specsDir) {
|
|
|
5795
7105
|
}
|
|
5796
7106
|
|
|
5797
7107
|
// ../../packages/e2e/src/devtools/native-log.ts
|
|
5798
|
-
import { copyFileSync as copyFileSync2, mkdirSync as
|
|
7108
|
+
import { copyFileSync as copyFileSync2, mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
5799
7109
|
import { homedir } from "os";
|
|
5800
|
-
import { dirname as
|
|
7110
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
5801
7111
|
var NATIVE_LOG = "native.log";
|
|
5802
7112
|
var DEFAULT_TIMEOUT_MS5 = 1e4;
|
|
5803
7113
|
var SLACK_S = 5;
|
|
@@ -5812,11 +7122,11 @@ function realNativeFs() {
|
|
|
5812
7122
|
}
|
|
5813
7123
|
},
|
|
5814
7124
|
copy: (from, to) => {
|
|
5815
|
-
|
|
7125
|
+
mkdirSync5(dirname4(to), { recursive: true });
|
|
5816
7126
|
copyFileSync2(from, to);
|
|
5817
7127
|
},
|
|
5818
7128
|
write: (path, text) => {
|
|
5819
|
-
|
|
7129
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
5820
7130
|
writeFileSync2(path, text);
|
|
5821
7131
|
}
|
|
5822
7132
|
};
|
|
@@ -6005,7 +7315,8 @@ function registrationDefaults(env = process.env) {
|
|
|
6005
7315
|
timeoutMs: parsed?.timeoutMs ?? DEFAULT_TIMEOUT_MS4,
|
|
6006
7316
|
actionTimeoutMs: parsed?.actionTimeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS,
|
|
6007
7317
|
assertionTimeoutMs: parsed?.assertionTimeoutMs ?? DEFAULT_ASSERTION_TIMEOUT_MS,
|
|
6008
|
-
platform: parsed?.platform
|
|
7318
|
+
platform: parsed?.platform,
|
|
7319
|
+
deviceKind: parsed?.deviceKind
|
|
6009
7320
|
};
|
|
6010
7321
|
}
|
|
6011
7322
|
var ONLY_KEY = Symbol.for("@delacour/warden-e2e/only");
|
|
@@ -6018,6 +7329,6 @@ function onlyWasSeen() {
|
|
|
6018
7329
|
|
|
6019
7330
|
// ../../packages/e2e/src/version.ts
|
|
6020
7331
|
var WORKSPACE_VERSION = "0.0.0-workspace";
|
|
6021
|
-
var WARDEN_E2E_VERSION2 = "0.
|
|
7332
|
+
var WARDEN_E2E_VERSION2 = "0.7.0";
|
|
6022
7333
|
|
|
6023
|
-
export { E2eError2, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, bunExec, execError, driverError, driverErr, logEventSchema, fatalEventSchema, responseSchema, mapWireError, encodeRequest, bunSpawn, stdioTransport, spawnSimd, connectSimd,
|
|
7334
|
+
export { E2eError2, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, bunExec, execError, driverError, driverErr, logEventSchema, fatalEventSchema, responseSchema, mapWireError, encodeRequest, bunSpawn, stdioTransport, spawnSimd, connectSimd, isRecoverableLoss, CONDITION_AREAS, resolveNetwork, resolveTextSize, androidFontScale, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultHidRecovery, setDefaultNativeLog, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession, IOS_DEVICE_CAPABILITIES, toE2eError, settled, unwrap, formatCoord, DEFAULT_SPEED_MPS, DEFAULT_INTERVAL_S, realTickerClock, startRouteTicker, startFlap, registerConditions, conditionsOf, conditionSinkFor, createDeviceConditions, parseBudgets, createDevicePerf, perfStartFor, spawnWarden, hash6, attemptDir, screenshotPath, specIdOf, realNativeLogDeps, captureNativeLog, callerFile, BUN_TIMEOUT_MARGIN_MS2, ownHook, currentScope, createScope, runInScope, hooksFor, resolveUse, registrationDefaults, markOnly, onlyWasSeen, WORKSPACE_VERSION, WARDEN_E2E_VERSION2 };
|