Skip to content

Commit c245906

Browse files
authored
fix(cli): forward --no-record from every recordable command reader (#1304) (#1305)
`--no-record` is accepted on every command (its key is in COMMON_COMMAND_SUPPORTED_FLAG_KEYS, which seeds every command schema's supportedFlags) and is documented as "Do not record this action", but no interaction/capture reader forwarded it into the options object the daemon request is built from. The flag parsed, then vanished. The rest of the chain was already wired: command-flags.ts maps options.noRecord onto the request, and recordActionEntry reads entry.flags?.noRecord to skip the action. Only the reader step was missing, so the documented behaviour never happened for any of press, click, fill, longpress, swipe, focus, type, scroll, get, is, find, snapshot, or wait. `app` was the sole command that forwarded it. Measured through the real argv path (parseArgs -> readInputFromCli), before this change: 13/13 commands accept `--no-record`, 0/13 reach the options object. After: 13/13. Fixed at the shared seam rather than per reader. noRecord is a common flag, so it gets a recordControlInputFromFlags() helper next to the existing settleInputFromFlags/repeatedInputFromFlags group helpers, and each recordable reader spreads it. A future reader picks it up by spreading one helper instead of re-deriving a flag it never names. The regression test covers all 13 recordable commands and fails without the fix ("press dropped --no-record").
1 parent a6789d0 commit c245906

6 files changed

Lines changed: 64 additions & 1 deletion

File tree

src/__tests__/cli-grammar.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { test } from 'vitest';
22
import assert from 'node:assert/strict';
33
import { readInputFromCli } from '../commands/cli-grammar.ts';
4+
import type { CommandName } from '../commands/command-metadata.ts';
45
import type { CliFlags } from '../commands/cli-grammar/flag-types.ts';
56

67
const BASE_FLAGS: CliFlags = {
@@ -138,6 +139,38 @@ test('find and is grammar decodes command action positionals', () => {
138139
assert.equal(isOptions.value, 'Welcome');
139140
});
140141

142+
// --no-record is accepted on every command (COMMON_COMMAND_SUPPORTED_FLAG_KEYS),
143+
// but it only reaches the daemon if the reader forwards it, so a reader that
144+
// never names it accepts the flag and silently ignores it. Cover every command
145+
// whose actions are recorded into a session script.
146+
test('readers forward --no-record instead of silently dropping it', () => {
147+
const recordable: Array<[CommandName, string[]]> = [
148+
['press', ['@e5']],
149+
['click', ['@e5']],
150+
['fill', ['id=email', 'qa@example.com']],
151+
['longpress', ['@e5']],
152+
['swipe', ['up']],
153+
['focus', ['10', '20']],
154+
['type', ['hello']],
155+
['scroll', ['down']],
156+
['get', ['attrs', '@e5']],
157+
['is', ['visible', 'id=title']],
158+
['find', ['label', 'Continue', 'exists']],
159+
['snapshot', []],
160+
['wait', ['Continue']],
161+
];
162+
163+
for (const [command, positionals] of recordable) {
164+
const options = readInputFromCli(command, positionals, { ...BASE_FLAGS, noRecord: true });
165+
assert.equal(options.noRecord, true, `${command} dropped --no-record`);
166+
}
167+
});
168+
169+
test('readers omit noRecord entirely when the flag is absent', () => {
170+
const options = readInputFromCli('press', ['@e5'], BASE_FLAGS);
171+
assert.equal(Object.hasOwn(options, 'noRecord'), false);
172+
});
173+
141174
test('is grammar accepts the selector-first form with a trailing predicate', () => {
142175
// `visible` is both a selector boolean key and a predicate; the trailing bare token
143176
// must be reserved as the predicate instead of being swallowed by the selector.

src/commands/capture/snapshot.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { PUBLIC_COMMANDS } from '../../command-catalog.ts';
22
import { SNAPSHOT_FLAGS } from '../cli-grammar/flag-groups.ts';
33
import { booleanField, integerField, stringField } from '../command-input.ts';
44
import { defineExecutableCommand } from '../command-contract.ts';
5-
import { commonInputFromFlags, direct } from '../cli-grammar/common.ts';
5+
import {
6+
commonInputFromFlags,
7+
direct,
8+
recordControlInputFromFlags,
9+
} from '../cli-grammar/common.ts';
610
import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts';
711
import { defineCommandFacet } from '../family/types.ts';
812
import { defineFieldCommandMetadata } from '../field-command-contract.ts';
@@ -41,6 +45,7 @@ const snapshotCliSchema = {
4145

4246
export const snapshotCliReader: CliReader = (_positionals, flags) => ({
4347
...commonInputFromFlags(flags),
48+
...recordControlInputFromFlags(flags),
4449
interactiveOnly: flags.snapshotInteractiveOnly,
4550
depth: flags.snapshotDepth,
4651
scope: flags.snapshotScope,

src/commands/capture/wait.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { defineExecutableCommand } from '../command-contract.ts';
1616
import {
1717
direct,
1818
optionalNumber,
19+
recordControlInputFromFlags,
1920
selectionOptionsFromFlags,
2021
selectorSnapshotOptionsFromFlags,
2122
} from '../cli-grammar/common.ts';
@@ -92,6 +93,7 @@ function readWaitOptionsFromPositionals(
9293
const base = {
9394
...selectionOptionsFromFlags(flags),
9495
...selectorSnapshotOptionsFromFlags(flags),
96+
...recordControlInputFromFlags(flags),
9597
};
9698
if (parsed.kind === 'sleep') return { ...base, durationMs: parsed.durationMs };
9799
if (parsed.kind === 'text') {

src/commands/cli-grammar/common.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ export function commonInputFromFlags(flags: CliFlags): Record<string, unknown> {
6666
});
6767
}
6868

69+
// --no-record is a common flag, but it only takes effect if the reader forwards
70+
// it: command-flags.ts maps options.noRecord onto the daemon request, and
71+
// recordActionEntry reads it from there. A reader that never names it accepts
72+
// the flag and silently drops it.
73+
export function recordControlInputFromFlags(flags: CliFlags): Record<string, unknown> {
74+
return compactRecord({
75+
noRecord: flags.noRecord,
76+
});
77+
}
78+
6979
export function selectionOptionsFromFlags(flags: CliFlags): SelectionOptions {
7080
return {
7181
platform: flags.platform,

src/commands/interaction/interactions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
optionalNumber,
2424
readElementTargetFromPositionals,
2525
readGetFormat,
26+
recordControlInputFromFlags,
2627
request,
2728
requiredDaemonString,
2829
repeatedInputFromFlags,
@@ -35,6 +36,7 @@ import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts';
3536
export const interactionCliReaders = {
3637
click: (positionals, flags) => ({
3738
...commonInputFromFlags(flags),
39+
...recordControlInputFromFlags(flags),
3840
...selectorSnapshotInputFromFlags(flags),
3941
...repeatedInputFromFlags(flags),
4042
...settleInputFromFlags(flags),
@@ -44,6 +46,7 @@ export const interactionCliReaders = {
4446
}),
4547
press: (positionals, flags) => ({
4648
...commonInputFromFlags(flags),
49+
...recordControlInputFromFlags(flags),
4750
...selectorSnapshotInputFromFlags(flags),
4851
...repeatedInputFromFlags(flags),
4952
...settleInputFromFlags(flags),
@@ -54,6 +57,7 @@ export const interactionCliReaders = {
5457
const decoded = readLongPressTargetFromPositionals(positionals);
5558
return {
5659
...commonInputFromFlags(flags),
60+
...recordControlInputFromFlags(flags),
5761
...selectorSnapshotInputFromFlags(flags),
5862
...settleInputFromFlags(flags),
5963
target: targetInputFromClientTarget(decoded),
@@ -62,6 +66,7 @@ export const interactionCliReaders = {
6266
},
6367
swipe: (positionals, flags) => ({
6468
...commonInputFromFlags(flags),
69+
...recordControlInputFromFlags(flags),
6570
...swipePayloadFromPositionals(positionals, {
6671
count: flags.count,
6772
pauseMs: flags.pauseMs,
@@ -70,18 +75,21 @@ export const interactionCliReaders = {
7075
}),
7176
focus: (positionals, flags) => ({
7277
...commonInputFromFlags(flags),
78+
...recordControlInputFromFlags(flags),
7379
x: Number(positionals[0]),
7480
y: Number(positionals[1]),
7581
}),
7682
type: (positionals, flags) => ({
7783
...commonInputFromFlags(flags),
84+
...recordControlInputFromFlags(flags),
7885
text: positionals.join(' '),
7986
delayMs: flags.delayMs,
8087
}),
8188
fill: (positionals, flags) => {
8289
const decoded = readFillTargetFromPositionals(positionals);
8390
return {
8491
...commonInputFromFlags(flags),
92+
...recordControlInputFromFlags(flags),
8593
...selectorSnapshotInputFromFlags(flags),
8694
...settleInputFromFlags(flags),
8795
target: targetInputFromClientTarget(decoded.target),
@@ -92,13 +100,15 @@ export const interactionCliReaders = {
92100
},
93101
scroll: (positionals, flags) => ({
94102
...commonInputFromFlags(flags),
103+
...recordControlInputFromFlags(flags),
95104
direction: readScrollDirection(positionals[0]),
96105
amount: optionalCliNumber(positionals[1]),
97106
pixels: flags.pixels,
98107
durationMs: flags.durationMs,
99108
}),
100109
get: (positionals, flags) => ({
101110
...commonInputFromFlags(flags),
111+
...recordControlInputFromFlags(flags),
102112
...selectorSnapshotInputFromFlags(flags),
103113
format: readGetFormat(positionals[0]),
104114
target: targetInputFromClientTarget(readElementTargetFromPositionals(positionals.slice(1))),

src/commands/interaction/selectors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
direct,
1212
optionalCliNumber,
1313
optionalNumber,
14+
recordControlInputFromFlags,
1415
request,
1516
selectionOptionsFromFlags,
1617
selectorSnapshotOptionsFromFlags,
@@ -64,6 +65,7 @@ function readFindOptionsFromPositionals(positionals: string[], flags: CliFlags):
6465
const base = {
6566
...findSnapshotOptionsFromFlags(flags),
6667
...selectionOptionsFromFlags(flags),
68+
...recordControlInputFromFlags(flags),
6769
first: flags.findFirst,
6870
last: flags.findLast,
6971
};
@@ -111,6 +113,7 @@ function readIsOptionsFromPositionals(positionals: string[], flags: CliFlags): I
111113
const base = {
112114
...selectorSnapshotOptionsFromFlags(flags),
113115
...selectionOptionsFromFlags(flags),
116+
...recordControlInputFromFlags(flags),
114117
};
115118
const normalized = normalizeIsPositionals(positionals);
116119
const predicate = normalized[0];

0 commit comments

Comments
 (0)