@e2e-dev/mobile 0.10.0 → 0.11.0-nightly-20261009094406

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/dist/.build.json CHANGED
@@ -1 +1 @@
1
- {"version":"0.10.0"}
1
+ {"version":"0.11.0-nightly-20261009094406"}
package/dist/surface.d.ts CHANGED
@@ -68,6 +68,8 @@ export declare class AgentDeviceSurface {
68
68
  private readonly createClient;
69
69
  private client;
70
70
  private attempt;
71
+ /** The attempt's `EngineAttemptContext.screen`, which locate captures feed; undefined between attempts. */
72
+ private screenSink;
71
73
  /** The device this worker drives, the pool's binding for its slot; undefined leaves the choice to agent-device. */
72
74
  private device;
73
75
  private generation;
@@ -293,6 +295,12 @@ export declare class AgentDeviceSurface {
293
295
  observe(operation: OperationContext, options?: EngineObserveOptions): Promise<EngineSnapshot>;
294
296
  /** Retains only action bindings for matches, independently of the observation generation. */
295
297
  locate(expression: LocatorExpression, operation: OperationContext): Promise<readonly SemanticNode[]>;
298
+ /**
299
+ * Hands the harness the capture a locate matched against, for the trace:
300
+ * the screen the step acted on, at no cost of another snapshot. A screen
301
+ * whose viewport is not known yet is skipped rather than probed.
302
+ */
303
+ private traceScreen;
296
304
  private resolveRef;
297
305
  private actionTarget;
298
306
  /**
package/dist/surface.js CHANGED
@@ -165,11 +165,22 @@ function settleOptions(settle) {
165
165
  }
166
166
  return { settle: true, settleQuietMs: quietMs };
167
167
  }
168
+ /** Calls one of the harness's trace hooks; a hook that throws, or one an older harness lacks, never fails the device's work. */
169
+ function tellHarness(call) {
170
+ try {
171
+ call();
172
+ }
173
+ catch {
174
+ // The trace is a reader of the attempt, never a reason it fails.
175
+ }
176
+ }
168
177
  export class AgentDeviceSurface {
169
178
  options;
170
179
  createClient;
171
180
  client;
172
181
  attempt;
182
+ /** The attempt's `EngineAttemptContext.screen`, which locate captures feed; undefined between attempts. */
183
+ screenSink;
173
184
  /** The device this worker drives, the pool's binding for its slot; undefined leaves the choice to agent-device. */
174
185
  device;
175
186
  generation = new Map();
@@ -346,11 +357,15 @@ export class AgentDeviceSurface {
346
357
  this.attempt = { attemptId: context.attemptId, artifactsDir: context.artifactsDir, screenshots: 0, video: undefined };
347
358
  this.generation = new Map();
348
359
  this.located.clear();
360
+ this.screenSink = context.screen;
361
+ const device = deviceLabel(this.device);
362
+ tellHarness(() => context.environment?.({ platform: this.options.platform, ...(device === undefined ? {} : { device }) }));
349
363
  }
350
364
  async endAttempt(context) {
351
365
  const attempt = this.attempt;
352
366
  const dangling = attempt?.video;
353
367
  this.attempt = undefined;
368
+ this.screenSink = undefined;
354
369
  this.generation = new Map();
355
370
  this.located.clear();
356
371
  // The harness stops the video before it ends the attempt; a recording still
@@ -737,7 +752,7 @@ export class AgentDeviceSurface {
737
752
  this.generation = new Map(projected.index.map((entry) => [entry.id, this.bind(entry, projected.index)]));
738
753
  const viewport = await this.viewportFor(projected, operation.signal);
739
754
  const location = screenLocation(raw.appBundleId ?? raw.appName ?? this.appIdentity, screenTitle(projected));
740
- const capture = options?.pixels === true ? await this.capturePixels(operation, projected, viewport) : undefined;
755
+ const capture = options?.pixels === true ? await this.capturePixels(operation, projected, viewport, { normalizeStatusBar: options.comparable === true }) : undefined;
741
756
  return {
742
757
  root: screenRoot(projected.roots, viewport),
743
758
  viewport,
@@ -750,6 +765,7 @@ export class AgentDeviceSurface {
750
765
  async locate(expression, operation) {
751
766
  const raw = await this.snapshotOrEmpty(operation, false);
752
767
  const projected = this.project(raw);
768
+ this.traceScreen(raw, projected);
753
769
  const matches = resolveExpression(expression, projected.index);
754
770
  for (const entry of matches)
755
771
  this.located.set(entry.id, this.bind(entry, projected.index));
@@ -760,6 +776,24 @@ export class AgentDeviceSurface {
760
776
  }
761
777
  return matches.map((entry) => entry.node);
762
778
  }
779
+ /**
780
+ * Hands the harness the capture a locate matched against, for the trace:
781
+ * the screen the step acted on, at no cost of another snapshot. A screen
782
+ * whose viewport is not known yet is skipped rather than probed.
783
+ */
784
+ traceScreen(raw, projected) {
785
+ const sink = this.screenSink;
786
+ const viewport = projected.viewport ?? this.knownViewport;
787
+ if (sink === undefined || viewport === undefined)
788
+ return;
789
+ const location = screenLocation(raw.appBundleId ?? raw.appName ?? this.appIdentity, screenTitle(projected));
790
+ tellHarness(() => sink({
791
+ root: screenRoot(projected.roots, viewport),
792
+ viewport,
793
+ ...(isTruncated(raw) ? { truncated: true } : {}),
794
+ ...(location === undefined ? {} : { location }),
795
+ }));
796
+ }
763
797
  resolveRef(ref) {
764
798
  const entry = this.located.get(ref.id) ?? this.generation.get(ref.id);
765
799
  if (entry === undefined) {
@@ -1190,20 +1224,20 @@ export class AgentDeviceSurface {
1190
1224
  return relative;
1191
1225
  }
1192
1226
  /** Raw device pixels; cleanup follows the capture even when its caller abandons it. */
1193
- rawScreenshot(signal) {
1194
- return this.captureScreenshot(signal, async (shot, file) => new Uint8Array(readFileSync(shot.path ?? file)));
1227
+ rawScreenshot(signal, capture = {}) {
1228
+ return this.captureScreenshot(signal, async (shot, file) => new Uint8Array(readFileSync(shot.path ?? file)), capture);
1195
1229
  }
1196
1230
  /**
1197
1231
  * One screenshot into a temp directory that is removed once `read` has
1198
1232
  * taken what it needs from the response or the file, even when the caller
1199
1233
  * abandons the capture.
1200
1234
  */
1201
- captureScreenshot(signal, read) {
1235
+ captureScreenshot(signal, read, capture = {}) {
1202
1236
  return this.command('screenshot', async (client) => {
1203
1237
  const directory = mkdtempSync(path.join(tmpdir(), 'e2e-agent-device-'));
1204
1238
  const file = path.join(directory, 'screenshot.png');
1205
1239
  try {
1206
- return await read(await client.capture.screenshot({ path: file }), file);
1240
+ return await read(await client.capture.screenshot({ path: file, ...(capture.normalizeStatusBar === true ? { normalizeStatusBar: true } : {}) }), file);
1207
1241
  }
1208
1242
  finally {
1209
1243
  rmSync(directory, { recursive: true, force: true });
@@ -1238,10 +1272,10 @@ export class AgentDeviceSurface {
1238
1272
  * Viewport pixels for an observation. Best-effort: a screenshot that cannot
1239
1273
  * be produced or redacted costs the observation its image, not the step.
1240
1274
  */
1241
- async capturePixels(operation, projected, viewport) {
1275
+ async capturePixels(operation, projected, viewport, capture) {
1242
1276
  let raw;
1243
1277
  try {
1244
- raw = await this.rawScreenshot(operation.signal);
1278
+ raw = await this.rawScreenshot(operation.signal, capture);
1245
1279
  }
1246
1280
  catch {
1247
1281
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e2e-dev/mobile",
3
- "version": "0.10.0",
3
+ "version": "0.11.0-nightly-20261009094406",
4
4
  "description": "Mobile engine (iOS and Android) for e2e agentic end-to-end testing, built on agent-device",
5
5
  "keywords": [
6
6
  "e2e",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "peerDependencies": {
59
59
  "ai": "^7.0.0",
60
- "e2e": ">=0.15.0 <1"
60
+ "e2e": "0.19.0-nightly-20261009094406"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "ai": {
@@ -68,7 +68,7 @@
68
68
  "@types/node": "26.6.2",
69
69
  "@types/pngjs": "6.0.5",
70
70
  "ai": "7.0.107",
71
- "e2e": "0.18.0",
71
+ "e2e": "0.19.0-nightly-20261009094406",
72
72
  "typescript": "7.0.2",
73
73
  "vitest": "5.0.1"
74
74
  },