Skip to content

Commit 84033c7

Browse files
committed
fix: hint sparse snapshot fallback
1 parent 34713df commit 84033c7

5 files changed

Lines changed: 65 additions & 3 deletions

File tree

src/commands/cli-output.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const cliOutputFormatters: Partial<Record<CommandName, CliOutputFormatter>> = {
6262
result: result as Parameters<typeof snapshotCliOutput>[0]['result'],
6363
raw: input.raw as boolean | undefined,
6464
interactiveOnly: input.interactiveOnly as boolean | undefined,
65+
scope: input.scope as string | undefined,
6566
}),
6667
wait: messageOutput,
6768
alert: messageOutput,

src/commands/client-output.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export function snapshotCliOutput(params: {
107107
result: CaptureSnapshotResult;
108108
raw?: boolean;
109109
interactiveOnly?: boolean;
110+
scope?: string;
110111
}): CliOutput {
111112
const data = serializeSnapshotResult(params.result);
112113
return {
@@ -116,6 +117,7 @@ export function snapshotCliOutput(params: {
116117
text: formatSnapshotText(data, {
117118
raw: params.raw,
118119
flatten: params.interactiveOnly,
120+
scoped: typeof params.scope === 'string' && params.scope.trim().length > 0,
119121
}),
120122
};
121123
}

src/utils/__tests__/output.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,49 @@ test('formatSnapshotText prints snapshot warnings ahead of empty output', () =>
12961296
assert.match(text, /Interactive snapshot is empty after filtering 42 raw Android nodes/);
12971297
});
12981298

1299+
test('formatSnapshotText hints to use screenshot overlay refs for sparse snapshots', () => {
1300+
const text = withNoColor(() =>
1301+
formatSnapshotText({
1302+
nodes: [
1303+
{
1304+
ref: 'e1',
1305+
index: 0,
1306+
depth: 0,
1307+
type: 'Window',
1308+
label: 'Main',
1309+
},
1310+
],
1311+
truncated: false,
1312+
}),
1313+
);
1314+
1315+
assert.match(text, /Snapshot: 1 node/);
1316+
assert.match(text, /Hint: sparse accessibility snapshot returned 1 node/);
1317+
assert.match(text, /screenshot --overlay-refs/);
1318+
});
1319+
1320+
test('formatSnapshotText suppresses sparse snapshot hint for scoped reads', () => {
1321+
const text = withNoColor(() =>
1322+
formatSnapshotText(
1323+
{
1324+
nodes: [
1325+
{
1326+
ref: 'e1',
1327+
index: 0,
1328+
depth: 0,
1329+
type: 'StaticText',
1330+
label: 'Expanded details',
1331+
},
1332+
],
1333+
truncated: false,
1334+
},
1335+
{ scoped: true },
1336+
),
1337+
);
1338+
1339+
assert.doesNotMatch(text, /sparse accessibility snapshot/);
1340+
});
1341+
12991342
test('formatSnapshotText keeps flattened output and adds duplicate nav warning', () => {
13001343
const nodes = Array.from({ length: 24 }, (_, index) => ({
13011344
ref: `e${index + 1}`,

src/utils/output.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,15 @@ type SnapshotDiffLine = {
5555
text?: string;
5656
};
5757

58+
type SnapshotTextOptions = {
59+
raw?: boolean;
60+
flatten?: boolean;
61+
scoped?: boolean;
62+
};
63+
5864
export function formatSnapshotText(
5965
data: Record<string, unknown>,
60-
options: { raw?: boolean; flatten?: boolean } = {},
66+
options: SnapshotTextOptions = {},
6167
): string {
6268
const rawNodes = data.nodes;
6369
const nodes = Array.isArray(rawNodes) ? (rawNodes as SnapshotNode[]) : [];
@@ -606,10 +612,12 @@ function formatMuted(text: string, useColor: boolean): string {
606612
function buildSnapshotNotices(
607613
data: Record<string, unknown>,
608614
nodes: SnapshotNode[],
609-
options: { raw?: boolean; flatten?: boolean },
615+
options: SnapshotTextOptions,
610616
helperPresentation: AndroidHelperPresentationInput = { nodes, filteredCount: 0 },
611617
): string[] {
612618
const notices = readSnapshotWarnings(data);
619+
const sparseSnapshotHint = formatSparseSnapshotHint(nodes, options);
620+
if (sparseSnapshotHint) notices.push(sparseSnapshotHint);
613621
if (!options.raw && helperPresentation.filteredCount > 0) {
614622
notices.push(
615623
`Collapsed ${helperPresentation.filteredCount} Android helper node${helperPresentation.filteredCount === 1 ? '' : 's'} from the agent-facing text snapshot; use --raw or --json for the full hierarchy.`,
@@ -622,6 +630,15 @@ function buildSnapshotNotices(
622630
return notices;
623631
}
624632

633+
function formatSparseSnapshotHint(
634+
nodes: SnapshotNode[],
635+
options: Pick<SnapshotTextOptions, 'scoped'>,
636+
): string | null {
637+
if (options.scoped === true || nodes.length > 3) return null;
638+
const noun = nodes.length === 1 ? 'node' : 'nodes';
639+
return `Hint: sparse accessibility snapshot returned ${nodes.length} ${noun}. The app may expose limited accessibility metadata; run screenshot --overlay-refs for visual context.`;
640+
}
641+
625642
function readSnapshotWarnings(data: Record<string, unknown>): string[] {
626643
const rawWarnings = data.warnings;
627644
if (!Array.isArray(rawWarnings)) {

website/docs/docs/snapshots.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ It does not automatically switch to AX.
3838
- Those summaries intentionally show only a few labels for token efficiency. Use `snapshot --raw` when you need the full off-screen tree instead of the compact summary.
3939
- Add `-s "<label>"` (or `-s @ref`) to keep results screen-local.
4040
- Add `-d <depth>` when you only need upper hierarchy layers.
41-
- If `snapshot -i` returns 0 nodes on Android but the screen is visibly populated, trust `screenshot` as visual truth, wait briefly, then take one fresh `snapshot -i`.
4241
- If `snapshot -i -d <n>` says the interactive output is empty at that depth, retry once without `-d` before taking more shallow snapshots.
4342
- Re-snapshot after any UI mutation before reusing refs.
4443
- On Android after navigation or submit, snapshot capture retries suspicious trees for a short post-action deadline and `@ref` interactions refresh while that freshness window is active. If `snapshot -i` still disagrees with the visible screen, trust `screenshot`, wait briefly, then take one fresh snapshot instead of looping stale snapshots.

0 commit comments

Comments
 (0)