@delacour/warden-e2e 0.13.0 → 0.15.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-tnr47n9c.mjs → chunk-e3cry5br.mjs} +828 -255
- package/index.mjs +144 -14
- package/package.json +2 -2
- package/preload.mjs +375 -63
- package/types/core/src/affected/affected.schema.d.ts +5 -0
- package/types/core/src/builds/builds.defaults.d.ts +8 -0
- package/types/core/src/builds/config.schema.d.ts +9 -0
- package/types/core/src/builds/define-config.d.ts +9 -1
- package/types/core/src/conditions/conditions.config.d.ts +6 -1
- package/types/core/src/conditions/conditions.types.d.ts +21 -0
- package/types/core/src/conditions/inject-state.d.ts +3 -2
- package/types/e2e/src/a11y/a11y-target.d.ts +2 -0
- package/types/e2e/src/conditions/condition-events.d.ts +6 -1
- package/types/e2e/src/conditions/device-conditions.d.ts +8 -2
- package/types/e2e/src/device.d.ts +10 -2
- package/types/e2e/src/driver/android/android-settings.d.ts +9 -2
- package/types/e2e/src/driver/ax-wedge.d.ts +38 -0
- package/types/e2e/src/driver/bridge-stall.d.ts +41 -0
- package/types/e2e/src/driver/driver.types.d.ts +35 -2
- package/types/e2e/src/driver/ios/inject.d.ts +17 -2
- package/types/e2e/src/driver/ios/ios-driver.d.ts +17 -2
- package/types/e2e/src/driver/ios/ios-keyboard.d.ts +39 -0
- package/types/e2e/src/driver/ios/simd-client.d.ts +2 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +129 -0
- package/types/e2e/src/driver/keyboard-state.d.ts +9 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +1 -1
- package/types/e2e/src/driver/read-deadline.d.ts +4 -0
- package/types/e2e/src/driver/settle.d.ts +11 -1
- package/types/e2e/src/errors.d.ts +3 -1
- package/types/e2e/src/expect/a11y-matchers.d.ts +7 -0
- package/types/e2e/src/expect/matchers.d.ts +5 -0
- package/types/e2e/src/index.d.ts +2 -1
- package/types/e2e/src/runner/bridge-evidence.d.ts +12 -0
- package/types/e2e/src/runner/results.d.ts +29 -0
- package/types/e2e/src/screen/actions.d.ts +7 -0
- package/types/e2e/src/screen/context.d.ts +11 -12
- package/types/e2e/src/screen/degraded-read.d.ts +23 -0
- package/types/e2e/src/screen/keyboard-cover.d.ts +26 -0
- package/types/e2e/src/screen/locator.d.ts +61 -7
- package/types/e2e/src/screen/react-locator.d.ts +17 -2
- package/types/e2e/src/screen/react-merge.d.ts +10 -0
- package/types/e2e/src/session.d.ts +6 -0
package/preload.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
settleDetailOf,
|
|
12
12
|
DEFAULT_SETTLE_QUIET_MS,
|
|
13
13
|
DEFAULT_SETTLE_TIMEOUT_MS,
|
|
14
|
+
hashFallbackQuietMs,
|
|
14
15
|
settleByHash,
|
|
15
16
|
snapshotHash,
|
|
16
17
|
ok,
|
|
@@ -29,13 +30,14 @@ import {
|
|
|
29
30
|
isInteractiveRole,
|
|
30
31
|
resolveRole,
|
|
31
32
|
indexSnapshot,
|
|
33
|
+
keyboardVisible,
|
|
32
34
|
testIdQuery,
|
|
33
35
|
textQuery,
|
|
34
36
|
roleQuery,
|
|
35
37
|
selectorExpression,
|
|
36
38
|
renderSnapshot,
|
|
37
39
|
renderHeader,
|
|
38
|
-
|
|
40
|
+
lookUpTestIdViaReact,
|
|
39
41
|
isLocator,
|
|
40
42
|
Locator,
|
|
41
43
|
scrollUntilVisible,
|
|
@@ -44,6 +46,10 @@ import {
|
|
|
44
46
|
execError,
|
|
45
47
|
driverError,
|
|
46
48
|
driverErr,
|
|
49
|
+
bridgeStallSchema,
|
|
50
|
+
toBridgeStall,
|
|
51
|
+
noteBridgeStall,
|
|
52
|
+
formatBridgeStall,
|
|
47
53
|
logEventSchema,
|
|
48
54
|
fatalEventSchema,
|
|
49
55
|
responseSchema,
|
|
@@ -56,6 +62,10 @@ import {
|
|
|
56
62
|
createSimdClient,
|
|
57
63
|
spawnSimd,
|
|
58
64
|
connectSimd,
|
|
65
|
+
KEYBOARD_MODES,
|
|
66
|
+
DEFAULT_KEYBOARD_MODE,
|
|
67
|
+
IME_STASH_ENABLED,
|
|
68
|
+
IME_STASH_DEFAULT,
|
|
59
69
|
CAMERA_FACINGS,
|
|
60
70
|
CONDITION_AREAS,
|
|
61
71
|
resolveNetwork,
|
|
@@ -109,7 +119,7 @@ import {
|
|
|
109
119
|
onlyWasSeen,
|
|
110
120
|
WORKSPACE_VERSION,
|
|
111
121
|
WARDEN_E2E_VERSION2
|
|
112
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-e3cry5br.mjs";
|
|
113
123
|
|
|
114
124
|
// ../../packages/e2e/src/preload.ts
|
|
115
125
|
import { afterAll, setDefaultTimeout } from "bun:test";
|
|
@@ -1371,6 +1381,8 @@ function physicalConditionOps(device) {
|
|
|
1371
1381
|
locale: () => refuse("locale"),
|
|
1372
1382
|
a11y: () => refuse("a11y"),
|
|
1373
1383
|
battery: () => refuse("battery"),
|
|
1384
|
+
keyboard: () => refuse("keyboard"),
|
|
1385
|
+
keyboardState: () => refuse("keyboardState"),
|
|
1374
1386
|
memoryWarning: () => refuse("memoryWarning"),
|
|
1375
1387
|
push: () => refuse("push"),
|
|
1376
1388
|
camera: () => refuse("camera")
|
|
@@ -4074,6 +4086,75 @@ function batteryOp(c) {
|
|
|
4074
4086
|
};
|
|
4075
4087
|
};
|
|
4076
4088
|
}
|
|
4089
|
+
var nullable = (value) => value.trim() === "null" ? "" : value.trim();
|
|
4090
|
+
async function readIme(c) {
|
|
4091
|
+
const enabled = await c.shell(settingGetArgv("secure", "enabled_input_methods"));
|
|
4092
|
+
const current = await c.shell(settingGetArgv("secure", "default_input_method"));
|
|
4093
|
+
return { enabled: nullable(enabled.stdout), default: nullable(current.stdout) };
|
|
4094
|
+
}
|
|
4095
|
+
async function readImeStash(c) {
|
|
4096
|
+
const enabled = await c.shell(settingGetArgv("global", IME_STASH_ENABLED));
|
|
4097
|
+
const current = await c.shell(settingGetArgv("global", IME_STASH_DEFAULT));
|
|
4098
|
+
const state = { enabled: nullable(enabled.stdout), default: nullable(current.stdout) };
|
|
4099
|
+
return state.enabled === "" ? undefined : state;
|
|
4100
|
+
}
|
|
4101
|
+
async function readKeyboardMode(c) {
|
|
4102
|
+
return (await readIme(c)).enabled === "" ? "hardware" : "software";
|
|
4103
|
+
}
|
|
4104
|
+
function imeVisibleIn(dump) {
|
|
4105
|
+
return /\bisVisible=true\b/.test(dump);
|
|
4106
|
+
}
|
|
4107
|
+
async function readKeyboardVisible(c) {
|
|
4108
|
+
const read = await c.shell(["dumpsys", "window", "InputMethod"]);
|
|
4109
|
+
return read.ok && imeVisibleIn(read.stdout);
|
|
4110
|
+
}
|
|
4111
|
+
async function restoreImes(c) {
|
|
4112
|
+
const stash = await readImeStash(c);
|
|
4113
|
+
if (stash !== undefined) {
|
|
4114
|
+
const steps = [
|
|
4115
|
+
["settings", "put", "secure", "enabled_input_methods", stash.enabled],
|
|
4116
|
+
...stash.default === "" ? [] : [["settings", "put", "secure", "default_input_method", stash.default]],
|
|
4117
|
+
["settings", "delete", "global", IME_STASH_ENABLED],
|
|
4118
|
+
["settings", "delete", "global", IME_STASH_DEFAULT]
|
|
4119
|
+
];
|
|
4120
|
+
for (const argv of steps) {
|
|
4121
|
+
const done = await c.must(argv);
|
|
4122
|
+
if (!done.success)
|
|
4123
|
+
return done;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
return { success: true, data: { mode: "software" } };
|
|
4127
|
+
}
|
|
4128
|
+
async function hideImes(c, current) {
|
|
4129
|
+
const listed = await c.shell(["ime", "list", "-s"]);
|
|
4130
|
+
const ids = listed.stdout.split(`
|
|
4131
|
+
`).map((line) => line.trim()).filter((line) => line.includes("/"));
|
|
4132
|
+
const steps = [
|
|
4133
|
+
["settings", "put", "global", IME_STASH_ENABLED, current.enabled],
|
|
4134
|
+
...current.default === "" ? [] : [["settings", "put", "global", IME_STASH_DEFAULT, current.default]],
|
|
4135
|
+
...ids.map((id) => ["ime", "disable", id])
|
|
4136
|
+
];
|
|
4137
|
+
for (const argv of steps) {
|
|
4138
|
+
const done = await c.must(argv);
|
|
4139
|
+
if (!done.success)
|
|
4140
|
+
return done;
|
|
4141
|
+
}
|
|
4142
|
+
if ((await readIme(c)).enabled !== "") {
|
|
4143
|
+
return driverErr("E_IO", "an input method is still enabled after ime disable", { detail: { serial: c.serial } });
|
|
4144
|
+
}
|
|
4145
|
+
return { success: true, data: { mode: "hardware", previousIme: current } };
|
|
4146
|
+
}
|
|
4147
|
+
function keyboardOp(c) {
|
|
4148
|
+
return async (mode) => {
|
|
4149
|
+
if (mode === "software")
|
|
4150
|
+
return restoreImes(c);
|
|
4151
|
+
const current = await readIme(c);
|
|
4152
|
+
if (current.enabled !== "")
|
|
4153
|
+
return hideImes(c, current);
|
|
4154
|
+
const previous = await readImeStash(c);
|
|
4155
|
+
return { success: true, data: { mode, ...previous === undefined ? {} : { previousIme: previous } } };
|
|
4156
|
+
};
|
|
4157
|
+
}
|
|
4077
4158
|
function memoryWarningOp(c) {
|
|
4078
4159
|
return async (opts) => {
|
|
4079
4160
|
const app = c.packageOf(opts?.bundleId, "memoryWarning");
|
|
@@ -4148,6 +4229,12 @@ function createAndroidSettings(deps) {
|
|
|
4148
4229
|
locale: localeOp(ctx),
|
|
4149
4230
|
a11y: a11yOp(ctx),
|
|
4150
4231
|
battery: batteryOp(ctx),
|
|
4232
|
+
keyboard: keyboardOp(ctx),
|
|
4233
|
+
keyboardMode: () => readKeyboardMode(ctx),
|
|
4234
|
+
keyboardVisible: () => readKeyboardVisible(ctx),
|
|
4235
|
+
async keyboardState() {
|
|
4236
|
+
return { success: true, data: { mode: await readKeyboardMode(ctx), visible: await readKeyboardVisible(ctx) } };
|
|
4237
|
+
},
|
|
4151
4238
|
memoryWarning: memoryWarningOp(ctx),
|
|
4152
4239
|
async push() {
|
|
4153
4240
|
return driverErr("E_UNSUPPORTED", "push notifications through the shell are iOS only (`simctl push`); Android needs FCM");
|
|
@@ -5835,7 +5922,12 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
|
|
|
5835
5922
|
});
|
|
5836
5923
|
return { success: true, data: { ...applied.data, condition: op } };
|
|
5837
5924
|
};
|
|
5838
|
-
const {
|
|
5925
|
+
const {
|
|
5926
|
+
talkback,
|
|
5927
|
+
keyboardMode: _mode,
|
|
5928
|
+
keyboardVisible: _visible,
|
|
5929
|
+
...settings
|
|
5930
|
+
} = createAndroidSettings({
|
|
5839
5931
|
session,
|
|
5840
5932
|
serial,
|
|
5841
5933
|
...options.appPackage ? { appPackage: options.appPackage } : {},
|
|
@@ -5953,6 +6045,11 @@ function buildDriver(options, session, baseScreen, clock, execBinary, facts) {
|
|
|
5953
6045
|
...options.cameraHost ?? createAndroidCameraHost({ serial, exec })
|
|
5954
6046
|
}),
|
|
5955
6047
|
...facts.physical ? physicalConditionOps(`physical Android device ${serial}`) : {},
|
|
6048
|
+
keyboardState: settings.keyboardState,
|
|
6049
|
+
async dismissKeyboard() {
|
|
6050
|
+
const pressed = await pressButton("back");
|
|
6051
|
+
return pressed.success ? { success: true, data: { via: "back" } } : pressed;
|
|
6052
|
+
},
|
|
5956
6053
|
biometrics,
|
|
5957
6054
|
app: createAppCommands({
|
|
5958
6055
|
session,
|
|
@@ -6031,6 +6128,17 @@ function writeLaunchSettings(path, settings) {
|
|
|
6031
6128
|
}
|
|
6032
6129
|
}
|
|
6033
6130
|
|
|
6131
|
+
// ../../packages/e2e/src/driver/read-deadline.ts
|
|
6132
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6133
|
+
var deadlines = new AsyncLocalStorage;
|
|
6134
|
+
function withReadDeadline(remaining, fn) {
|
|
6135
|
+
return deadlines.run({ remaining }, fn);
|
|
6136
|
+
}
|
|
6137
|
+
function readDeadlineRemaining() {
|
|
6138
|
+
const deadline = deadlines.getStore();
|
|
6139
|
+
return deadline === undefined ? undefined : Math.max(0, deadline.remaining());
|
|
6140
|
+
}
|
|
6141
|
+
|
|
6034
6142
|
// ../../packages/e2e/src/driver/ios/inject.ts
|
|
6035
6143
|
import { mkdirSync as mkdirSync7, readFileSync as readFileSync5, renameSync as renameSync6, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
6036
6144
|
import { dirname as dirname3, join as join10, resolve } from "path";
|
|
@@ -6051,12 +6159,17 @@ function encodeInjectState(state) {
|
|
|
6051
6159
|
clockOffsetMs: Math.trunc(state.clockOffsetMs ?? 0),
|
|
6052
6160
|
...state.clockFrozenMs === undefined ? {} : { clockFrozenMs: Math.trunc(state.clockFrozenMs) },
|
|
6053
6161
|
...state.a11yRead ? { a11yRead: true } : {},
|
|
6054
|
-
...state.camera === undefined ? {} : { camera: state.camera }
|
|
6162
|
+
...state.camera === undefined ? {} : { camera: state.camera },
|
|
6163
|
+
...state.keyboard === undefined ? {} : { keyboard: state.keyboard },
|
|
6164
|
+
...state.dismissKeyboard ? { dismissKeyboard: true } : {}
|
|
6055
6165
|
});
|
|
6056
6166
|
}
|
|
6057
6167
|
function isRecord3(value) {
|
|
6058
6168
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6059
6169
|
}
|
|
6170
|
+
function parseKeyboard(value) {
|
|
6171
|
+
return KEYBOARD_MODES.find((mode) => mode === value);
|
|
6172
|
+
}
|
|
6060
6173
|
function parseFeed(value) {
|
|
6061
6174
|
if (!isRecord3(value) || typeof value.path !== "string")
|
|
6062
6175
|
return;
|
|
@@ -6084,7 +6197,8 @@ function parseInjectState(text) {
|
|
|
6084
6197
|
clockOffsetMs: typeof data.clockOffsetMs === "number" ? data.clockOffsetMs : 0,
|
|
6085
6198
|
...typeof data.clockFrozenMs === "number" ? { clockFrozenMs: data.clockFrozenMs } : {},
|
|
6086
6199
|
...data.a11yRead === true ? { a11yRead: true } : {},
|
|
6087
|
-
..."camera" in data ? { camera: parseCamera(data.camera) } : {}
|
|
6200
|
+
..."camera" in data ? { camera: parseCamera(data.camera) } : {},
|
|
6201
|
+
...parseKeyboard(data.keyboard) ? { keyboard: parseKeyboard(data.keyboard) } : {}
|
|
6088
6202
|
};
|
|
6089
6203
|
} catch {
|
|
6090
6204
|
return;
|
|
@@ -6118,11 +6232,12 @@ function injectLaunchEnv(input) {
|
|
|
6118
6232
|
...input.clockOffsetMs ? { WARDEN_CLOCK_OFFSET_MS: String(Math.trunc(input.clockOffsetMs)) } : {},
|
|
6119
6233
|
...input.clockFrozenMs === undefined ? {} : { WARDEN_CLOCK_FROZEN_MS: String(Math.trunc(input.clockFrozenMs)) },
|
|
6120
6234
|
...input.camera ? { WARDEN_CAMERA: "1" } : {},
|
|
6235
|
+
...input.keyboard ? { WARDEN_KEYBOARD: input.keyboard } : {},
|
|
6121
6236
|
...input.debug ? { WARDEN_INJECT_DEBUG: "1" } : {}
|
|
6122
6237
|
};
|
|
6123
6238
|
}
|
|
6124
6239
|
function shouldInject(input) {
|
|
6125
|
-
return input.networkActive || input.clockActive || input.cameraActive === true || input.requested;
|
|
6240
|
+
return input.networkActive || input.clockActive || input.cameraActive === true || input.keyboardActive === true || input.requested;
|
|
6126
6241
|
}
|
|
6127
6242
|
function locateInjectDylib(input) {
|
|
6128
6243
|
const tried = [];
|
|
@@ -6207,7 +6322,13 @@ function createInjectChannel(input) {
|
|
|
6207
6322
|
current: readState,
|
|
6208
6323
|
write(next) {
|
|
6209
6324
|
fs.mkdir(dirname3(statePath));
|
|
6210
|
-
const
|
|
6325
|
+
const previous = readState();
|
|
6326
|
+
const keyboard = next.keyboard ?? previous.keyboard;
|
|
6327
|
+
const state = {
|
|
6328
|
+
...next,
|
|
6329
|
+
...keyboard === undefined ? {} : { keyboard },
|
|
6330
|
+
seq: Math.max(previous.seq + 1, Math.trunc(now()))
|
|
6331
|
+
};
|
|
6211
6332
|
fs.writeAtomic(statePath, encodeInjectState(state));
|
|
6212
6333
|
return state;
|
|
6213
6334
|
},
|
|
@@ -6224,9 +6345,14 @@ function createInjectChannel(input) {
|
|
|
6224
6345
|
}
|
|
6225
6346
|
},
|
|
6226
6347
|
reset() {
|
|
6348
|
+
const keyboard = readState().keyboard;
|
|
6227
6349
|
fs.remove(ackPath);
|
|
6228
6350
|
fs.remove(statePath);
|
|
6229
6351
|
fs.remove(injectEventsPath(statePath));
|
|
6352
|
+
if (keyboard !== undefined) {
|
|
6353
|
+
fs.mkdir(dirname3(statePath));
|
|
6354
|
+
fs.writeAtomic(statePath, encodeInjectState({ seq: 0, keyboard }));
|
|
6355
|
+
}
|
|
6230
6356
|
}
|
|
6231
6357
|
};
|
|
6232
6358
|
}
|
|
@@ -6787,8 +6913,14 @@ function defaultBuildCommand(platform, configuration, framework = "expo") {
|
|
|
6787
6913
|
if (platform === "ios") {
|
|
6788
6914
|
return `${IOS_NATIVE_PREPARE} && bunx expo run:ios ${configuration === "Release" ? "--configuration Release " : ""}${flags}`;
|
|
6789
6915
|
}
|
|
6790
|
-
return
|
|
6916
|
+
return androidGradleCommand(configuration, framework);
|
|
6917
|
+
}
|
|
6918
|
+
function androidGradleCommand(configuration, framework = "expo") {
|
|
6919
|
+
const gradle = `cd android && ./gradlew app:assemble${configuration} -PreactNativeArchitectures=${ANDROID_HOST_ABI}`;
|
|
6920
|
+
return framework === "react-native" ? gradle : `${ANDROID_NATIVE_PREPARE} && ${gradle}`;
|
|
6791
6921
|
}
|
|
6922
|
+
var ANDROID_NATIVE_PREPARE = "{ [ -f android/gradlew ] || bunx expo prebuild --platform android --no-install; }";
|
|
6923
|
+
var ANDROID_HOST_ABI = '"$([ "$(uname -m)" = arm64 ] && echo arm64-v8a || echo x86_64)"';
|
|
6792
6924
|
var DEFAULT_BUILD_COMMAND = {
|
|
6793
6925
|
ios: defaultBuildCommand("ios", "Debug"),
|
|
6794
6926
|
android: defaultBuildCommand("android", "Debug")
|
|
@@ -7365,6 +7497,63 @@ function createIosNativeAudit(o) {
|
|
|
7365
7497
|
};
|
|
7366
7498
|
}
|
|
7367
7499
|
|
|
7500
|
+
// ../../packages/e2e/src/driver/ios/ios-keyboard.ts
|
|
7501
|
+
var okVoid2 = { success: true, data: undefined };
|
|
7502
|
+
function createIosKeyboard(deps) {
|
|
7503
|
+
const { channel } = deps;
|
|
7504
|
+
let expectLoad = false;
|
|
7505
|
+
const mode = () => channel.current().keyboard ?? DEFAULT_KEYBOARD_MODE;
|
|
7506
|
+
return {
|
|
7507
|
+
mode,
|
|
7508
|
+
launchEnv() {
|
|
7509
|
+
const dylib = deps.dylib();
|
|
7510
|
+
if (!dylib.success)
|
|
7511
|
+
return;
|
|
7512
|
+
channel.reset();
|
|
7513
|
+
channel.write({});
|
|
7514
|
+
expectLoad = true;
|
|
7515
|
+
return injectLaunchEnv({
|
|
7516
|
+
dylib: dylib.data,
|
|
7517
|
+
statePath: channel.statePath,
|
|
7518
|
+
keyboard: mode(),
|
|
7519
|
+
...deps.debug ? { debug: true } : {}
|
|
7520
|
+
});
|
|
7521
|
+
},
|
|
7522
|
+
launchMode: () => ({ WARDEN_KEYBOARD: mode() }),
|
|
7523
|
+
async confirmLoaded() {
|
|
7524
|
+
if (!expectLoad)
|
|
7525
|
+
return okVoid2;
|
|
7526
|
+
expectLoad = false;
|
|
7527
|
+
if (await channel.acked(channel.current().seq, INJECT_LOAD_MS))
|
|
7528
|
+
return okVoid2;
|
|
7529
|
+
const error = driverError("E_INTERNAL", `the app did not load libwarden-inject.dylib within ${INJECT_LOAD_MS} ms: the keyboard mode would not reach it. Check the app is a simulator build, and run with WARDEN_E2E_DEBUG=1`, { detail: { statePath: channel.statePath } });
|
|
7530
|
+
return { success: false, error };
|
|
7531
|
+
},
|
|
7532
|
+
async dismiss() {
|
|
7533
|
+
if (!channel.attached())
|
|
7534
|
+
return { success: true, data: { attached: false } };
|
|
7535
|
+
const { seq: _seq, a11yRead: _read, ...rest } = channel.current();
|
|
7536
|
+
const state = channel.write({ ...rest, dismissKeyboard: true });
|
|
7537
|
+
if (await channel.acked(state.seq, INJECT_ACK_MS))
|
|
7538
|
+
return { success: true, data: { attached: true } };
|
|
7539
|
+
return driverErr("E_TIMEOUT", `the app did not acknowledge the keyboard dismissal within ${INJECT_ACK_MS} ms`, {
|
|
7540
|
+
detail: { statePath: channel.statePath }
|
|
7541
|
+
});
|
|
7542
|
+
},
|
|
7543
|
+
async keyboard(next) {
|
|
7544
|
+
const { seq: _seq, a11yRead: _read, ...rest } = channel.current();
|
|
7545
|
+
const state = channel.write({ ...rest, keyboard: next });
|
|
7546
|
+
if (!channel.attached())
|
|
7547
|
+
return { success: true, data: { mode: next, appliesOnLaunch: true } };
|
|
7548
|
+
if (await channel.acked(state.seq, INJECT_ACK_MS))
|
|
7549
|
+
return { success: true, data: { mode: next } };
|
|
7550
|
+
return driverErr("E_TIMEOUT", `the app did not acknowledge the keyboard mode within ${INJECT_ACK_MS} ms`, {
|
|
7551
|
+
detail: { statePath: channel.statePath }
|
|
7552
|
+
});
|
|
7553
|
+
}
|
|
7554
|
+
};
|
|
7555
|
+
}
|
|
7556
|
+
|
|
7368
7557
|
// ../../packages/e2e/src/driver/ios/ios-session.ts
|
|
7369
7558
|
var SHUTDOWN_TIMEOUT_MS = 500;
|
|
7370
7559
|
function swappableClient(initial, o) {
|
|
@@ -7774,6 +7963,9 @@ function optionalFields(node) {
|
|
|
7774
7963
|
fields.pid = node.pid;
|
|
7775
7964
|
return fields;
|
|
7776
7965
|
}
|
|
7966
|
+
function roleOf(node) {
|
|
7967
|
+
return node.role === "other" && node.identifier === "inputView" ? "keyboard" : node.role;
|
|
7968
|
+
}
|
|
7777
7969
|
function normalizeElements(wire, screen, prefix = "", depth = 0) {
|
|
7778
7970
|
const flat = [];
|
|
7779
7971
|
const elements = wire.map((node, index) => {
|
|
@@ -7781,7 +7973,7 @@ function normalizeElements(wire, screen, prefix = "", depth = 0) {
|
|
|
7781
7973
|
const element = {
|
|
7782
7974
|
...optionalFields(node),
|
|
7783
7975
|
id: node.id ?? path,
|
|
7784
|
-
role: node
|
|
7976
|
+
role: roleOf(node),
|
|
7785
7977
|
rawType: node.rawType,
|
|
7786
7978
|
frame: node.frame,
|
|
7787
7979
|
depth: node.depth ?? depth,
|
|
@@ -7819,6 +8011,8 @@ function toSnapshot(data, takenAt) {
|
|
|
7819
8011
|
snapshot.modal = data.modal;
|
|
7820
8012
|
if (data.fallback?.from === "axbridge")
|
|
7821
8013
|
snapshot.fellBackFrom = "axbridge";
|
|
8014
|
+
if (data.fallback?.stall)
|
|
8015
|
+
snapshot.bridgeStall = toBridgeStall(data.fallback.stall);
|
|
7822
8016
|
return snapshot;
|
|
7823
8017
|
}
|
|
7824
8018
|
|
|
@@ -8407,6 +8601,14 @@ var REBIND_TIMEOUT_MS = 1e4;
|
|
|
8407
8601
|
var COLD_BRIDGE_BUDGET_MS = 60000;
|
|
8408
8602
|
var COLD_BRIDGE_FIRST_WAIT_MS = 2000;
|
|
8409
8603
|
var COLD_BRIDGE_WAIT_MS = 5000;
|
|
8604
|
+
var BRIDGE_COLD_DEADLINE_MS = 12000;
|
|
8605
|
+
var AX_FALLBACK_MARGIN_MS = 8000;
|
|
8606
|
+
var COLD_READ_TIMEOUT_MS = BRIDGE_COLD_DEADLINE_MS + AX_FALLBACK_MARGIN_MS;
|
|
8607
|
+
function coldReadTimeoutMs(env = process.env) {
|
|
8608
|
+
const raw = Number(env.WARDEN_SIMD_BRIDGE_COLD_DEADLINE_MS);
|
|
8609
|
+
const deadline = Number.isFinite(raw) && raw > BRIDGE_COLD_DEADLINE_MS ? raw : BRIDGE_COLD_DEADLINE_MS;
|
|
8610
|
+
return deadline + AX_FALLBACK_MARGIN_MS;
|
|
8611
|
+
}
|
|
8410
8612
|
var GESTURE_MARGIN_MS = 5000;
|
|
8411
8613
|
var FRONTMOST_TIMEOUT_MS = 3000;
|
|
8412
8614
|
var LAUNCH_TIMEOUT_MS = 30000;
|
|
@@ -8444,7 +8646,7 @@ var SOCKET_RETRY_MS = 500;
|
|
|
8444
8646
|
function params(values) {
|
|
8445
8647
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
8446
8648
|
}
|
|
8447
|
-
var
|
|
8649
|
+
var okVoid3 = { success: true, data: undefined };
|
|
8448
8650
|
function fail2(error) {
|
|
8449
8651
|
return { success: false, error };
|
|
8450
8652
|
}
|
|
@@ -8480,6 +8682,23 @@ function noteSnapshotError(detail) {
|
|
|
8480
8682
|
if (typeof value === "string")
|
|
8481
8683
|
noteSpanDetail(to, value);
|
|
8482
8684
|
}
|
|
8685
|
+
const stall = bridgeStallSchema.safeParse(detail.stall);
|
|
8686
|
+
if (stall.success)
|
|
8687
|
+
noteBridgeStall(toBridgeStall(stall.data));
|
|
8688
|
+
}
|
|
8689
|
+
function noteFallback(fallback, log) {
|
|
8690
|
+
const stall = fallback.stall && toBridgeStall(fallback.stall);
|
|
8691
|
+
const why = stall ? ` (${formatBridgeStall(stall)})` : "";
|
|
8692
|
+
log?.(`[ios] snapshot fell back ${fallback.from} -> ${fallback.to}: ${fallback.reason}${why}`);
|
|
8693
|
+
noteSpanDetail("fallback", fallback.reason);
|
|
8694
|
+
if (stall)
|
|
8695
|
+
noteBridgeStall(stall);
|
|
8696
|
+
}
|
|
8697
|
+
function noteSnapshotTarget(target) {
|
|
8698
|
+
if (!target)
|
|
8699
|
+
return;
|
|
8700
|
+
noteSpanDetail("targetPid", target.pid);
|
|
8701
|
+
noteSpanDetail("targetSource", target.source);
|
|
8483
8702
|
}
|
|
8484
8703
|
function noteHelperHealth(health) {
|
|
8485
8704
|
noteSpanDetail("bridge", health.ax.bridge);
|
|
@@ -8495,6 +8714,10 @@ function noteHelperHealth(health) {
|
|
|
8495
8714
|
noteSpanDetail("coreSimulator", health.coreSimulatorVersion);
|
|
8496
8715
|
if (health.frontmost)
|
|
8497
8716
|
noteSpanDetail("frontmostUnresolvedAgoMs", health.frontmost.unresolvedAgoMs);
|
|
8717
|
+
if (health.bridgeRecycles) {
|
|
8718
|
+
noteSpanDetail("bridgeRecycles", health.bridgeRecycles.count);
|
|
8719
|
+
noteSpanDetail("bridgeRecyclesExhausted", health.bridgeRecycles.exhausted);
|
|
8720
|
+
}
|
|
8498
8721
|
}
|
|
8499
8722
|
function coldBridgeWait(error, attempt, spentMs, budgetMs) {
|
|
8500
8723
|
if (!bridgeNotReady(error))
|
|
@@ -8538,8 +8761,26 @@ function healthDetail(d) {
|
|
|
8538
8761
|
...d.ax.error !== undefined ? { axError: d.ax.error } : {},
|
|
8539
8762
|
...d.socket ? { socket: d.socket } : {},
|
|
8540
8763
|
...d.xcode ? { xcode: d.xcode } : {},
|
|
8541
|
-
...d.state ? { state: d.state } : {}
|
|
8542
|
-
|
|
8764
|
+
...d.state ? { state: d.state } : {},
|
|
8765
|
+
...d.axWedge ? { axWedge: toAxWedgeState(d.axWedge) } : {}
|
|
8766
|
+
};
|
|
8767
|
+
}
|
|
8768
|
+
var AX_WEDGE_RECOVER_MS = 75000;
|
|
8769
|
+
function toAxWedgeState(d) {
|
|
8770
|
+
const state = {
|
|
8771
|
+
restarts: d.restarts,
|
|
8772
|
+
maxRestarts: d.maxRestarts,
|
|
8773
|
+
recovering: d.recovering,
|
|
8774
|
+
...d.checked !== undefined ? { checked: d.checked } : {}
|
|
8775
|
+
};
|
|
8776
|
+
const last = d.last;
|
|
8777
|
+
if (last?.outcome === "restarted")
|
|
8778
|
+
state.last = { outcome: "restarted", agoMs: last.agoMs, ...last.ms !== undefined ? { ms: last.ms } : {} };
|
|
8779
|
+
else if (last?.outcome === "not-wedged")
|
|
8780
|
+
state.last = { outcome: "not-wedged", agoMs: last.agoMs };
|
|
8781
|
+
else if (last?.outcome === "failed")
|
|
8782
|
+
state.last = { outcome: "failed", agoMs: last.agoMs, error: last.error };
|
|
8783
|
+
return state;
|
|
8543
8784
|
}
|
|
8544
8785
|
|
|
8545
8786
|
class DriverFailure extends Error {
|
|
@@ -8562,14 +8803,15 @@ function decodeBase64(png) {
|
|
|
8562
8803
|
function predatesOp(error) {
|
|
8563
8804
|
return error.code === "E_UNSUPPORTED" || error.code === "E_BAD_REQUEST" && /unknown (input )?op/.test(error.message);
|
|
8564
8805
|
}
|
|
8565
|
-
async function launchWithInjection(net, a11y, requested) {
|
|
8806
|
+
async function launchWithInjection(net, a11y, keyboard, requested) {
|
|
8566
8807
|
const injected = net ? await net.launchEnv(requested ?? {}) : undefined;
|
|
8567
8808
|
if (injected && !injected.success)
|
|
8568
8809
|
return injected;
|
|
8569
8810
|
const forA11y = injected?.data || !a11y ? undefined : await a11y.launchEnv(requested ?? {});
|
|
8570
8811
|
if (forA11y && !forA11y.success)
|
|
8571
8812
|
return forA11y;
|
|
8572
|
-
const
|
|
8813
|
+
const carried = injected?.data ?? forA11y?.data;
|
|
8814
|
+
const env = keyboard ? carried ? { ...carried, ...keyboard.launchMode() } : keyboard.launchEnv() : carried;
|
|
8573
8815
|
return { success: true, data: env ? { ...requested, env: { ...requested?.env, ...env } } : requested };
|
|
8574
8816
|
}
|
|
8575
8817
|
function createIosDriver(swap, simctl, opts) {
|
|
@@ -8584,6 +8826,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8584
8826
|
relaunch: (bundleId) => driverRef ? driverRef.app.launch(bundleId, { relaunch: true, inject: true }) : noLeaseProxy()
|
|
8585
8827
|
});
|
|
8586
8828
|
const a11y = opts.a11y?.();
|
|
8829
|
+
const kbd = opts.keyboard?.();
|
|
8587
8830
|
const nativeAudit = opts.nativeAudit?.({ lastBundle: () => lastLaunched });
|
|
8588
8831
|
const { client } = swap;
|
|
8589
8832
|
let axFailures = 0;
|
|
@@ -8595,6 +8838,9 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8595
8838
|
let settleAxFailures = 0;
|
|
8596
8839
|
let rebindUnsupported = false;
|
|
8597
8840
|
let bridgeProven = false;
|
|
8841
|
+
let warmGeneration;
|
|
8842
|
+
const coldTimeoutMs = coldReadTimeoutMs();
|
|
8843
|
+
const readOpts = () => warmGeneration === swap.generation() ? {} : { minTimeoutMs: coldTimeoutMs };
|
|
8598
8844
|
let gestureUnsupported = false;
|
|
8599
8845
|
let appOpsOutdated = false;
|
|
8600
8846
|
const capabilities = {
|
|
@@ -8647,7 +8893,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8647
8893
|
const send = (op, p = {}, timeoutMs) => request(op, p, timeoutMs === undefined ? {} : { timeoutMs });
|
|
8648
8894
|
const sendVoid = async (op, p = {}, timeoutMs) => {
|
|
8649
8895
|
const result = await send(op, p, timeoutMs);
|
|
8650
|
-
return result.success ?
|
|
8896
|
+
return result.success ? okVoid3 : result;
|
|
8651
8897
|
};
|
|
8652
8898
|
const gestureTimeout = (durationMs, base = 0) => (durationMs ?? 0) + base + GESTURE_MARGIN_MS;
|
|
8653
8899
|
const helperOutdated = () => fail2(driverError("E_UNSUPPORTED", `this warden-simd predates the gesture op: restart the helper (warden ui close --udid ${udid}) or rebuild it (bun run --cwd packages/e2e native:build)`, { detail: { reason: "helper_outdated" } }));
|
|
@@ -8686,11 +8932,12 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8686
8932
|
if (!viaHelper.success)
|
|
8687
8933
|
return viaHelper;
|
|
8688
8934
|
note(viaHelper.data);
|
|
8689
|
-
return
|
|
8935
|
+
return okVoid3;
|
|
8690
8936
|
}
|
|
8691
8937
|
let failureDiagnosed = false;
|
|
8692
8938
|
const fetchSnapshot = async (o) => {
|
|
8693
|
-
const
|
|
8939
|
+
const generation = swap.generation();
|
|
8940
|
+
const result = await request("snapshot", params({ interactiveOnly: o?.interactiveOnly, backend: o?.backend }), readOpts());
|
|
8694
8941
|
if (!result.success) {
|
|
8695
8942
|
noteSnapshotError(result.error.detail);
|
|
8696
8943
|
if (!failureDiagnosed) {
|
|
@@ -8703,15 +8950,12 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8703
8950
|
}
|
|
8704
8951
|
failureDiagnosed = false;
|
|
8705
8952
|
const { fallback, target } = result.data;
|
|
8953
|
+
if (result.data.backend === "axbridge" && !fallback)
|
|
8954
|
+
warmGeneration = generation;
|
|
8706
8955
|
noteSpanDetail("backend", result.data.backend);
|
|
8707
|
-
if (fallback)
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
}
|
|
8711
|
-
if (target) {
|
|
8712
|
-
noteSpanDetail("targetPid", target.pid);
|
|
8713
|
-
noteSpanDetail("targetSource", target.source);
|
|
8714
|
-
}
|
|
8956
|
+
if (fallback)
|
|
8957
|
+
noteFallback(fallback, opts.log);
|
|
8958
|
+
noteSnapshotTarget(target);
|
|
8715
8959
|
return { success: true, data: toSnapshot(result.data, now()) };
|
|
8716
8960
|
};
|
|
8717
8961
|
async function snapshotOnce(o) {
|
|
@@ -8738,10 +8982,11 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8738
8982
|
async function snapshot(o) {
|
|
8739
8983
|
if (bridgeProven)
|
|
8740
8984
|
return snapshotOnce(o);
|
|
8985
|
+
const budgetMs = opts.coldBridgeBudgetMs ?? COLD_BRIDGE_BUDGET_MS;
|
|
8741
8986
|
const result = await retryColdSnapshot(() => snapshotOnce(o), {
|
|
8742
8987
|
now,
|
|
8743
8988
|
sleep,
|
|
8744
|
-
budgetMs:
|
|
8989
|
+
budgetMs: Math.min(budgetMs, readDeadlineRemaining() ?? budgetMs),
|
|
8745
8990
|
...opts.log ? { log: opts.log } : {}
|
|
8746
8991
|
});
|
|
8747
8992
|
if (result.success)
|
|
@@ -8774,7 +9019,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8774
9019
|
if (!snap.success)
|
|
8775
9020
|
throw new DriverFailure(snap.error);
|
|
8776
9021
|
return snapshotHash(snap.data);
|
|
8777
|
-
}, { quietMs: o.quietMs, timeoutMs: o.timeoutMs, now, sleep });
|
|
9022
|
+
}, { quietMs: hashFallbackQuietMs(o.quietMs), timeoutMs: o.timeoutMs, now, sleep });
|
|
8778
9023
|
noteSpanDetail("via", "hash");
|
|
8779
9024
|
return { success: true, data: { ...outcome, via: "hash" } };
|
|
8780
9025
|
} catch (error) {
|
|
@@ -8785,6 +9030,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8785
9030
|
}
|
|
8786
9031
|
async function settleAfterLaunch(bundleId) {
|
|
8787
9032
|
lastLaunched = bundleId;
|
|
9033
|
+
warmGeneration = undefined;
|
|
8788
9034
|
launchedBundle = bundleId;
|
|
8789
9035
|
if (await rebind(bundleId))
|
|
8790
9036
|
launchedBundle = undefined;
|
|
@@ -8794,6 +9040,9 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8794
9040
|
const a11yLoaded = await a11y?.confirmLoaded();
|
|
8795
9041
|
if (a11yLoaded && !a11yLoaded.success)
|
|
8796
9042
|
return a11yLoaded.error;
|
|
9043
|
+
const kbdLoaded = await kbd?.confirmLoaded();
|
|
9044
|
+
if (kbdLoaded && !kbdLoaded.success)
|
|
9045
|
+
return kbdLoaded.error;
|
|
8797
9046
|
return;
|
|
8798
9047
|
}
|
|
8799
9048
|
async function rebind(bundleId) {
|
|
@@ -8808,8 +9057,6 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8808
9057
|
return false;
|
|
8809
9058
|
}
|
|
8810
9059
|
noteRebind(reply.data);
|
|
8811
|
-
if (reply.data.bound)
|
|
8812
|
-
bridgeProven = true;
|
|
8813
9060
|
return reply.data.bound;
|
|
8814
9061
|
}
|
|
8815
9062
|
function noteSettleAxFailure(message) {
|
|
@@ -8882,7 +9129,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8882
9129
|
return info;
|
|
8883
9130
|
}
|
|
8884
9131
|
async function readAlert(backend) {
|
|
8885
|
-
const result = await request("alertButtons", params({ backend }));
|
|
9132
|
+
const result = await request("alertButtons", params({ backend }), readOpts());
|
|
8886
9133
|
if (!result.success)
|
|
8887
9134
|
return result;
|
|
8888
9135
|
return { success: true, data: result.data === null ? null : toAlertInfo(result.data) };
|
|
@@ -8895,6 +9142,18 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8895
9142
|
const result = await request("frontmost", { bundleId }, { timeoutMs: FRONTMOST_TIMEOUT_MS });
|
|
8896
9143
|
return result.success ? { success: true, data: result.data.frontmost } : result;
|
|
8897
9144
|
}
|
|
9145
|
+
async function axWedge(o) {
|
|
9146
|
+
const recover = o?.recover === true;
|
|
9147
|
+
const result = await request("axWedge", { recover }, { timeoutMs: recover ? AX_WEDGE_RECOVER_MS : 5000 });
|
|
9148
|
+
if (result.success)
|
|
9149
|
+
return { success: true, data: toAxWedgeState(result.data) };
|
|
9150
|
+
if (predatesOp(result.error)) {
|
|
9151
|
+
return driverErr("E_UNSUPPORTED", "this warden-simd predates the axWedge op", {
|
|
9152
|
+
detail: { reason: "helper_outdated" }
|
|
9153
|
+
});
|
|
9154
|
+
}
|
|
9155
|
+
return result;
|
|
9156
|
+
}
|
|
8898
9157
|
async function health() {
|
|
8899
9158
|
const result = await request("health");
|
|
8900
9159
|
if (!result.success)
|
|
@@ -8938,7 +9197,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8938
9197
|
health,
|
|
8939
9198
|
snapshot,
|
|
8940
9199
|
async elementAtPoint(point, o) {
|
|
8941
|
-
const result = await request("elementAtPoint", params({ x: point.x, y: point.y, reachability: o?.reachability }));
|
|
9200
|
+
const result = await request("elementAtPoint", params({ x: point.x, y: point.y, reachability: o?.reachability }), readOpts());
|
|
8942
9201
|
if (!result.success)
|
|
8943
9202
|
return result;
|
|
8944
9203
|
return {
|
|
@@ -8964,6 +9223,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8964
9223
|
alert,
|
|
8965
9224
|
alertOn,
|
|
8966
9225
|
isFrontmost,
|
|
9226
|
+
axWedge,
|
|
8967
9227
|
readA11y: async (snapshot) => a11y ? a11y.read(snapshot) : driverErr("E_UNSUPPORTED", "this iOS driver was opened without the injected library channel"),
|
|
8968
9228
|
a11yEvents: async (since) => a11y ? a11y.events(since) : driverErr("E_UNSUPPORTED", "this iOS driver was opened without the injected library channel"),
|
|
8969
9229
|
auditNatively: async (options) => nativeAudit ? nativeAudit.audit(options ?? {}) : { issues: [], skipped: { source: "xcuitest", reason: "this iOS driver was opened without the runner" } },
|
|
@@ -8976,6 +9236,26 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8976
9236
|
locale: (tag, o) => settings ? settings.locale(tag, o) : noSettings(),
|
|
8977
9237
|
a11y: (op) => settings ? settings.a11y(op) : noSettings(),
|
|
8978
9238
|
battery: (op) => settings ? settings.battery(op) : noSettings(),
|
|
9239
|
+
keyboard: async (mode) => kbd ? kbd.keyboard(mode) : driverErr("E_UNSUPPORTED", "this iOS driver was opened without the injected library channel"),
|
|
9240
|
+
async dismissKeyboard() {
|
|
9241
|
+
const resigned = kbd ? await kbd.dismiss() : undefined;
|
|
9242
|
+
if (resigned !== undefined && !resigned.success)
|
|
9243
|
+
return resigned;
|
|
9244
|
+
if (resigned?.data.attached)
|
|
9245
|
+
return { success: true, data: { via: "resign" } };
|
|
9246
|
+
opts.log?.("[ios] keyboard dismissed with Enter: the app was launched without libwarden-inject.dylib, so Return may submit the focused field");
|
|
9247
|
+
const pressed = await sendVoid("key", params({ usage: KEY_USAGE.Enter }));
|
|
9248
|
+
return pressed.success ? { success: true, data: { via: "key" } } : pressed;
|
|
9249
|
+
},
|
|
9250
|
+
async keyboardState() {
|
|
9251
|
+
const taken = await snapshot();
|
|
9252
|
+
if (!taken.success)
|
|
9253
|
+
return taken;
|
|
9254
|
+
return {
|
|
9255
|
+
success: true,
|
|
9256
|
+
data: { mode: kbd?.mode() ?? DEFAULT_KEYBOARD_MODE, visible: keyboardVisible(taken.data) }
|
|
9257
|
+
};
|
|
9258
|
+
},
|
|
8979
9259
|
camera: async (op) => net ? net.camera(op) : driverErr("E_UNSUPPORTED", "the simulated camera on iOS needs a device lease for the injected dylib: run the spec with `warden e2e`, or `warden ui`; a bare `bun test` has no lease"),
|
|
8980
9260
|
memoryWarning: (o) => settings ? settings.memoryWarning(o) : noSettings(),
|
|
8981
9261
|
push: (payload, o) => settings ? settings.push(payload, o) : noSettings(),
|
|
@@ -8997,7 +9277,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8997
9277
|
app: {
|
|
8998
9278
|
launch: async (bundleId, plain) => {
|
|
8999
9279
|
const requested = settings ? withStagedSettings(plain, settings.launchSettings()) : plain;
|
|
9000
|
-
const withEnv = await launchWithInjection(net, a11y, requested);
|
|
9280
|
+
const withEnv = await launchWithInjection(net, a11y, kbd, requested);
|
|
9001
9281
|
if (!withEnv.success)
|
|
9002
9282
|
return withEnv;
|
|
9003
9283
|
const o = withEnv.data;
|
|
@@ -9133,6 +9413,7 @@ async function openIosDriver(o) {
|
|
|
9133
9413
|
...o.coldBridgeBudgetMs !== undefined ? { coldBridgeBudgetMs: o.coldBridgeBudgetMs } : {},
|
|
9134
9414
|
...network ? { network } : {},
|
|
9135
9415
|
a11y: () => iosA11yFor(o, now, sleep),
|
|
9416
|
+
keyboard: () => iosKeyboardFor(o, now, sleep),
|
|
9136
9417
|
nativeAudit: (ctx) => nativeAuditFor(o, exec, log, ctx),
|
|
9137
9418
|
settings: (ctx) => settingsFor(o, exec, ctx),
|
|
9138
9419
|
...o.clockRecorder ? { clockRecorder: o.clockRecorder } : {}
|
|
@@ -9172,6 +9453,28 @@ function iosNetworkFactory(o, simctl, now, sleep) {
|
|
|
9172
9453
|
...o.debug ? { debug: true } : {}
|
|
9173
9454
|
});
|
|
9174
9455
|
}
|
|
9456
|
+
function iosKeyboardFor(o, now, sleep) {
|
|
9457
|
+
const wardenHome2 = o.wardenHome ?? wardenHome();
|
|
9458
|
+
return createIosKeyboard({
|
|
9459
|
+
channel: createInjectChannel({
|
|
9460
|
+
statePath: injectStatePath2(wardenHome2, o.udid),
|
|
9461
|
+
now,
|
|
9462
|
+
sleep,
|
|
9463
|
+
...o.bundleId ? { bundleId: () => o.bundleId } : {},
|
|
9464
|
+
...o.injectFs ? { fs: o.injectFs } : {}
|
|
9465
|
+
}),
|
|
9466
|
+
dylib: () => locateInjectDylib({
|
|
9467
|
+
env: { [INJECT_DYLIB_ENV]: process.env[INJECT_DYLIB_ENV], WARDEN_HELPER_DIR: o.env?.helperDir },
|
|
9468
|
+
execPath: o.execPath ?? process.execPath,
|
|
9469
|
+
importDir: o.importDir ?? import.meta.dir,
|
|
9470
|
+
wardenHome: wardenHome2,
|
|
9471
|
+
exists: o.exists ?? existsSync8,
|
|
9472
|
+
realpath: o.realpath ?? realpathSync2,
|
|
9473
|
+
...o.version ? { version: o.version } : {}
|
|
9474
|
+
}),
|
|
9475
|
+
...o.debug ? { debug: true } : {}
|
|
9476
|
+
});
|
|
9477
|
+
}
|
|
9175
9478
|
function iosA11yFor(o, now, sleep) {
|
|
9176
9479
|
const wardenHome2 = o.wardenHome ?? wardenHome();
|
|
9177
9480
|
return createIosA11y({
|
|
@@ -9415,7 +9718,7 @@ var SCREENSHOT_FALLBACK2 = new Set(["E_UNSUPPORTED", "E_IO", "E_INTERNAL"]);
|
|
|
9415
9718
|
function params2(values) {
|
|
9416
9719
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
9417
9720
|
}
|
|
9418
|
-
var
|
|
9721
|
+
var okVoid4 = { success: true, data: undefined };
|
|
9419
9722
|
function fail3(error) {
|
|
9420
9723
|
return { success: false, error };
|
|
9421
9724
|
}
|
|
@@ -9448,7 +9751,7 @@ function createIosDeviceDriver(client, devicectl, opts) {
|
|
|
9448
9751
|
}
|
|
9449
9752
|
const sendVoid = async (op, p = {}, timeoutMs) => {
|
|
9450
9753
|
const result = await request(op, p, timeoutMs === undefined ? {} : { timeoutMs });
|
|
9451
|
-
return result.success ?
|
|
9754
|
+
return result.success ? okVoid4 : result;
|
|
9452
9755
|
};
|
|
9453
9756
|
async function snapshot(o) {
|
|
9454
9757
|
const result = await request("snapshot", params2({ interactiveOnly: o?.interactiveOnly, backend: o?.backend }));
|
|
@@ -9466,7 +9769,12 @@ function createIosDeviceDriver(client, devicectl, opts) {
|
|
|
9466
9769
|
if (!snap.success)
|
|
9467
9770
|
throw new DriverFailure2(snap.error);
|
|
9468
9771
|
return snapshotHash(snap.data);
|
|
9469
|
-
}, {
|
|
9772
|
+
}, {
|
|
9773
|
+
quietMs: hashFallbackQuietMs(o.quietMs ?? DEFAULT_QUIET_MS2),
|
|
9774
|
+
timeoutMs: o.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS3,
|
|
9775
|
+
now,
|
|
9776
|
+
sleep
|
|
9777
|
+
});
|
|
9470
9778
|
noteSpanDetail("via", "hash");
|
|
9471
9779
|
return { success: true, data: { ...outcome, via: "hash" } };
|
|
9472
9780
|
} catch (error) {
|
|
@@ -9578,7 +9886,7 @@ function createIosDeviceDriver(client, devicectl, opts) {
|
|
|
9578
9886
|
return installed;
|
|
9579
9887
|
for (const bundleId of installed.data.bundleIds)
|
|
9580
9888
|
owned.add(bundleId);
|
|
9581
|
-
return
|
|
9889
|
+
return okVoid4;
|
|
9582
9890
|
}
|
|
9583
9891
|
async function uninstall(bundleId) {
|
|
9584
9892
|
const refused = guard(bundleId, "uninstall");
|
|
@@ -9617,6 +9925,11 @@ function createIosDeviceDriver(client, devicectl, opts) {
|
|
|
9617
9925
|
gesture: async () => unsupported2("gesture", "the XCUITest runner takes taps, swipes and drags only"),
|
|
9618
9926
|
biometrics: async () => unsupported2("biometrics", "Face ID / Touch ID cannot be simulated on a real sensor; use a simulator"),
|
|
9619
9927
|
...physicalConditionOps(`physical iPhone ${udid}`),
|
|
9928
|
+
dismissKeyboard: async () => {
|
|
9929
|
+
const pressed = await sendVoid("key", params2({ usage: KEY_USAGE.Enter }));
|
|
9930
|
+
return pressed.success ? { success: true, data: { via: "key" } } : pressed;
|
|
9931
|
+
},
|
|
9932
|
+
keyboardState: async () => unsupported2("the keyboard mode", "the injected library runs in simulator apps only"),
|
|
9620
9933
|
typeText: (text) => sendVoid("typeText", { text }, deviceTypeTextBudgetMs(text)),
|
|
9621
9934
|
key: (key, modifiers) => sendVoid("key", params2({ usage: KEY_USAGE[key], modifiers: modifiers && [...modifiers] })),
|
|
9622
9935
|
pressButton,
|
|
@@ -10339,6 +10652,10 @@ function createDevice({
|
|
|
10339
10652
|
locale: conditions.locale,
|
|
10340
10653
|
a11y: conditions.a11y,
|
|
10341
10654
|
battery: conditions.battery,
|
|
10655
|
+
keyboard: conditions.keyboard,
|
|
10656
|
+
async keyboardVisible() {
|
|
10657
|
+
return (await unwrap(() => driver.keyboardState(), "read the keyboard", signal())).visible;
|
|
10658
|
+
},
|
|
10342
10659
|
camera: conditions.camera,
|
|
10343
10660
|
memoryWarning: conditions.memoryWarning,
|
|
10344
10661
|
push: conditions.push,
|
|
@@ -10408,13 +10725,9 @@ function createDevice({
|
|
|
10408
10725
|
await unwrap(() => driver.clipboard(text), "set clipboard", signal());
|
|
10409
10726
|
},
|
|
10410
10727
|
async hideKeyboard() {
|
|
10411
|
-
|
|
10412
|
-
if (!snapshot.flat.some((element) => element.role === "keyboard"))
|
|
10728
|
+
if (!await device.keyboardVisible())
|
|
10413
10729
|
return;
|
|
10414
|
-
|
|
10415
|
-
await unwrap(() => driver.key("Enter"), "hide keyboard", signal());
|
|
10416
|
-
else
|
|
10417
|
-
await unwrap(() => driver.pressButton("back"), "hide keyboard", signal());
|
|
10730
|
+
await unwrap(() => driver.dismissKeyboard(), "hide keyboard", signal());
|
|
10418
10731
|
},
|
|
10419
10732
|
async screenshot(name) {
|
|
10420
10733
|
const dir = session.attemptDir ?? join22(session.out, "adhoc");
|
|
@@ -10455,7 +10768,7 @@ function createDevice({
|
|
|
10455
10768
|
if (native.error.code !== "E_UNSUPPORTED")
|
|
10456
10769
|
throw toE2eError(native.error, "settle");
|
|
10457
10770
|
const polled = await settleByHash(async () => snapshotHash(await readSnapshot()), {
|
|
10458
|
-
quietMs,
|
|
10771
|
+
quietMs: hashFallbackQuietMs(quietMs),
|
|
10459
10772
|
timeoutMs,
|
|
10460
10773
|
now: clock.now,
|
|
10461
10774
|
sleep: clock.sleep
|
|
@@ -10534,13 +10847,11 @@ var EXCERPT_LINES = 40;
|
|
|
10534
10847
|
var MAX_ALERT_ANSWERS = 3;
|
|
10535
10848
|
var REACT_THROTTLE_MS = 450;
|
|
10536
10849
|
var REACT_TIMEOUT_MS = 2000;
|
|
10537
|
-
var FIRST_READER_RETRY_MS = 5000;
|
|
10538
10850
|
|
|
10539
10851
|
class Runtime {
|
|
10540
10852
|
host;
|
|
10541
10853
|
reactCache;
|
|
10542
10854
|
indexed = new WeakMap;
|
|
10543
|
-
firstReaderRetryAt = 0;
|
|
10544
10855
|
constructor(host) {
|
|
10545
10856
|
this.host = host;
|
|
10546
10857
|
}
|
|
@@ -10580,7 +10891,7 @@ class Runtime {
|
|
|
10580
10891
|
const result = canObserve(driver) ? await driver.observe({ reuse }) : await driver.snapshot();
|
|
10581
10892
|
if (!result.success)
|
|
10582
10893
|
return { ok: false, error: result.error };
|
|
10583
|
-
const snapshot =
|
|
10894
|
+
const snapshot = result.data;
|
|
10584
10895
|
const observation = {
|
|
10585
10896
|
ok: true,
|
|
10586
10897
|
observed: { driver, snapshot, index: this.indexOf(snapshot) }
|
|
@@ -10594,16 +10905,6 @@ class Runtime {
|
|
|
10594
10905
|
reuse = false;
|
|
10595
10906
|
}
|
|
10596
10907
|
}
|
|
10597
|
-
async restoreDegraded(driver, snapshot) {
|
|
10598
|
-
const first = snapshot.fellBackFrom;
|
|
10599
|
-
if (first === undefined || this.clock.now() < this.firstReaderRetryAt)
|
|
10600
|
-
return snapshot;
|
|
10601
|
-
const again = await driver.snapshot({ backend: first });
|
|
10602
|
-
if (again.success && again.data.backend === first)
|
|
10603
|
-
return again.data;
|
|
10604
|
-
this.firstReaderRetryAt = this.clock.now() + FIRST_READER_RETRY_MS;
|
|
10605
|
-
return snapshot;
|
|
10606
|
-
}
|
|
10607
10908
|
earlierThan(driver, before, look) {
|
|
10608
10909
|
const after = canObserve(driver) ? driver.previous?.() : undefined;
|
|
10609
10910
|
const same = after !== undefined && after.generation === before.generation && after.snapshot === look;
|
|
@@ -10640,18 +10941,25 @@ class Runtime {
|
|
|
10640
10941
|
throw driverFailure("snapshot", result.last ?? { code: "E_TIMEOUT", message: "no snapshot", retryable: true });
|
|
10641
10942
|
}
|
|
10642
10943
|
async reactMatches(testId, observed) {
|
|
10944
|
+
const lookup = await this.reactLookup(testId, observed);
|
|
10945
|
+
return lookup.answered ? lookup.matches : [];
|
|
10946
|
+
}
|
|
10947
|
+
hasReactTree() {
|
|
10948
|
+
return this.host.devtools?.() !== undefined;
|
|
10949
|
+
}
|
|
10950
|
+
async reactLookup(testId, observed) {
|
|
10643
10951
|
const facet = this.host.devtools?.();
|
|
10644
10952
|
if (facet === undefined)
|
|
10645
|
-
return
|
|
10953
|
+
return { answered: false };
|
|
10646
10954
|
const key = typeof testId === "string" ? `s:${testId}` : `r:${testId.toString()}`;
|
|
10647
10955
|
const cached = this.reactCache;
|
|
10648
10956
|
if (cached !== undefined && cached.key === key && this.clock.now() - cached.at < REACT_THROTTLE_MS) {
|
|
10649
|
-
return cached.
|
|
10957
|
+
return cached.lookup;
|
|
10650
10958
|
}
|
|
10651
10959
|
const view = { platform: observed.snapshot.platform, screen: observed.snapshot.screen };
|
|
10652
|
-
const
|
|
10653
|
-
this.reactCache = { key, at: this.clock.now(),
|
|
10654
|
-
return
|
|
10960
|
+
const lookup = await lookUpTestIdViaReact(facet, testId, view, { timeoutMs: REACT_TIMEOUT_MS });
|
|
10961
|
+
this.reactCache = { key, at: this.clock.now(), lookup };
|
|
10962
|
+
return lookup;
|
|
10655
10963
|
}
|
|
10656
10964
|
excerpt(snapshot) {
|
|
10657
10965
|
const rendered = renderSnapshot(snapshot, { interactiveOnly: true, refs: new Map, maxLines: EXCERPT_LINES });
|
|
@@ -10660,13 +10968,15 @@ class Runtime {
|
|
|
10660
10968
|
}
|
|
10661
10969
|
async poll(step, timeoutMs, options = {}) {
|
|
10662
10970
|
const signal = this.host.signal;
|
|
10663
|
-
const
|
|
10971
|
+
const outer = readDeadlineRemaining();
|
|
10972
|
+
const deadline = this.clock.now() + (outer === undefined ? timeoutMs : Math.min(timeoutMs, outer));
|
|
10973
|
+
const result = await withReadDeadline(() => deadline - this.clock.now(), () => poll(step, {
|
|
10664
10974
|
timeoutMs,
|
|
10665
10975
|
...options.intervalMs === undefined ? {} : { intervalMs: options.intervalMs },
|
|
10666
10976
|
...signal === undefined ? {} : { signal },
|
|
10667
10977
|
now: this.clock.now,
|
|
10668
10978
|
sleep: this.clock.sleep
|
|
10669
|
-
});
|
|
10979
|
+
}));
|
|
10670
10980
|
if (result.status === "aborted")
|
|
10671
10981
|
throw abortError(signal);
|
|
10672
10982
|
return result;
|
|
@@ -10839,6 +11149,8 @@ function traceDevice(device, span) {
|
|
|
10839
11149
|
locale: (tag, options) => span("device.locale", () => device.locale(tag, options)),
|
|
10840
11150
|
a11y: (op) => span("device.a11y", () => device.a11y(op)),
|
|
10841
11151
|
battery: (op) => span("device.battery", () => device.battery(op)),
|
|
11152
|
+
keyboard: (mode) => span("device.keyboard", () => device.keyboard(mode)),
|
|
11153
|
+
keyboardVisible: () => span("device.keyboardVisible", () => device.keyboardVisible()),
|
|
10842
11154
|
camera: {
|
|
10843
11155
|
set: (path, options) => span("device.camera.set", () => camera.set(path, options)),
|
|
10844
11156
|
clear: (facing) => span("device.camera.clear", () => camera.clear(facing)),
|