Skip to content

Commit 0ea4b3b

Browse files
committed
test(replay): extract shared fixtures from the target-verification suite
The 26 scenarios in session-replay-target-verification-runtime.test.ts each rebuilt the same temp root, iOS app session, written .ad script, and request-recording invoke by hand, and repeated the same one-button XCTest capture literal. Those setup pieces now live as named exports in the sibling session-replay-scenario.fixtures.ts (replayScriptScene, saveButtonCapture, emptyCapture, and the annotation constants), built on the existing test-utils builders (mkdtempForTestSync, makeIosAppSession) and the replay runtime fixtures. Every test keeps its title, comments, and expect lines, so each still reads as its own scenario. Test count (26) and expect count (128) are unchanged. jscpd (--min-tokens 80 --min-lines 8) duplicated lines within the suite drop from 1,046 (25 clone pairs) to 125 (6 pairs). The divergence and repair-empty-tail suites are left for a follow-up: this extraction alone is ~950 gross diff lines.
1 parent d04e213 commit 0ea4b3b

2 files changed

Lines changed: 338 additions & 611 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import path from 'node:path';
2+
import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts';
3+
import { makeIosAppSession } from '../../../../__tests__/test-utils/session-factories.ts';
4+
import { SessionStore } from '../../../session-store.ts';
5+
import type { DaemonInvokeFn, DaemonRequest, DaemonResponse } from '../../../types.ts';
6+
import { runReplayForTest } from '../../__tests__/replay-command-fixture.ts';
7+
import {
8+
baseReplayRequest,
9+
writeReplayFile,
10+
} from '../../__tests__/session-replay-runtime.fixtures.ts';
11+
12+
/** A temp root with one seeded session in its store: the ground every replay scenario stands on. */
13+
type ReplaySessionScene = Readonly<{
14+
root: string;
15+
sessionStore: SessionStore;
16+
sessionName: string;
17+
logPath: string;
18+
}>;
19+
20+
function iosReplayScene(prefix: string): ReplaySessionScene {
21+
const root = mkdtempForTestSync(prefix);
22+
const sessionStore = new SessionStore(path.join(root, 'sessions'));
23+
const sessionName = 'default';
24+
sessionStore.set(sessionName, makeIosAppSession(sessionName));
25+
return { root, sessionStore, sessionName, logPath: path.join(root, 'daemon.log') };
26+
}
27+
28+
export type ReplayScriptScene = ReplaySessionScene &
29+
Readonly<{
30+
filePath: string;
31+
/** Every request the replay handed to `invoke`, in dispatch order. */
32+
invoked: DaemonRequest[];
33+
/**
34+
* Runs the script through `runReplayCommand`. Each request is appended to
35+
* `invoked` before `invoke` answers it; without `invoke` every request succeeds
36+
* with empty data.
37+
*/
38+
replay(options?: { invoke?: DaemonInvokeFn }): Promise<DaemonResponse>;
39+
}>;
40+
41+
/** An iOS app session plus a written `.ad` script, ready to replay. */
42+
export function replayScriptScene(prefix: string, lines: string[]): ReplayScriptScene {
43+
const scene = iosReplayScene(prefix);
44+
const filePath = writeReplayFile(scene.root, lines);
45+
const invoked: DaemonRequest[] = [];
46+
return {
47+
...scene,
48+
filePath,
49+
invoked,
50+
replay: ({ invoke } = {}) =>
51+
runReplayForTest({
52+
req: baseReplayRequest({ positionals: [filePath] }),
53+
sessionName: scene.sessionName,
54+
logPath: scene.logPath,
55+
sessionStore: scene.sessionStore,
56+
invoke: async (req) => {
57+
invoked.push(req);
58+
return invoke ? invoke(req) : { ok: true, data: {} };
59+
},
60+
}),
61+
};
62+
}
63+
64+
export const SAVE_ANNOTATION =
65+
'# agent-device:target-v1 {"id":"save","role":"button","label":"Save","ancestry":[],"sibling":0,"viewportOrder":0,"verification":"verified"}';
66+
67+
export const UNVERIFIABLE_ANNOTATION =
68+
'# agent-device:target-v1 {"id":"save","role":"button","label":"Save","ancestry":[],"sibling":0,"viewportOrder":0,"verification":"unverifiable"}';
69+
70+
export const WAIT_ANNOTATION =
71+
'# agent-device:target-v1 {"role":"statictext","label":"Screen X","ancestry":[{"role":"other","label":"Detail Screen"}],"sibling":0,"viewportOrder":0,"verification":"verified"}';
72+
73+
export const WAIT_UNVERIFIABLE_ANNOTATION =
74+
'# agent-device:target-v1 {"role":"statictext","label":"Screen X","ancestry":[{"role":"other","label":"Detail Screen"}],"sibling":0,"viewportOrder":0,"verification":"unverifiable"}';
75+
76+
export const DRAG_ANNOTATION = `# agent-device:targets-v1 ${JSON.stringify({
77+
source: {
78+
id: 'source',
79+
role: 'view',
80+
label: 'Source',
81+
ancestry: [],
82+
sibling: 0,
83+
viewportOrder: 0,
84+
verification: 'verified',
85+
},
86+
destination: {
87+
id: 'destination',
88+
role: 'view',
89+
label: 'Drop',
90+
ancestry: [],
91+
sibling: 1,
92+
viewportOrder: 0,
93+
verification: 'verified',
94+
},
95+
})}`;
96+
97+
export const DRAG_NODES = [
98+
{
99+
index: 0,
100+
depth: 0,
101+
type: 'View',
102+
identifier: 'source',
103+
label: 'Source',
104+
rect: { x: 10, y: 10, width: 40, height: 20 },
105+
},
106+
{
107+
index: 1,
108+
depth: 0,
109+
type: 'View',
110+
identifier: 'destination',
111+
label: 'Drop',
112+
rect: { x: 100, y: 100, width: 40, height: 20 },
113+
},
114+
];
115+
116+
/** An XCTest capture holding one "Save" button; `identifier` renames it (`'save-v2'` = renamed id, same label). */
117+
export function saveButtonCapture(identifier = 'save') {
118+
return {
119+
nodes: [
120+
{
121+
index: 0,
122+
depth: 0,
123+
type: 'Button',
124+
identifier,
125+
label: 'Save',
126+
rect: { x: 10, y: 10, width: 40, height: 20 },
127+
},
128+
],
129+
truncated: false,
130+
backend: 'xctest',
131+
};
132+
}
133+
134+
/** An XCTest capture of an entirely empty tree: nothing matches any recorded selector. */
135+
export function emptyCapture() {
136+
return { nodes: [], truncated: false, backend: 'xctest' };
137+
}

0 commit comments

Comments
 (0)