@delacour/warden-e2e 0.12.1 → 0.14.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-rbbn127e.mjs → chunk-zk3wxa70.mjs} +557 -230
- package/index.mjs +226 -12
- package/package.json +2 -2
- package/preload.mjs +125 -25
- package/types/core/src/affected/affected.schema.d.ts +2 -0
- package/types/core/src/builds/config.schema.d.ts +1 -0
- package/types/core/src/builds/define-config.d.ts +10 -0
- package/types/e2e/src/driver/bridge-stall.d.ts +37 -0
- package/types/e2e/src/driver/driver.types.d.ts +9 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +22 -1
- package/types/e2e/src/driver/ios/simd-client.d.ts +2 -0
- package/types/e2e/src/driver/ios/simd-protocol.d.ts +30 -0
- package/types/e2e/src/driver/settle.d.ts +8 -0
- package/types/e2e/src/errors.d.ts +3 -1
- package/types/e2e/src/expect/matchers.d.ts +5 -0
- package/types/e2e/src/index.d.ts +2 -0
- package/types/e2e/src/run.d.ts +48 -0
- 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/context.d.ts +8 -1
- package/types/e2e/src/screen/degraded-read.d.ts +23 -0
- package/types/e2e/src/screen/locator.d.ts +46 -1
- 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 +2 -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,
|
|
@@ -35,7 +36,7 @@ import {
|
|
|
35
36
|
selectorExpression,
|
|
36
37
|
renderSnapshot,
|
|
37
38
|
renderHeader,
|
|
38
|
-
|
|
39
|
+
lookUpTestIdViaReact,
|
|
39
40
|
isLocator,
|
|
40
41
|
Locator,
|
|
41
42
|
scrollUntilVisible,
|
|
@@ -44,6 +45,10 @@ import {
|
|
|
44
45
|
execError,
|
|
45
46
|
driverError,
|
|
46
47
|
driverErr,
|
|
48
|
+
bridgeStallSchema,
|
|
49
|
+
toBridgeStall,
|
|
50
|
+
noteBridgeStall,
|
|
51
|
+
formatBridgeStall,
|
|
47
52
|
logEventSchema,
|
|
48
53
|
fatalEventSchema,
|
|
49
54
|
responseSchema,
|
|
@@ -109,7 +114,7 @@ import {
|
|
|
109
114
|
onlyWasSeen,
|
|
110
115
|
WORKSPACE_VERSION,
|
|
111
116
|
WARDEN_E2E_VERSION2
|
|
112
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-zk3wxa70.mjs";
|
|
113
118
|
|
|
114
119
|
// ../../packages/e2e/src/preload.ts
|
|
115
120
|
import { afterAll, setDefaultTimeout } from "bun:test";
|
|
@@ -7728,6 +7733,7 @@ function locateHelper(input) {
|
|
|
7728
7733
|
function resolveHelper(o) {
|
|
7729
7734
|
if (o.helperPath !== undefined)
|
|
7730
7735
|
return { success: true, data: o.helperPath };
|
|
7736
|
+
const version = o.version ?? o.env?.wardenVersion;
|
|
7731
7737
|
const located = locateHelper({
|
|
7732
7738
|
env: { [ENV_NAMES.simd]: o.env?.simd, [ENV_NAMES.helperDir]: o.env?.helperDir },
|
|
7733
7739
|
execPath: o.execPath ?? process.execPath,
|
|
@@ -7735,7 +7741,7 @@ function resolveHelper(o) {
|
|
|
7735
7741
|
wardenHome: o.wardenHome ?? wardenHome(),
|
|
7736
7742
|
exists: o.exists ?? existsSync7,
|
|
7737
7743
|
realpath: o.realpath ?? realpathSync,
|
|
7738
|
-
...
|
|
7744
|
+
...version ? { version } : {}
|
|
7739
7745
|
});
|
|
7740
7746
|
return located.success ? { success: true, data: located.data.path } : located;
|
|
7741
7747
|
}
|
|
@@ -7816,6 +7822,10 @@ function toSnapshot(data, takenAt) {
|
|
|
7816
7822
|
};
|
|
7817
7823
|
if (data.modal)
|
|
7818
7824
|
snapshot.modal = data.modal;
|
|
7825
|
+
if (data.fallback?.from === "axbridge")
|
|
7826
|
+
snapshot.fellBackFrom = "axbridge";
|
|
7827
|
+
if (data.fallback?.stall)
|
|
7828
|
+
snapshot.bridgeStall = toBridgeStall(data.fallback.stall);
|
|
7819
7829
|
return snapshot;
|
|
7820
7830
|
}
|
|
7821
7831
|
|
|
@@ -8401,6 +8411,17 @@ var DEFAULT_QUIET_MS = 250;
|
|
|
8401
8411
|
var DEFAULT_SETTLE_TIMEOUT_MS2 = 5000;
|
|
8402
8412
|
var SETTLE_MARGIN_MS = 2000;
|
|
8403
8413
|
var REBIND_TIMEOUT_MS = 1e4;
|
|
8414
|
+
var COLD_BRIDGE_BUDGET_MS = 60000;
|
|
8415
|
+
var COLD_BRIDGE_FIRST_WAIT_MS = 2000;
|
|
8416
|
+
var COLD_BRIDGE_WAIT_MS = 5000;
|
|
8417
|
+
var BRIDGE_COLD_DEADLINE_MS = 12000;
|
|
8418
|
+
var AX_FALLBACK_MARGIN_MS = 8000;
|
|
8419
|
+
var COLD_READ_TIMEOUT_MS = BRIDGE_COLD_DEADLINE_MS + AX_FALLBACK_MARGIN_MS;
|
|
8420
|
+
function coldReadTimeoutMs(env = process.env) {
|
|
8421
|
+
const raw = Number(env.WARDEN_SIMD_BRIDGE_COLD_DEADLINE_MS);
|
|
8422
|
+
const deadline = Number.isFinite(raw) && raw > BRIDGE_COLD_DEADLINE_MS ? raw : BRIDGE_COLD_DEADLINE_MS;
|
|
8423
|
+
return deadline + AX_FALLBACK_MARGIN_MS;
|
|
8424
|
+
}
|
|
8404
8425
|
var GESTURE_MARGIN_MS = 5000;
|
|
8405
8426
|
var FRONTMOST_TIMEOUT_MS = 3000;
|
|
8406
8427
|
var LAUNCH_TIMEOUT_MS = 30000;
|
|
@@ -8474,6 +8495,23 @@ function noteSnapshotError(detail) {
|
|
|
8474
8495
|
if (typeof value === "string")
|
|
8475
8496
|
noteSpanDetail(to, value);
|
|
8476
8497
|
}
|
|
8498
|
+
const stall = bridgeStallSchema.safeParse(detail.stall);
|
|
8499
|
+
if (stall.success)
|
|
8500
|
+
noteBridgeStall(toBridgeStall(stall.data));
|
|
8501
|
+
}
|
|
8502
|
+
function noteFallback(fallback, log) {
|
|
8503
|
+
const stall = fallback.stall && toBridgeStall(fallback.stall);
|
|
8504
|
+
const why = stall ? ` (${formatBridgeStall(stall)})` : "";
|
|
8505
|
+
log?.(`[ios] snapshot fell back ${fallback.from} -> ${fallback.to}: ${fallback.reason}${why}`);
|
|
8506
|
+
noteSpanDetail("fallback", fallback.reason);
|
|
8507
|
+
if (stall)
|
|
8508
|
+
noteBridgeStall(stall);
|
|
8509
|
+
}
|
|
8510
|
+
function noteSnapshotTarget(target) {
|
|
8511
|
+
if (!target)
|
|
8512
|
+
return;
|
|
8513
|
+
noteSpanDetail("targetPid", target.pid);
|
|
8514
|
+
noteSpanDetail("targetSource", target.source);
|
|
8477
8515
|
}
|
|
8478
8516
|
function noteHelperHealth(health) {
|
|
8479
8517
|
noteSpanDetail("bridge", health.ax.bridge);
|
|
@@ -8489,6 +8527,36 @@ function noteHelperHealth(health) {
|
|
|
8489
8527
|
noteSpanDetail("coreSimulator", health.coreSimulatorVersion);
|
|
8490
8528
|
if (health.frontmost)
|
|
8491
8529
|
noteSpanDetail("frontmostUnresolvedAgoMs", health.frontmost.unresolvedAgoMs);
|
|
8530
|
+
if (health.bridgeRecycles) {
|
|
8531
|
+
noteSpanDetail("bridgeRecycles", health.bridgeRecycles.count);
|
|
8532
|
+
noteSpanDetail("bridgeRecyclesExhausted", health.bridgeRecycles.exhausted);
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
function coldBridgeWait(error, attempt, spentMs, budgetMs) {
|
|
8536
|
+
if (!bridgeNotReady(error))
|
|
8537
|
+
return;
|
|
8538
|
+
const wait = attempt === 1 ? COLD_BRIDGE_FIRST_WAIT_MS : COLD_BRIDGE_WAIT_MS;
|
|
8539
|
+
return spentMs + wait >= budgetMs ? undefined : wait;
|
|
8540
|
+
}
|
|
8541
|
+
async function retryColdSnapshot(read, ctx) {
|
|
8542
|
+
const started = ctx.now();
|
|
8543
|
+
let waited = 0;
|
|
8544
|
+
for (let attempt = 1;; attempt++) {
|
|
8545
|
+
const result = await read();
|
|
8546
|
+
const spent = Math.max(ctx.now() - started, waited);
|
|
8547
|
+
const wait = result.success ? undefined : coldBridgeWait(result.error, attempt, spent, ctx.budgetMs);
|
|
8548
|
+
if (wait === undefined) {
|
|
8549
|
+
if (attempt > 1)
|
|
8550
|
+
noteSpanDetail("coldBridgeMs", spent);
|
|
8551
|
+
return result;
|
|
8552
|
+
}
|
|
8553
|
+
ctx.log?.(`[ios] snapshot try ${attempt} failed, the bridge is not up yet: retrying`);
|
|
8554
|
+
await ctx.sleep(wait);
|
|
8555
|
+
waited += wait;
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
function bridgeNotReady(error) {
|
|
8559
|
+
return error.code === "E_TIMEOUT" || error.code === "E_AX_UNAVAILABLE";
|
|
8492
8560
|
}
|
|
8493
8561
|
function noteRebind(data) {
|
|
8494
8562
|
noteSpanDetail("rebind", data.bound ? "bound" : data.reason ?? "unbound");
|
|
@@ -8562,6 +8630,10 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8562
8630
|
let settleUnsupported = !nativeSettle;
|
|
8563
8631
|
let settleAxFailures = 0;
|
|
8564
8632
|
let rebindUnsupported = false;
|
|
8633
|
+
let bridgeProven = false;
|
|
8634
|
+
let warmGeneration;
|
|
8635
|
+
const coldTimeoutMs = coldReadTimeoutMs();
|
|
8636
|
+
const readOpts = () => warmGeneration === swap.generation() ? {} : { minTimeoutMs: coldTimeoutMs };
|
|
8565
8637
|
let gestureUnsupported = false;
|
|
8566
8638
|
let appOpsOutdated = false;
|
|
8567
8639
|
const capabilities = {
|
|
@@ -8657,7 +8729,8 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8657
8729
|
}
|
|
8658
8730
|
let failureDiagnosed = false;
|
|
8659
8731
|
const fetchSnapshot = async (o) => {
|
|
8660
|
-
const
|
|
8732
|
+
const generation = swap.generation();
|
|
8733
|
+
const result = await request("snapshot", params({ interactiveOnly: o?.interactiveOnly, backend: o?.backend }), readOpts());
|
|
8661
8734
|
if (!result.success) {
|
|
8662
8735
|
noteSnapshotError(result.error.detail);
|
|
8663
8736
|
if (!failureDiagnosed) {
|
|
@@ -8670,18 +8743,15 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8670
8743
|
}
|
|
8671
8744
|
failureDiagnosed = false;
|
|
8672
8745
|
const { fallback, target } = result.data;
|
|
8746
|
+
if (result.data.backend === "axbridge" && !fallback)
|
|
8747
|
+
warmGeneration = generation;
|
|
8673
8748
|
noteSpanDetail("backend", result.data.backend);
|
|
8674
|
-
if (fallback)
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
}
|
|
8678
|
-
if (target) {
|
|
8679
|
-
noteSpanDetail("targetPid", target.pid);
|
|
8680
|
-
noteSpanDetail("targetSource", target.source);
|
|
8681
|
-
}
|
|
8749
|
+
if (fallback)
|
|
8750
|
+
noteFallback(fallback, opts.log);
|
|
8751
|
+
noteSnapshotTarget(target);
|
|
8682
8752
|
return { success: true, data: toSnapshot(result.data, now()) };
|
|
8683
8753
|
};
|
|
8684
|
-
async function
|
|
8754
|
+
async function snapshotOnce(o) {
|
|
8685
8755
|
const generation = swap.generation();
|
|
8686
8756
|
const first = await fetchSnapshot(o);
|
|
8687
8757
|
if (first.success) {
|
|
@@ -8702,6 +8772,19 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8702
8772
|
axFailures = 0;
|
|
8703
8773
|
return fetchSnapshot(o);
|
|
8704
8774
|
}
|
|
8775
|
+
async function snapshot(o) {
|
|
8776
|
+
if (bridgeProven)
|
|
8777
|
+
return snapshotOnce(o);
|
|
8778
|
+
const result = await retryColdSnapshot(() => snapshotOnce(o), {
|
|
8779
|
+
now,
|
|
8780
|
+
sleep,
|
|
8781
|
+
budgetMs: opts.coldBridgeBudgetMs ?? COLD_BRIDGE_BUDGET_MS,
|
|
8782
|
+
...opts.log ? { log: opts.log } : {}
|
|
8783
|
+
});
|
|
8784
|
+
if (result.success)
|
|
8785
|
+
bridgeProven = true;
|
|
8786
|
+
return result;
|
|
8787
|
+
}
|
|
8705
8788
|
async function typeText(text) {
|
|
8706
8789
|
const typed = await sendVoid("typeText", { text }, typeTextBudgetMs(text));
|
|
8707
8790
|
if (typed.success || typed.error.code !== "E_UNSUPPORTED_TEXT")
|
|
@@ -8728,7 +8811,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8728
8811
|
if (!snap.success)
|
|
8729
8812
|
throw new DriverFailure(snap.error);
|
|
8730
8813
|
return snapshotHash(snap.data);
|
|
8731
|
-
}, { quietMs: o.quietMs, timeoutMs: o.timeoutMs, now, sleep });
|
|
8814
|
+
}, { quietMs: hashFallbackQuietMs(o.quietMs), timeoutMs: o.timeoutMs, now, sleep });
|
|
8732
8815
|
noteSpanDetail("via", "hash");
|
|
8733
8816
|
return { success: true, data: { ...outcome, via: "hash" } };
|
|
8734
8817
|
} catch (error) {
|
|
@@ -8739,6 +8822,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8739
8822
|
}
|
|
8740
8823
|
async function settleAfterLaunch(bundleId) {
|
|
8741
8824
|
lastLaunched = bundleId;
|
|
8825
|
+
warmGeneration = undefined;
|
|
8742
8826
|
launchedBundle = bundleId;
|
|
8743
8827
|
if (await rebind(bundleId))
|
|
8744
8828
|
launchedBundle = undefined;
|
|
@@ -8762,6 +8846,8 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8762
8846
|
return false;
|
|
8763
8847
|
}
|
|
8764
8848
|
noteRebind(reply.data);
|
|
8849
|
+
if (reply.data.bound)
|
|
8850
|
+
bridgeProven = true;
|
|
8765
8851
|
return reply.data.bound;
|
|
8766
8852
|
}
|
|
8767
8853
|
function noteSettleAxFailure(message) {
|
|
@@ -8834,7 +8920,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8834
8920
|
return info;
|
|
8835
8921
|
}
|
|
8836
8922
|
async function readAlert(backend) {
|
|
8837
|
-
const result = await request("alertButtons", params({ backend }));
|
|
8923
|
+
const result = await request("alertButtons", params({ backend }), readOpts());
|
|
8838
8924
|
if (!result.success)
|
|
8839
8925
|
return result;
|
|
8840
8926
|
return { success: true, data: result.data === null ? null : toAlertInfo(result.data) };
|
|
@@ -8890,7 +8976,7 @@ function createIosDriver(swap, simctl, opts) {
|
|
|
8890
8976
|
health,
|
|
8891
8977
|
snapshot,
|
|
8892
8978
|
async elementAtPoint(point, o) {
|
|
8893
|
-
const result = await request("elementAtPoint", params({ x: point.x, y: point.y, reachability: o?.reachability }));
|
|
8979
|
+
const result = await request("elementAtPoint", params({ x: point.x, y: point.y, reachability: o?.reachability }), readOpts());
|
|
8894
8980
|
if (!result.success)
|
|
8895
8981
|
return result;
|
|
8896
8982
|
return {
|
|
@@ -9082,6 +9168,7 @@ async function openIosDriver(o) {
|
|
|
9082
9168
|
now,
|
|
9083
9169
|
sleep,
|
|
9084
9170
|
log,
|
|
9171
|
+
...o.coldBridgeBudgetMs !== undefined ? { coldBridgeBudgetMs: o.coldBridgeBudgetMs } : {},
|
|
9085
9172
|
...network ? { network } : {},
|
|
9086
9173
|
a11y: () => iosA11yFor(o, now, sleep),
|
|
9087
9174
|
nativeAudit: (ctx) => nativeAuditFor(o, exec, log, ctx),
|
|
@@ -9417,7 +9504,12 @@ function createIosDeviceDriver(client, devicectl, opts) {
|
|
|
9417
9504
|
if (!snap.success)
|
|
9418
9505
|
throw new DriverFailure2(snap.error);
|
|
9419
9506
|
return snapshotHash(snap.data);
|
|
9420
|
-
}, {
|
|
9507
|
+
}, {
|
|
9508
|
+
quietMs: hashFallbackQuietMs(o.quietMs ?? DEFAULT_QUIET_MS2),
|
|
9509
|
+
timeoutMs: o.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS3,
|
|
9510
|
+
now,
|
|
9511
|
+
sleep
|
|
9512
|
+
});
|
|
9421
9513
|
noteSpanDetail("via", "hash");
|
|
9422
9514
|
return { success: true, data: { ...outcome, via: "hash" } };
|
|
9423
9515
|
} catch (error) {
|
|
@@ -10406,7 +10498,7 @@ function createDevice({
|
|
|
10406
10498
|
if (native.error.code !== "E_UNSUPPORTED")
|
|
10407
10499
|
throw toE2eError(native.error, "settle");
|
|
10408
10500
|
const polled = await settleByHash(async () => snapshotHash(await readSnapshot()), {
|
|
10409
|
-
quietMs,
|
|
10501
|
+
quietMs: hashFallbackQuietMs(quietMs),
|
|
10410
10502
|
timeoutMs,
|
|
10411
10503
|
now: clock.now,
|
|
10412
10504
|
sleep: clock.sleep
|
|
@@ -10529,15 +10621,16 @@ class Runtime {
|
|
|
10529
10621
|
const result = canObserve(driver) ? await driver.observe({ reuse }) : await driver.snapshot();
|
|
10530
10622
|
if (!result.success)
|
|
10531
10623
|
return { ok: false, error: result.error };
|
|
10624
|
+
const snapshot = result.data;
|
|
10532
10625
|
const observation = {
|
|
10533
10626
|
ok: true,
|
|
10534
|
-
observed: { driver, snapshot
|
|
10627
|
+
observed: { driver, snapshot, index: this.indexOf(snapshot) }
|
|
10535
10628
|
};
|
|
10536
10629
|
if (before !== undefined)
|
|
10537
10630
|
observation.earlier = this.earlierThan(driver, before, result.data);
|
|
10538
10631
|
if (options.alerts === false || answered >= MAX_ALERT_ANSWERS)
|
|
10539
10632
|
return observation;
|
|
10540
|
-
if (!await this.answeredAlert(driver,
|
|
10633
|
+
if (!await this.answeredAlert(driver, snapshot))
|
|
10541
10634
|
return observation;
|
|
10542
10635
|
reuse = false;
|
|
10543
10636
|
}
|
|
@@ -10578,18 +10671,25 @@ class Runtime {
|
|
|
10578
10671
|
throw driverFailure("snapshot", result.last ?? { code: "E_TIMEOUT", message: "no snapshot", retryable: true });
|
|
10579
10672
|
}
|
|
10580
10673
|
async reactMatches(testId, observed) {
|
|
10674
|
+
const lookup = await this.reactLookup(testId, observed);
|
|
10675
|
+
return lookup.answered ? lookup.matches : [];
|
|
10676
|
+
}
|
|
10677
|
+
hasReactTree() {
|
|
10678
|
+
return this.host.devtools?.() !== undefined;
|
|
10679
|
+
}
|
|
10680
|
+
async reactLookup(testId, observed) {
|
|
10581
10681
|
const facet = this.host.devtools?.();
|
|
10582
10682
|
if (facet === undefined)
|
|
10583
|
-
return
|
|
10683
|
+
return { answered: false };
|
|
10584
10684
|
const key = typeof testId === "string" ? `s:${testId}` : `r:${testId.toString()}`;
|
|
10585
10685
|
const cached = this.reactCache;
|
|
10586
10686
|
if (cached !== undefined && cached.key === key && this.clock.now() - cached.at < REACT_THROTTLE_MS) {
|
|
10587
|
-
return cached.
|
|
10687
|
+
return cached.lookup;
|
|
10588
10688
|
}
|
|
10589
10689
|
const view = { platform: observed.snapshot.platform, screen: observed.snapshot.screen };
|
|
10590
|
-
const
|
|
10591
|
-
this.reactCache = { key, at: this.clock.now(),
|
|
10592
|
-
return
|
|
10690
|
+
const lookup = await lookUpTestIdViaReact(facet, testId, view, { timeoutMs: REACT_TIMEOUT_MS });
|
|
10691
|
+
this.reactCache = { key, at: this.clock.now(), lookup };
|
|
10692
|
+
return lookup;
|
|
10593
10693
|
}
|
|
10594
10694
|
excerpt(snapshot) {
|
|
10595
10695
|
const rendered = renderSnapshot(snapshot, { interactiveOnly: true, refs: new Map, maxLines: EXCERPT_LINES });
|
|
@@ -18,6 +18,7 @@ type RunSettings = {
|
|
|
18
18
|
passes: number;
|
|
19
19
|
count?: number | undefined;
|
|
20
20
|
profile?: string | undefined;
|
|
21
|
+
runtime?: string | undefined;
|
|
21
22
|
devices?: Array<{
|
|
22
23
|
profile: string;
|
|
23
24
|
default: boolean;
|
|
@@ -141,6 +142,7 @@ export declare const e2eSuiteSchema: z.ZodObject<{
|
|
|
141
142
|
retry: z.ZodDefault<z.ZodNumber>;
|
|
142
143
|
count: z.ZodOptional<z.ZodNumber>;
|
|
143
144
|
profile: z.ZodOptional<z.ZodString>;
|
|
145
|
+
runtime: z.ZodOptional<z.ZodString>;
|
|
144
146
|
app: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
145
147
|
installMode: z.ZodOptional<z.ZodEnum<{
|
|
146
148
|
clean: "clean";
|
|
@@ -333,6 +333,7 @@ export declare const wardenConfigSchema: z.ZodObject<{
|
|
|
333
333
|
retry: z.ZodDefault<z.ZodNumber>;
|
|
334
334
|
count: z.ZodOptional<z.ZodNumber>;
|
|
335
335
|
profile: z.ZodOptional<z.ZodString>;
|
|
336
|
+
runtime: z.ZodOptional<z.ZodString>;
|
|
336
337
|
app: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
337
338
|
installMode: z.ZodOptional<z.ZodEnum<{
|
|
338
339
|
clean: "clean";
|
|
@@ -687,6 +687,16 @@ export interface WardenE2eSuite {
|
|
|
687
687
|
count?: number | undefined;
|
|
688
688
|
/** Device profile (not with `devices`). */
|
|
689
689
|
profile?: string | undefined;
|
|
690
|
+
/**
|
|
691
|
+
* iOS simulator runtime the run's devices boot on, and whose golden they clone: `latest`, `iOS-26-5` or `26.5`.
|
|
692
|
+
* Pin it so the run (locally and on CI) sees one accessibility tree: values change between iOS releases.
|
|
693
|
+
* A runtime that is not installed fails the run before any device is claimed, listing the installed ones; it never
|
|
694
|
+
* falls back to another. Applies to every `devices` leg. Skipped on Android. `--runtime` overrides it.
|
|
695
|
+
* `warden golden key --profile <profile> --runtime <runtime>` names the golden archive the run uses.
|
|
696
|
+
*
|
|
697
|
+
* @example "27.0"
|
|
698
|
+
*/
|
|
699
|
+
runtime?: string | undefined;
|
|
690
700
|
/** Ensure the app on every device first. */
|
|
691
701
|
app?: WardenAppOption | undefined;
|
|
692
702
|
/** Ports leased for the run (`<from>[:<span>]`), exported as `WARDEN_PORT_<i>` / `WARDEN_PORTS` to serve, setup and every spec process. */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type SpanDetail } from "./span-detail";
|
|
3
|
+
/**
|
|
4
|
+
* What one iOS `axbridge` fallback or stall looked like, as `warden-simd` reports it (`fallback.stall` on a snapshot,
|
|
5
|
+
* `detail.stall` on a failed read). Recorded in `trace.jsonl` (the `stall*` span fields), `results.jsonl` (`bridge`)
|
|
6
|
+
* and under the header of `screen-at-failure.txt`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const bridgeStallSchema: z.ZodObject<{
|
|
9
|
+
stage: z.ZodString;
|
|
10
|
+
readMs: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
deadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
guestAgeMs: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
cold: z.ZodBoolean;
|
|
14
|
+
cooldownRemainingMs: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
probes: z.ZodNumber;
|
|
16
|
+
recycles: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
recyclesExhausted: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
}, z.core.$loose>;
|
|
19
|
+
export type BridgeStall = {
|
|
20
|
+
stage: string;
|
|
21
|
+
readMs?: number;
|
|
22
|
+
deadlineMs?: number;
|
|
23
|
+
guestAgeMs?: number;
|
|
24
|
+
cold: boolean;
|
|
25
|
+
cooldownRemainingMs?: number;
|
|
26
|
+
probes: number;
|
|
27
|
+
recycles?: number;
|
|
28
|
+
recyclesExhausted?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** Only the fields of the schema, so a newer helper's extra keys stay off the span and out of the results. */
|
|
31
|
+
export declare function toBridgeStall(wire: z.infer<typeof bridgeStallSchema>): BridgeStall;
|
|
32
|
+
/** Leaves the stall on the span timing the read (`stallStage`, `stallReadMs`, …). */
|
|
33
|
+
export declare function noteBridgeStall(stall: BridgeStall): void;
|
|
34
|
+
/** The stall a span recorded, or undefined when it has none. */
|
|
35
|
+
export declare function bridgeStallFromSpan(detail: SpanDetail | undefined): BridgeStall | undefined;
|
|
36
|
+
/** One line for a log or a report: `stage read · read 4012 ms of a 4000 ms deadline · warm · …`. */
|
|
37
|
+
export declare function formatBridgeStall(stall: BridgeStall): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CameraCondition, ClockCondition, HostFault, LocationCondition, NetworkCondition, PermissionName, PermissionState, TextSize } from "../../../core/src/conditions/conditions.types";
|
|
2
2
|
import type { AsyncResult } from "../../../types/src/result";
|
|
3
3
|
import type { ScreenReaderState } from "../a11y/a11y.types";
|
|
4
|
+
import type { BridgeStall } from "./bridge-stall";
|
|
4
5
|
/**
|
|
5
6
|
* The device driver contract every layer of warden-e2e builds on: the spec API (`screen`, `device`,
|
|
6
7
|
* `app`), `warden ui`, specs and the devtools facet all talk to a `Driver`. Implementations:
|
|
@@ -134,6 +135,14 @@ export type Snapshot = {
|
|
|
134
135
|
flat: Element[];
|
|
135
136
|
truncated: boolean;
|
|
136
137
|
modal?: ModalInfo;
|
|
138
|
+
/**
|
|
139
|
+
* Set when `backend` answered because this reader failed or was skipped first (iOS: `axbridge` stalled, `ax` read in
|
|
140
|
+
* its place). The fallback reader can lack what the first one has, so a missing node on such a snapshot is not final:
|
|
141
|
+
* the iOS helper probes the first reader in the background, and a later read is back on it once it answers.
|
|
142
|
+
*/
|
|
143
|
+
fellBackFrom?: SnapshotBackend;
|
|
144
|
+
/** iOS: what the bridge failure behind `fellBackFrom` looked like, when the helper reported it */
|
|
145
|
+
bridgeStall?: BridgeStall;
|
|
137
146
|
};
|
|
138
147
|
export type Orientation = "portrait" | "portraitUpsideDown" | "landscapeLeft" | "landscapeRight";
|
|
139
148
|
export type HardwareButton = "home" | "back" | "lock" | "volumeUp" | "volumeDown" | "shake";
|
|
@@ -25,7 +25,7 @@ import type { SimdSpawn } from "./simd-transport";
|
|
|
25
25
|
* cannot type, `settle` polls snapshot hashes when the helper has no native quiescence, `screenshot` uses
|
|
26
26
|
* `simctl io` when the helper cannot, and a helper built before the app ops leaves app lifecycle to `simctl`.
|
|
27
27
|
*/
|
|
28
|
-
export type IosDriverEnv = Pick<E2eEnv, "simd" | "simdSocket" | "helperDir"
|
|
28
|
+
export type IosDriverEnv = Pick<E2eEnv, "simd" | "simdSocket" | "helperDir"> & Partial<Pick<E2eEnv, "wardenVersion">>;
|
|
29
29
|
/** The iOS driver is a `Driver` plus the optional capabilities of `driver/alerts.ts`. */
|
|
30
30
|
export type IosDriver = Driver & SelectsAll & PastesText & ReadsAlertOn & ProbesFrontmost & ReadsA11y & ReadsA11yEvents & AuditsNatively;
|
|
31
31
|
export type OpenIosDriverOptions = {
|
|
@@ -46,6 +46,8 @@ export type OpenIosDriverOptions = {
|
|
|
46
46
|
client?: SimdClient;
|
|
47
47
|
now?: () => number;
|
|
48
48
|
sleep?: (ms: number) => Promise<void>;
|
|
49
|
+
/** how long the session's first snapshot waits for an in-sim bridge that is up but silent (default 60 s); 0 tries once */
|
|
50
|
+
coldBridgeBudgetMs?: number;
|
|
49
51
|
/** for `locateHelper` / `$WARDEN_HOME/simd/<version>`; defaults read the process */
|
|
50
52
|
version?: string;
|
|
51
53
|
wardenHome?: string;
|
|
@@ -82,6 +84,23 @@ export type OpenIosDriverOptions = {
|
|
|
82
84
|
/** test seams for the XCUITest audit's runner */
|
|
83
85
|
nativeAudit?: Pick<IosNativeAuditOptions, "ensureBuild" | "spawn" | "connect">;
|
|
84
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* How long the first snapshot of a session waits for an in-sim bridge that is up but silent. A freshly cloned
|
|
89
|
+
* simulator on a loaded host can take longer than one read's budget (axbridge 4 s + AX fallback 5 s) to answer the
|
|
90
|
+
* first time, and every read after it is fast, so the first one retries while the failure says "not ready yet".
|
|
91
|
+
* Once a read or a rebind has succeeded the normal per-call budgets apply.
|
|
92
|
+
*/
|
|
93
|
+
export declare const COLD_BRIDGE_BUDGET_MS = 60000;
|
|
94
|
+
/**
|
|
95
|
+
* warden-simd's deadline for the first `axbridge` read of a cold guest (helper start, a recycled guest, an app just
|
|
96
|
+
* launched): `BridgeDeadline.defaultColdReadMs`, or `WARDEN_SIMD_BRIDGE_COLD_DEADLINE_MS`. On a 3-vCPU runner that
|
|
97
|
+
* first read takes 4-11 s; warm reads keep the 4 s deadline.
|
|
98
|
+
*/
|
|
99
|
+
export declare const BRIDGE_COLD_DEADLINE_MS = 12000;
|
|
100
|
+
/** The request timeout of a read while the bridge is cold, so the client never gives up before the helper's deadline. */
|
|
101
|
+
export declare const COLD_READ_TIMEOUT_MS: number;
|
|
102
|
+
/** {@link COLD_READ_TIMEOUT_MS}, following a `WARDEN_SIMD_BRIDGE_COLD_DEADLINE_MS` the helper also reads. */
|
|
103
|
+
export declare function coldReadTimeoutMs(env?: Readonly<Record<string, string | undefined>>): number;
|
|
85
104
|
/** USB HID keyboard-page usage ids for the keys `Driver.key` offers. */
|
|
86
105
|
export declare const KEY_USAGE: Readonly<Record<KeyName, number>>;
|
|
87
106
|
/** The fields of a failed snapshot's `detail` the helper sets (which reader, why, both readers' errors), on its span. */
|
|
@@ -93,6 +112,8 @@ export declare function createIosDriver(swap: SwappableClient, simctl: Simctl, o
|
|
|
93
112
|
now: () => number;
|
|
94
113
|
sleep: (ms: number) => Promise<void>;
|
|
95
114
|
log?: Logger;
|
|
115
|
+
/** how long the first snapshot waits for a bridge that is not up yet (default {@link COLD_BRIDGE_BUDGET_MS}) */
|
|
116
|
+
coldBridgeBudgetMs?: number;
|
|
96
117
|
/** told about every successful `clock` op */
|
|
97
118
|
clockRecorder?: ClockRecorder;
|
|
98
119
|
/** device settings (`ios-settings.ts`); absent: those ops answer `E_UNSUPPORTED` */
|
|
@@ -21,6 +21,8 @@ export type SimdEventMap = {
|
|
|
21
21
|
};
|
|
22
22
|
export type RequestOptions = {
|
|
23
23
|
timeoutMs?: number;
|
|
24
|
+
/** a floor for this request's timeout (a cold bridge read): raises the default or `timeoutMs`, never lowers it */
|
|
25
|
+
minTimeoutMs?: number;
|
|
24
26
|
};
|
|
25
27
|
export type SimdClient = {
|
|
26
28
|
/** the handshake of the current helper process (refreshed after a respawn) */
|
|
@@ -192,6 +192,10 @@ export declare const healthDataSchema: z.ZodObject<{
|
|
|
192
192
|
unresolvedAgoMs: z.ZodNumber;
|
|
193
193
|
method: z.ZodString;
|
|
194
194
|
}, z.core.$strip>>;
|
|
195
|
+
bridgeRecycles: z.ZodOptional<z.ZodObject<{
|
|
196
|
+
count: z.ZodNumber;
|
|
197
|
+
exhausted: z.ZodBoolean;
|
|
198
|
+
}, z.core.$strip>>;
|
|
195
199
|
state: z.ZodOptional<z.ZodString>;
|
|
196
200
|
uptimeMs: z.ZodOptional<z.ZodNumber>;
|
|
197
201
|
}, z.core.$loose>;
|
|
@@ -214,6 +218,17 @@ export declare const snapshotDataSchema: z.ZodObject<{
|
|
|
214
218
|
from: z.ZodString;
|
|
215
219
|
to: z.ZodString;
|
|
216
220
|
reason: z.ZodString;
|
|
221
|
+
stall: z.ZodOptional<z.ZodObject<{
|
|
222
|
+
stage: z.ZodString;
|
|
223
|
+
readMs: z.ZodOptional<z.ZodNumber>;
|
|
224
|
+
deadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
225
|
+
guestAgeMs: z.ZodOptional<z.ZodNumber>;
|
|
226
|
+
cold: z.ZodBoolean;
|
|
227
|
+
cooldownRemainingMs: z.ZodOptional<z.ZodNumber>;
|
|
228
|
+
probes: z.ZodNumber;
|
|
229
|
+
recycles: z.ZodOptional<z.ZodNumber>;
|
|
230
|
+
recyclesExhausted: z.ZodOptional<z.ZodBoolean>;
|
|
231
|
+
}, z.core.$loose>>;
|
|
217
232
|
}, z.core.$strip>>;
|
|
218
233
|
target: z.ZodOptional<z.ZodObject<{
|
|
219
234
|
pid: z.ZodNumber;
|
|
@@ -362,6 +377,10 @@ export declare const opSchemas: {
|
|
|
362
377
|
unresolvedAgoMs: z.ZodNumber;
|
|
363
378
|
method: z.ZodString;
|
|
364
379
|
}, z.core.$strip>>;
|
|
380
|
+
bridgeRecycles: z.ZodOptional<z.ZodObject<{
|
|
381
|
+
count: z.ZodNumber;
|
|
382
|
+
exhausted: z.ZodBoolean;
|
|
383
|
+
}, z.core.$strip>>;
|
|
365
384
|
state: z.ZodOptional<z.ZodString>;
|
|
366
385
|
uptimeMs: z.ZodOptional<z.ZodNumber>;
|
|
367
386
|
}, z.core.$loose>;
|
|
@@ -383,6 +402,17 @@ export declare const opSchemas: {
|
|
|
383
402
|
from: z.ZodString;
|
|
384
403
|
to: z.ZodString;
|
|
385
404
|
reason: z.ZodString;
|
|
405
|
+
stall: z.ZodOptional<z.ZodObject<{
|
|
406
|
+
stage: z.ZodString;
|
|
407
|
+
readMs: z.ZodOptional<z.ZodNumber>;
|
|
408
|
+
deadlineMs: z.ZodOptional<z.ZodNumber>;
|
|
409
|
+
guestAgeMs: z.ZodOptional<z.ZodNumber>;
|
|
410
|
+
cold: z.ZodBoolean;
|
|
411
|
+
cooldownRemainingMs: z.ZodOptional<z.ZodNumber>;
|
|
412
|
+
probes: z.ZodNumber;
|
|
413
|
+
recycles: z.ZodOptional<z.ZodNumber>;
|
|
414
|
+
recyclesExhausted: z.ZodOptional<z.ZodBoolean>;
|
|
415
|
+
}, z.core.$loose>>;
|
|
386
416
|
}, z.core.$strip>>;
|
|
387
417
|
target: z.ZodOptional<z.ZodObject<{
|
|
388
418
|
pid: z.ZodNumber;
|
|
@@ -33,6 +33,14 @@ export declare function settleDetailOf(outcome: SettleOutcome): SettleDetail;
|
|
|
33
33
|
export declare const DEFAULT_SETTLE_QUIET_MS = 250;
|
|
34
34
|
/** How long a settle waits for quiet before it gives up. */
|
|
35
35
|
export declare const DEFAULT_SETTLE_TIMEOUT_MS = 3000;
|
|
36
|
+
/**
|
|
37
|
+
* The shortest window a snapshot-hash settle fallback waits (iOS, and `device.settle` on a driver without one). A navigation push exposes both screens at their final
|
|
38
|
+
* frames for 300-460 ms before the old one leaves the tree, so two equal reads 250 ms apart can settle on that mixed
|
|
39
|
+
* tree. The helper's own snapshot fallback (`FrameStability.minQuietMs`) uses the same floor.
|
|
40
|
+
*/
|
|
41
|
+
export declare const HASH_FALLBACK_MIN_QUIET_MS = 500;
|
|
42
|
+
/** The quiet window a hash fallback uses for a caller's `quietMs`. */
|
|
43
|
+
export declare function hashFallbackQuietMs(quietMs: number): number;
|
|
36
44
|
/**
|
|
37
45
|
* Snapshot-hash settle: poll a UI fingerprint until it stays unchanged for `quietMs`
|
|
38
46
|
* (two consecutive equal hashes and ≥ quietMs since the last change), or give up at `timeoutMs`.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ErrorResult } from "../../types/src/result";
|
|
2
|
-
export type E2eErrorCode = "LOCATOR_NOT_FOUND" | "LOCATOR_AMBIGUOUS" | "INVALID_LOCATOR" | "ACTION_FAILED" | "TIMEOUT" | "NOT_ACTIONABLE" | "INVALID_ARGUMENT" | "SESSION" | "DRIVER" | "ASSERTION" | "PERF_BUDGET"
|
|
2
|
+
export type E2eErrorCode = "LOCATOR_NOT_FOUND" | "LOCATOR_AMBIGUOUS" | "INVALID_LOCATOR" | "ACTION_FAILED" | "TIMEOUT" | "NOT_ACTIONABLE" | "INVALID_ARGUMENT" | "SESSION" | "DRIVER" | "ASSERTION" | "PERF_BUDGET" | "RUN_FAILED"
|
|
3
|
+
/** a read that fell back from its first reader (or came back an empty root) could not confirm the locator either way */
|
|
4
|
+
| "READ_DEGRADED";
|
|
3
5
|
/** Plain shape of a spec error: what `e2eErr()` accepts and what a `Result` carries. */
|
|
4
6
|
export type E2eErrorShape = {
|
|
5
7
|
code: E2eErrorCode;
|
|
@@ -81,6 +81,11 @@ export declare class LocatorAssertions {
|
|
|
81
81
|
toHaveCount(expected: number, options?: AssertionOptions): Promise<void>;
|
|
82
82
|
private assert;
|
|
83
83
|
private poll;
|
|
84
|
+
/**
|
|
85
|
+
* What one look says about `spec`: a judgement (from the accessibility tree, or from the React tree for a degraded
|
|
86
|
+
* read it answered), or `unsettled` when the read was degraded and nothing could say whether the locator is there.
|
|
87
|
+
*/
|
|
88
|
+
private judgeLook;
|
|
84
89
|
/** The screen is rendered here, from the last snapshot read, because only a failure needs the text. */
|
|
85
90
|
private failure;
|
|
86
91
|
}
|
package/types/e2e/src/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { expect } from "./expect/expect";
|
|
|
10
10
|
export type { PerfBreach, PerfBudgets } from "./perf/budgets";
|
|
11
11
|
export type { DevicePerf } from "./perf/device-perf";
|
|
12
12
|
export type { PerfMetrics, PerfReport, PerfStartOptions } from "./perf/perf.types";
|
|
13
|
+
export type { Run, RunCommand, RunDuration, RunOptions, RunResult } from "./run";
|
|
14
|
+
export { run } from "./run";
|
|
13
15
|
export type { App, Device, Screen, SystemAlerts, TestFixtures } from "./runner/fixtures.types";
|
|
14
16
|
export { afterAll, afterEach, beforeAll, beforeEach, describe, test } from "./runner/test";
|
|
15
17
|
export type { Locator, LocatorReading, PinchOptions, RotateOptions, TapOptions, TapUntil } from "./screen/locator";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SpanRunner } from "./runner/span";
|
|
2
|
+
/** Milliseconds, or a duration string (`500ms`, `30s`, `2m`). */
|
|
3
|
+
export type RunDuration = string | number;
|
|
4
|
+
export type RunOptions = {
|
|
5
|
+
/** working directory; default the spec process's (the project root under `warden e2e`) */
|
|
6
|
+
cwd?: string;
|
|
7
|
+
/** merged over the spec's environment; `undefined` removes a variable */
|
|
8
|
+
env?: Record<string, string | undefined>;
|
|
9
|
+
/** kill the command after this long and throw `TIMEOUT`; default `60s` */
|
|
10
|
+
timeout?: RunDuration;
|
|
11
|
+
/** written to the command's stdin */
|
|
12
|
+
input?: string;
|
|
13
|
+
/** throw `RUN_FAILED` on a non-zero exit; default true. `false` returns the result with `ok: false` */
|
|
14
|
+
check?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type RunResult = {
|
|
17
|
+
/** exit code was 0 */
|
|
18
|
+
ok: boolean;
|
|
19
|
+
exitCode: number;
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
/** wall time of the command */
|
|
23
|
+
ms: number;
|
|
24
|
+
/** `stdout` parsed as JSON; throws `INVALID_ARGUMENT` when it is not JSON */
|
|
25
|
+
json(): unknown;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A shell command (`string`, run with `sh -c`) or an argv (`string[]`, no shell: nothing is expanded or split).
|
|
29
|
+
*/
|
|
30
|
+
export type RunCommand = string | readonly string[];
|
|
31
|
+
export type Run = (command: RunCommand, options?: RunOptions) => Promise<RunResult>;
|
|
32
|
+
export type RunDeps = {
|
|
33
|
+
/** times the call as a span in the running attempt's trace */
|
|
34
|
+
span: SpanRunner;
|
|
35
|
+
};
|
|
36
|
+
/** `run` with its span runner injected (tests); specs use {@link run}. */
|
|
37
|
+
export declare function createRun(deps: RunDeps): Run;
|
|
38
|
+
/**
|
|
39
|
+
* Runs a command on the host (the machine running the spec, not the device) and returns its exit code and output.
|
|
40
|
+
* Use it to seed a backend, call a script, or reset state before a test. A non-zero exit throws `RUN_FAILED`
|
|
41
|
+
* (`check: false` returns it instead), a command over `timeout` (default 60 s) is killed and throws `TIMEOUT`.
|
|
42
|
+
* Inside a test it is one `run` span in `trace.jsonl` (exit code only, never the command).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* await run("bun scripts/seed.ts");
|
|
46
|
+
* const { id } = (await run(["bun", "scripts/create-user.ts", "--json"])).json() as { id: string };
|
|
47
|
+
*/
|
|
48
|
+
export declare const run: Run;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TestResult } from "./results";
|
|
2
|
+
import type { TraceEvent } from "./trace";
|
|
3
|
+
export type BridgeEvidence = NonNullable<TestResult["bridge"]>;
|
|
4
|
+
/**
|
|
5
|
+
* Collects an attempt's iOS bridge fallbacks from its spans for `results.jsonl` (`bridge`): reads answered by `ax`
|
|
6
|
+
* in place of `axbridge`, and failed reads that carried a stall report. A read the bridge answered by pid (frontmost
|
|
7
|
+
* unresolved) is not one.
|
|
8
|
+
*/
|
|
9
|
+
export declare function bridgeEvidence(): {
|
|
10
|
+
observe(event: TraceEvent): void;
|
|
11
|
+
summary(): BridgeEvidence | undefined;
|
|
12
|
+
};
|