Skip to content

Commit 8d00b58

Browse files
committed
refactor: normalize click alias to press path
1 parent b398c41 commit 8d00b58

8 files changed

Lines changed: 44 additions & 41 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ Use `press` as the canonical tap command; `click` is an equivalent alias.
3939
```bash
4040
agent-device open Contacts --platform ios # creates session on iOS Simulator
4141
agent-device snapshot
42-
agent-device click @e5
42+
agent-device press @e5
4343
agent-device fill @e6 "John"
4444
agent-device fill @e7 "Doe"
45-
agent-device click @e3
45+
agent-device press @e3
4646
agent-device close
4747
```
4848

@@ -82,7 +82,7 @@ Gesture series examples:
8282
```bash
8383
agent-device press 300 500 --count 12 --interval-ms 45
8484
agent-device press 300 500 --count 6 --hold-ms 120 --interval-ms 30 --jitter-px 2
85-
agent-device click @e5 --count 5 --interval-ms 1 --double-tap
85+
agent-device press @e5 --count 5 --double-tap
8686
agent-device swipe 540 1500 540 500 120 --count 8 --pause-ms 30 --pattern ping-pong
8787
```
8888

skills/agent-device/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ For agent-driven exploration: use refs. For deterministic replay scripts: use se
1212
```bash
1313
agent-device open Settings --platform ios
1414
agent-device snapshot -i
15-
agent-device click @e3
15+
agent-device press @e3
1616
agent-device wait text "Camera"
1717
agent-device alert wait 10000
1818
agent-device fill @e5 "test"

src/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,12 @@ export async function runCli(argv: string[], deps: CliDeps = DEFAULT_CLI_DEPS):
146146
if (logTailStopper) logTailStopper();
147147
return;
148148
}
149-
if (command === 'click') {
149+
if (command === 'click' || command === 'press') {
150150
const ref = (response.data as any)?.ref ?? '';
151151
const x = (response.data as any)?.x;
152152
const y = (response.data as any)?.y;
153153
if (ref && typeof x === 'number' && typeof y === 'number') {
154-
process.stdout.write(`Clicked @${ref} (${x}, ${y})\n`);
154+
process.stdout.write(`Tapped @${ref} (${x}, ${y})\n`);
155155
}
156156
if (logTailStopper) logTailStopper();
157157
return;

src/core/__tests__/dispatch-press.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ test('shouldUseIosTapSeries enables fast path for repeated plain iOS taps', () =
2525

2626
test('shouldUseIosTapSeries disables fast path for single press or modified gestures', () => {
2727
assert.equal(shouldUseIosTapSeries(iosDevice, 1, 0, 0), false);
28-
assert.equal(shouldUseIosTapSeries(iosDevice, 1, 0, 0, true), true);
2928
assert.equal(shouldUseIosTapSeries(iosDevice, 5, 100, 0), false);
3029
assert.equal(shouldUseIosTapSeries(iosDevice, 5, 0, 1), false);
3130
});

src/core/dispatch.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export async function dispatchCommand(
141141
throw new AppError('INVALID_ARGS', 'double-tap cannot be combined with jitter-px');
142142
}
143143

144-
if (shouldUseIosTapSeries(device, count, holdMs, jitterPx, doubleTap)) {
144+
if (shouldUseIosTapSeries(device, count, holdMs, jitterPx)) {
145145
await runIosRunnerCommand(
146146
device,
147147
{
@@ -421,9 +421,8 @@ export function shouldUseIosTapSeries(
421421
count: number,
422422
holdMs: number,
423423
jitterPx: number,
424-
doubleTap = false,
425424
): boolean {
426-
return device.platform === 'ios' && (count > 1 || doubleTap) && holdMs === 0 && jitterPx === 0;
425+
return device.platform === 'ios' && count > 1 && holdMs === 0 && jitterPx === 0;
427426
}
428427

429428
export function shouldUseIosDragSeries(device: DeviceInfo, count: number): boolean {

src/daemon.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,16 @@ async function handleRequest(req: DaemonRequest): Promise<DaemonResponse> {
5555
return { ok: false, error: { code: 'UNAUTHORIZED', message: 'Invalid token' } };
5656
}
5757

58-
const command = req.command;
59-
const sessionName = resolveEffectiveSessionName(req, sessionStore);
58+
const normalizedReq = normalizeAliasedCommands(req);
59+
const command = normalizedReq.command;
60+
const sessionName = resolveEffectiveSessionName(normalizedReq, sessionStore);
6061
const existingSession = sessionStore.get(sessionName);
6162
if (existingSession && !selectorValidationExemptCommands.has(command)) {
62-
assertSessionSelectorMatches(existingSession, req.flags);
63+
assertSessionSelectorMatches(existingSession, normalizedReq.flags);
6364
}
6465

6566
const sessionResponse = await handleSessionCommands({
66-
req,
67+
req: normalizedReq,
6768
sessionName,
6869
logPath,
6970
sessionStore,
@@ -72,22 +73,22 @@ async function handleRequest(req: DaemonRequest): Promise<DaemonResponse> {
7273
if (sessionResponse) return sessionResponse;
7374

7475
const snapshotResponse = await handleSnapshotCommands({
75-
req,
76+
req: normalizedReq,
7677
sessionName,
7778
logPath,
7879
sessionStore,
7980
});
8081
if (snapshotResponse) return snapshotResponse;
8182

8283
const recordTraceResponse = await handleRecordTraceCommands({
83-
req,
84+
req: normalizedReq,
8485
sessionName,
8586
sessionStore,
8687
});
8788
if (recordTraceResponse) return recordTraceResponse;
8889

8990
const findResponse = await handleFindCommands({
90-
req,
91+
req: normalizedReq,
9192
sessionName,
9293
logPath,
9394
sessionStore,
@@ -96,7 +97,7 @@ async function handleRequest(req: DaemonRequest): Promise<DaemonResponse> {
9697
if (findResponse) return findResponse;
9798

9899
const interactionResponse = await handleInteractionCommands({
99-
req,
100+
req: normalizedReq,
100101
sessionName,
101102
sessionStore,
102103
contextFromFlags,
@@ -119,18 +120,23 @@ async function handleRequest(req: DaemonRequest): Promise<DaemonResponse> {
119120
};
120121
}
121122

122-
const data = await dispatchCommand(session.device, command, req.positionals ?? [], req.flags?.out, {
123-
...contextFromFlags(req.flags, session.appBundleId, session.trace?.outPath),
123+
const data = await dispatchCommand(session.device, command, normalizedReq.positionals ?? [], normalizedReq.flags?.out, {
124+
...contextFromFlags(normalizedReq.flags, session.appBundleId, session.trace?.outPath),
124125
});
125126
sessionStore.recordAction(session, {
126127
command,
127-
positionals: req.positionals ?? [],
128-
flags: req.flags ?? {},
128+
positionals: normalizedReq.positionals ?? [],
129+
flags: normalizedReq.flags ?? {},
129130
result: data ?? {},
130131
});
131132
return { ok: true, data: data ?? {} };
132133
}
133134

135+
function normalizeAliasedCommands(req: DaemonRequest): DaemonRequest {
136+
if (req.command !== 'click') return req;
137+
return { ...req, command: 'press' };
138+
}
139+
134140
function writeInfo(port: number): void {
135141
if (!fs.existsSync(baseDir)) fs.mkdirSync(baseDir, { recursive: true });
136142
fs.writeFileSync(logPath, '');

src/daemon/handlers/__tests__/interaction.test.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ test('unsupportedRefSnapshotFlags returns empty when no ref-unsupported flags ar
5656
assert.deepEqual(unsupported, []);
5757
});
5858

59-
test('click coordinates dispatches press and records as click', async () => {
59+
test('press coordinates dispatches press and records as press', async () => {
6060
const sessionStore = makeSessionStore();
6161
const sessionName = 'default';
6262
sessionStore.set(sessionName, makeSession(sessionName));
@@ -67,7 +67,7 @@ test('click coordinates dispatches press and records as click', async () => {
6767
req: {
6868
token: 't',
6969
session: sessionName,
70-
command: 'click',
70+
command: 'press',
7171
positionals: ['100', '200'],
7272
flags: { count: 3, intervalMs: 1, doubleTap: true },
7373
},
@@ -92,7 +92,7 @@ test('click coordinates dispatches press and records as click', async () => {
9292
const session = sessionStore.get(sessionName);
9393
assert.ok(session);
9494
assert.equal(session?.actions.length, 1);
95-
assert.equal(session?.actions[0]?.command, 'click');
95+
assert.equal(session?.actions[0]?.command, 'press');
9696
assert.deepEqual(session?.actions[0]?.positionals, ['100', '200']);
9797
});
9898

@@ -155,30 +155,33 @@ test('press @ref resolves snapshot node and records press action', async () => {
155155
assert.ok(Array.isArray(result.selectorChain));
156156
});
157157

158-
test('press coordinates returns null to allow daemon passthrough', async () => {
158+
test('press coordinates does not treat extra trailing args as selector', async () => {
159159
const sessionStore = makeSessionStore();
160160
const sessionName = 'default';
161161
sessionStore.set(sessionName, makeSession(sessionName));
162162

163-
let dispatchCalls = 0;
163+
const dispatchCalls: Array<{ command: string; positionals: string[] }> = [];
164164
const response = await handleInteractionCommands({
165165
req: {
166166
token: 't',
167167
session: sessionName,
168168
command: 'press',
169-
positionals: ['100', '200'],
169+
positionals: ['100', '200', 'extra'],
170170
flags: { count: 2 },
171171
},
172172
sessionName,
173173
sessionStore,
174174
contextFromFlags,
175-
dispatch: async () => {
176-
dispatchCalls += 1;
177-
return {};
175+
dispatch: async (_device, command, positionals) => {
176+
dispatchCalls.push({ command, positionals });
177+
return { ok: true };
178178
},
179179
});
180180

181-
assert.equal(response, null);
182-
assert.equal(dispatchCalls, 0);
183-
assert.equal(sessionStore.get(sessionName)?.actions.length, 0);
181+
assert.ok(response);
182+
assert.equal(response.ok, true);
183+
assert.equal(dispatchCalls.length, 1);
184+
assert.equal(dispatchCalls[0]?.command, 'press');
185+
assert.deepEqual(dispatchCalls[0]?.positionals, ['100', '200']);
186+
assert.equal(sessionStore.get(sessionName)?.actions.length, 1);
184187
});

src/daemon/handlers/interaction.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type { DaemonRequest, DaemonResponse, SessionState } from '../types.ts';
66
import { SessionStore } from '../session-store.ts';
77
import { evaluateIsPredicate, isSupportedPredicate } from '../is-predicates.ts';
88
import { extractNodeText, findNodeByLabel, isFillableType, pruneGroupNodes, resolveRefLabel } from '../snapshot-processing.ts';
9-
import { isClickLikeCommand } from '../script-utils.ts';
109
import {
1110
buildSelectorChainForNode,
1211
findSelectorChainMatch,
@@ -34,7 +33,7 @@ export async function handleInteractionCommands(params: {
3433
const dispatch = params.dispatch ?? dispatchCommand;
3534
const command = req.command;
3635

37-
if (isClickLikeCommand(command)) {
36+
if (command === 'press') {
3837
const session = sessionStore.get(sessionName);
3938
if (!session) {
4039
return {
@@ -44,8 +43,6 @@ export async function handleInteractionCommands(params: {
4443
}
4544
const directCoordinates = parseCoordinateTarget(req.positionals ?? []);
4645
if (directCoordinates) {
47-
// Coordinate-only press is handled by daemon fallback to preserve legacy passthrough semantics.
48-
if (command === 'press') return null;
4946
const data = await dispatch(
5047
session.device,
5148
'press',
@@ -67,7 +64,7 @@ export async function handleInteractionCommands(params: {
6764
const selectorAction = 'click';
6865
const refInput = req.positionals?.[0] ?? '';
6966
if (refInput.startsWith('@')) {
70-
const invalidRefFlagsResponse = refSnapshotFlagGuardResponse(command, req.flags);
67+
const invalidRefFlagsResponse = refSnapshotFlagGuardResponse('press', req.flags);
7168
if (invalidRefFlagsResponse) return invalidRefFlagsResponse;
7269
if (!session.snapshot) {
7370
return { ok: false, error: { code: 'INVALID_ARGS', message: 'No snapshot in session. Run snapshot first.' } };
@@ -575,7 +572,7 @@ const REF_UNSUPPORTED_FLAG_MAP: ReadonlyArray<[keyof CommandFlags, string]> = [
575572
];
576573

577574
function refSnapshotFlagGuardResponse(
578-
command: 'click' | 'press' | 'fill' | 'get',
575+
command: 'press' | 'fill' | 'get',
579576
flags: CommandFlags | undefined,
580577
): DaemonResponse | null {
581578
const unsupported = unsupportedRefSnapshotFlags(flags);
@@ -591,7 +588,6 @@ function refSnapshotFlagGuardResponse(
591588

592589
function parseCoordinateTarget(positionals: string[]): { x: number; y: number } | null {
593590
if (positionals.length < 2) return null;
594-
// Keep legacy "first two numeric tokens win" behavior for coordinate targets.
595591
const x = Number(positionals[0]);
596592
const y = Number(positionals[1]);
597593
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;

0 commit comments

Comments
 (0)