Skip to content

Commit 557197b

Browse files
committed
test(apple-runner): pin production-built runner requests in a golden table
Add contracts/fixtures/runner-requests.json: one entry per production request site, captured at its real entry point. Three producer tests own the entries: the platform-apple request sites, the runner-internal sites (real HTTP bodies from the fake runner), and the two recording modules. runner-contract.test.ts asserts every RunnerCommand has an entry, names are sorted and unique, and the drive code builds no request itself. Refs #2881
1 parent fa1082d commit 557197b

10 files changed

Lines changed: 717 additions & 62 deletions

‎apple/runner/RUNNER_PROTOCOL.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The daemon probes `http://127.0.0.1:<port>/command` for simulator and desktop fl
1414
## Request Shape
1515

1616
Every request includes a `command` field. Additional fields depend on the command family.
17+
The request vocabulary is `contracts/fixtures/runner-requests.json`: the requests production builds.
1718

1819
Examples:
1920

‎contracts/fixtures/runner-requests.json‎

Lines changed: 68 additions & 0 deletions
Large diffs are not rendered by default.

‎packages/platform-apple/src/__tests__/interactor-runner-provider.test.ts‎

Lines changed: 7 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { GesturePlan } from '@agent-device/contracts/gesture-plan-types';
21
import type {
32
Interactor,
43
RunnerContext,
@@ -9,14 +8,14 @@ import { AppError } from '@agent-device/kernel/errors';
98
import assert from 'node:assert/strict';
109
import { test } from 'vitest';
1110
import { IOS_SIMULATOR, MACOS_DEVICE } from './device-fixtures.ts';
12-
import type {
13-
AppleRunnerCommandOptions,
14-
AppleRunnerProvider,
15-
RunnerCommand,
16-
} from '../runner/index.ts';
11+
import type { AppleRunnerProvider } from '../runner/index.ts';
1712
import { createAppleInteractor } from '../interactor.ts';
18-
19-
type RecordedRunnerCall = { command: RunnerCommand; options: AppleRunnerCommandOptions };
13+
import {
14+
recordingRunnerProvider,
15+
runnerResultFor,
16+
singlePointerPanPlan,
17+
type RecordedRunnerCall,
18+
} from './recording-runner-provider.ts';
2019

2120
function presentedSnapshot(result: SnapshotRuntimeResult): SnapshotResult {
2221
if ('stage' in result) throw new Error('Apple runner snapshot must be presented');
@@ -446,57 +445,3 @@ test('snapshot forwards either forceable preferredBackend into the emitted runne
446445
assert.equal(snapshots[0]?.command.preferredBackend, 'tree');
447446
assert.equal(snapshots[1]?.command.preferredBackend, undefined);
448447
});
449-
450-
function recordingRunnerProvider(calls: RecordedRunnerCall[]): AppleRunnerProvider {
451-
return {
452-
hasLiveSession: () => true,
453-
runCommand: async (_device, command, options) => {
454-
calls.push({ command, options });
455-
return runnerResultFor(command);
456-
},
457-
};
458-
}
459-
460-
function runnerResultFor(command: RunnerCommand): Record<string, unknown> {
461-
switch (command.command) {
462-
case 'snapshot':
463-
return {
464-
nodes: [
465-
{ index: 0, type: 'Application', rect: { x: 0, y: 0, width: 390, height: 844 } },
466-
{
467-
index: 1,
468-
parentIndex: 0,
469-
type: 'Button',
470-
label: 'Go',
471-
hittable: true,
472-
rect: { x: 10, y: 10, width: 80, height: 40 },
473-
},
474-
],
475-
};
476-
case 'gestureViewport':
477-
return { x: 0, y: 0, x2: 390, y2: 844 };
478-
case 'rotate':
479-
return { orientation: command.orientation };
480-
default:
481-
return {};
482-
}
483-
}
484-
485-
function singlePointerPanPlan(): GesturePlan {
486-
return {
487-
topology: 'single',
488-
intent: 'pan',
489-
executionProfile: 'timed-pan',
490-
durationMs: 120,
491-
viewport: { x: 0, y: 0, width: 390, height: 844 },
492-
pointers: [
493-
{
494-
pointerId: 0,
495-
samples: [
496-
{ offsetMs: 0, point: { x: 100, y: 400 } },
497-
{ offsetMs: 120, point: { x: 100, y: 200 } },
498-
],
499-
},
500-
],
501-
};
502-
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { GesturePlan } from '@agent-device/contracts/gesture-plan-types';
2+
import type {
3+
AppleRunnerCommandOptions,
4+
AppleRunnerProvider,
5+
RunnerCommand,
6+
} from '../runner/index.ts';
7+
8+
export type RecordedRunnerCall = Readonly<{
9+
command: RunnerCommand;
10+
options: AppleRunnerCommandOptions;
11+
}>;
12+
13+
/** A runner transport that answers every command with a minimal valid result and records it. */
14+
export function recordingRunnerProvider(
15+
calls: RecordedRunnerCall[],
16+
results: Partial<Record<RunnerCommand['command'], Record<string, unknown>>> = {},
17+
): AppleRunnerProvider {
18+
return {
19+
hasLiveSession: () => true,
20+
runCommand: async (_device, command, options) => {
21+
calls.push({ command, options });
22+
return results[command.command] ?? runnerResultFor(command);
23+
},
24+
};
25+
}
26+
27+
export function runnerResultFor(sent: Pick<RunnerCommand, 'command' | 'orientation'>) {
28+
switch (sent.command) {
29+
case 'snapshot':
30+
return {
31+
nodes: [
32+
{ index: 0, type: 'Application', rect: { x: 0, y: 0, width: 390, height: 844 } },
33+
{
34+
index: 1,
35+
parentIndex: 0,
36+
type: 'Button',
37+
label: 'Go',
38+
hittable: true,
39+
rect: { x: 10, y: 10, width: 80, height: 40 },
40+
},
41+
],
42+
};
43+
case 'gestureViewport':
44+
return { x: 0, y: 0, x2: 390, y2: 844 };
45+
case 'rotate':
46+
return { orientation: sent.orientation };
47+
case 'scroll':
48+
case 'desktopScroll':
49+
return { referenceWidth: 390, referenceHeight: 844 };
50+
default:
51+
return {};
52+
}
53+
}
54+
55+
export function singlePointerPanPlan(): GesturePlan {
56+
return {
57+
topology: 'single',
58+
intent: 'pan',
59+
executionProfile: 'timed-pan',
60+
durationMs: 120,
61+
viewport: { x: 0, y: 0, width: 390, height: 844 },
62+
pointers: [
63+
{
64+
pointerId: 0,
65+
samples: [
66+
{ offsetMs: 0, point: { x: 100, y: 400 } },
67+
{ offsetMs: 120, point: { x: 100, y: 200 } },
68+
],
69+
},
70+
],
71+
};
72+
}

0 commit comments

Comments
 (0)