mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
refactor(snapshot): drop the bridge truncation dimension; stop promising --scope for depth caps (#2511)
The dimension and limit the bridge adapter inferred for a cut capture had no renderer and one consumer, the comparison-identity string, where the kind alone gives the same comparability. The runner never produced them. The depth-cap warning suggested --scope to read deeper content; on iOS scope narrows presentation and acquisition stays scope-blind.
This commit is contained in:
committed by
GitHub
parent
1527146507
commit
8e8eeb2ced
+2
-1
@@ -7,7 +7,8 @@
|
||||
had no disclosure at all, so an agent read a screen missing its footer, tab bar, or the items
|
||||
after a long list as complete — the backends walk the tree in document order, so what falls
|
||||
off is what comes last, on screen or not. One shared warning renders from the shared flag; the
|
||||
limit and dimension stay backend-side.
|
||||
limit and dimension stay backend-side. The depth-cap warning no longer suggests `--scope` as a
|
||||
way to read deeper: on iOS, scope narrows the presented view and acquisition stays scope-blind.
|
||||
- Changed: the iOS Simulator AX bridge caps a capture at 5000 nodes, up from 1500, the Android
|
||||
helper's bound. Measured on a synthetic 600-row screen, acquisition time did not move with the
|
||||
cap (the native read fetches the whole tree; the cap only stops conversion) while the 1500 cut
|
||||
|
||||
@@ -377,7 +377,7 @@ function acquisition(
|
||||
truncated: false,
|
||||
viewport: { kind: 'reported', rect: viewport },
|
||||
lineage: { targetId: 'simulator-1', generation: 'generation-1' },
|
||||
residue: [{ kind: 'truncated', dimension: 'payload', limit: 2000 }],
|
||||
residue: [{ kind: 'truncated' }],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ function validationFacts(request: IosSnapshotRequest): IosSnapshotValidationFact
|
||||
viewport: { kind: 'reported', rect: viewport },
|
||||
hittability: { kind: 'available' },
|
||||
lineage: { targetId: 'simulator-1', generation: 'generation-1' },
|
||||
residue: [{ kind: 'truncated', dimension: 'payload', limit: 2000 }],
|
||||
residue: [{ kind: 'truncated' }],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ test('comparison identity rejects every identity axis and residue mismatch', ()
|
||||
{ ...base, intent: 'surface-observation' },
|
||||
{ ...base, lineage: { targetId: 'simulator-1', generation: 'generation-2' } },
|
||||
{ ...base, presentationKey: { ...base.presentationKey, depth: 1 } },
|
||||
{ ...base, residue: [{ kind: 'truncated', dimension: 'nodes' }] },
|
||||
{ ...base, residue: [{ kind: 'truncated' }] },
|
||||
];
|
||||
assert.equal(areIosSnapshotComparisonIdentitiesEqual(base, { ...base }), true);
|
||||
assert.equal(
|
||||
|
||||
@@ -126,11 +126,7 @@ function residueIdentity(residue: IosAcquisitionResidue): string {
|
||||
case 'missing-viewport':
|
||||
return JSON.stringify({ kind: residue.kind, reason: residue.reason });
|
||||
case 'truncated':
|
||||
return JSON.stringify({
|
||||
kind: residue.kind,
|
||||
dimension: residue.dimension,
|
||||
limit: residue.limit,
|
||||
});
|
||||
return JSON.stringify({ kind: residue.kind });
|
||||
case 'stale-generation':
|
||||
return JSON.stringify({
|
||||
kind: residue.kind,
|
||||
|
||||
@@ -84,7 +84,7 @@ function appAccessibilityDefectWarning(verdict: SnapshotQualityVerdict): string[
|
||||
function depthWarning(verdict: SnapshotQualityVerdict): string[] {
|
||||
if (verdict.effectiveDepth === undefined) return [];
|
||||
return [
|
||||
`Some deeper accessibility nodes were omitted; this tree is capped at depth ${verdict.effectiveDepth}. Re-run with --depth ${verdict.effectiveDepth} --scope <container> only if you need deeper content.`,
|
||||
`Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth ${verdict.effectiveDepth}. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.`,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ test('penalty-deferred recovered captures suppress the fallback warning but keep
|
||||
);
|
||||
|
||||
assert.deepEqual(warnings, [
|
||||
'Some deeper accessibility nodes were omitted; this tree is capped at depth 56. Re-run with --depth 56 --scope <container> only if you need deeper content.',
|
||||
'Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth 56. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ test('non-presentation recovery keeps the generic warning for the same reason te
|
||||
|
||||
assert.deepEqual(warnings, [
|
||||
'Detected an overly complex or slow accessibility tree. Fell back to the private-ax snapshot backend. It is OK to continue; use --json to inspect snapshotQuality.reason if you need recovery details.',
|
||||
'Some deeper accessibility nodes were omitted; this tree is capped at depth 56. Re-run with --depth 56 --scope <container> only if you need deeper content.',
|
||||
'Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth 56. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ test('presentation failures identify a runner bug and preserve composed warnings
|
||||
|
||||
assert.deepEqual(warnings, [
|
||||
'Agent Device could not safely present the captured accessibility tree and fell back to the private-ax snapshot backend. This is an Agent Device runner bug, not an app accessibility-tree issue. Use screenshot as visual truth and report snapshotQuality.reason with the screenshot.',
|
||||
'Some deeper accessibility nodes were omitted; this tree is capped at depth 56. Re-run with --depth 56 --scope <container> only if you need deeper content.',
|
||||
'Some deeper accessibility nodes were omitted; the accessibility backend capped this tree at depth 56. Navigate so the content you need sits higher in the tree, and use screenshot as visual truth for the rest.',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -114,7 +114,6 @@ export type IosHittabilityEvidence =
|
||||
}>;
|
||||
|
||||
export type IosProviderPrunedField = 'nodes' | 'depth' | 'scope' | 'interactive-only';
|
||||
export type IosTruncationDimension = 'nodes' | 'depth' | 'payload';
|
||||
|
||||
export type IosAcquisitionResidue =
|
||||
| Readonly<{
|
||||
@@ -127,8 +126,6 @@ export type IosAcquisitionResidue =
|
||||
}>
|
||||
| Readonly<{
|
||||
kind: 'truncated';
|
||||
dimension: IosTruncationDimension;
|
||||
limit?: number;
|
||||
}>
|
||||
| Readonly<{
|
||||
kind: 'stale-generation';
|
||||
|
||||
@@ -78,11 +78,7 @@ test('the Simulator AX source returns raw acquisition facts and discloses unsupp
|
||||
});
|
||||
assert.equal(rawDepthOne.stage, 'acquired');
|
||||
assert.equal(fixture.requestedDepths.at(-1), 1);
|
||||
assert.ok(
|
||||
rawDepthOne.acquisition.residue.some(
|
||||
(item) => item.kind === 'truncated' && item.dimension === 'depth',
|
||||
),
|
||||
);
|
||||
assert.ok(rawDepthOne.acquisition.residue.some((item) => item.kind === 'truncated'));
|
||||
|
||||
fixture.responsePid = 999;
|
||||
const outcome = await source.acquire({
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { AppError } from '@agent-device/kernel/errors';
|
||||
import type { CaptureHint, IosSnapshotAcquisition } from '@agent-device/contracts/ios-snapshot';
|
||||
import type {
|
||||
CaptureHint,
|
||||
IosSnapshotAcquisition,
|
||||
IosViewportEvidence,
|
||||
} from '@agent-device/contracts/ios-snapshot';
|
||||
import { ensureSnapshotBridgeBinary } from './cache.ts';
|
||||
import { createSnapshotSourceDeadline, remainingSnapshotSourceMs } from './deadline.ts';
|
||||
import { AcceptedDepthHints, type DepthHintDecision } from './depth-hints.ts';
|
||||
@@ -94,13 +98,7 @@ export function createSimulatorSnapshotSource(
|
||||
deadline,
|
||||
});
|
||||
remainingSnapshotSourceMs(deadline, 'snapshot-decode-deadline');
|
||||
const acquisition = createAcquisition(
|
||||
request.hint,
|
||||
request.target,
|
||||
envelope,
|
||||
limits,
|
||||
maxDepth,
|
||||
);
|
||||
const acquisition = createAcquisition(request.hint, request.target, envelope, limits);
|
||||
recordRecovery(
|
||||
host,
|
||||
depthHints,
|
||||
@@ -216,7 +214,6 @@ function createAcquisition(
|
||||
target: SnapshotSourceRequest['target'],
|
||||
envelope: SnapshotBridgeEnvelope,
|
||||
limits: SnapshotSourceLimits,
|
||||
maxDepth: number,
|
||||
): IosSnapshotAcquisition {
|
||||
if (envelope.automationEnabled !== true) {
|
||||
throw snapshotSourceError('unsupported', 'automation-mode-unavailable');
|
||||
@@ -243,14 +240,7 @@ function createAcquisition(
|
||||
const nodes = Object.freeze(
|
||||
decoded.nodes.map((node) => Object.freeze({ ...node, pid: target.pid })),
|
||||
);
|
||||
const residue = createAcquisitionResidue(
|
||||
hint,
|
||||
truncated,
|
||||
decoded,
|
||||
limits,
|
||||
maxDepth,
|
||||
nodes.length,
|
||||
);
|
||||
const residue = createAcquisitionResidue(hint, truncated, decoded.viewport);
|
||||
const lineage = Object.freeze({
|
||||
...(target.targetId ? { targetId: target.targetId } : {}),
|
||||
generation,
|
||||
@@ -276,36 +266,16 @@ function createAcquisition(
|
||||
function createAcquisitionResidue(
|
||||
hint: CaptureHint,
|
||||
truncated: boolean,
|
||||
decoded: ReturnType<typeof decodeSnapshotBridgeTree>,
|
||||
limits: SnapshotSourceLimits,
|
||||
maxDepth: number,
|
||||
nodeCount: number,
|
||||
viewport: IosViewportEvidence,
|
||||
) {
|
||||
return Object.freeze([
|
||||
{ kind: 'unavailable-fact', fact: 'hittability' } as const,
|
||||
...(hint.interactiveOnly
|
||||
? ([{ kind: 'unavailable-fact', fact: 'interactive-query' }] as const)
|
||||
: []),
|
||||
...(truncated
|
||||
? [truncationResidue(decoded.maxTraversalDepth, nodeCount, limits, maxDepth)]
|
||||
: []),
|
||||
...(decoded.viewport.kind === 'missing'
|
||||
? ([{ kind: 'missing-viewport', reason: decoded.viewport.reason }] as const)
|
||||
...(truncated ? ([{ kind: 'truncated' }] as const) : []),
|
||||
...(viewport.kind === 'missing'
|
||||
? ([{ kind: 'missing-viewport', reason: viewport.reason }] as const)
|
||||
: []),
|
||||
]);
|
||||
}
|
||||
|
||||
function truncationResidue(
|
||||
maxTraversalDepth: number,
|
||||
nodeCount: number,
|
||||
limits: SnapshotSourceLimits,
|
||||
maxDepth: number,
|
||||
): { kind: 'truncated'; dimension: 'nodes' | 'depth' | 'payload'; limit?: number } {
|
||||
if (nodeCount >= limits.maxNodes) {
|
||||
return { kind: 'truncated', dimension: 'nodes', limit: limits.maxNodes };
|
||||
}
|
||||
if (maxTraversalDepth >= maxDepth) {
|
||||
return { kind: 'truncated', dimension: 'depth', limit: maxDepth };
|
||||
}
|
||||
return { kind: 'truncated', dimension: 'payload', limit: limits.maxResponseBytes };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user