Skip to content

Commit 62f3914

Browse files
committed
refactor(screenshot): drop the dead crop outcome flag and cover the projection seams
- ScreenshotCropOutcome.cropped was a constant true that no caller read; the crop either returns (success) or throws, so the outcome reduces to the partialIntersection observation. - resolveScreenshotRectSpace and resolveSnapshotBounds were the only projection exports without coverage: pin the accepted-backend map, the unaccepted-backend typed refusal, and the viewport-root / union / empty bounds branches.
1 parent 643c9b8 commit 62f3914

3 files changed

Lines changed: 63 additions & 10 deletions

File tree

packages/capture-kit/src/snapshot-rect-projection.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import { test } from 'vitest';
22
import assert from 'node:assert/strict';
33
import fs from 'node:fs';
44
import path from 'node:path';
5+
import { SCREENSHOT_CROP_REASONS } from '@agent-device/contracts/capture';
6+
import { AppError } from '@agent-device/kernel/errors';
57
import type { Rect } from '@agent-device/kernel/snapshot';
68
import {
79
intersectScreenshotRect,
810
projectSnapshotRectToScreenshot,
11+
resolveScreenshotRectSpace,
12+
resolveSnapshotBounds,
913
type ScreenshotRectSpace,
1014
} from './snapshot-rect-projection.ts';
1115

@@ -56,3 +60,55 @@ test('the crop projection law agrees with every golden geometry case', () => {
5660
assert.deepEqual(intersection, fixture.expectedIntersection, fixture.name);
5761
}
5862
});
63+
64+
test('resolveScreenshotRectSpace maps each accepted backend to its projection space and refuses the rest', () => {
65+
assert.equal(resolveScreenshotRectSpace('android'), 'device-pixels');
66+
assert.equal(resolveScreenshotRectSpace('xctest'), 'viewport-points');
67+
assert.equal(resolveScreenshotRectSpace('macos-helper'), 'viewport-points');
68+
for (const backend of [undefined, 'linux', 'web', 'harmonyos', 'vega']) {
69+
assert.throws(
70+
() => resolveScreenshotRectSpace(backend),
71+
(error: unknown) =>
72+
error instanceof AppError &&
73+
error.code === 'UNSUPPORTED_OPERATION' &&
74+
error.details?.reason === SCREENSHOT_CROP_REASONS.targetNotAccepted,
75+
);
76+
}
77+
});
78+
79+
test('resolveSnapshotBounds prefers the largest viewport root, else unions every positive rect excluding unlabeled images', () => {
80+
assert.deepEqual(
81+
resolveSnapshotBounds([
82+
{ type: 'XCUIElementTypeImage', label: '', rect: { x: 0, y: 0, width: 50, height: 50 } },
83+
{
84+
type: 'XCUIElementTypeApplication',
85+
label: 'Settings',
86+
rect: { x: 0, y: 0, width: 402, height: 874 },
87+
},
88+
{
89+
type: 'XCUIElementTypeButton',
90+
label: 'Continue',
91+
rect: { x: 100, y: 100, width: 200, height: 50 },
92+
},
93+
]),
94+
{ x: 0, y: 0, width: 402, height: 874 },
95+
);
96+
assert.deepEqual(
97+
resolveSnapshotBounds([
98+
{ type: 'XCUIElementTypeButton', label: 'A', rect: { x: 10, y: 20, width: 100, height: 40 } },
99+
{
100+
type: 'XCUIElementTypeButton',
101+
label: 'B',
102+
rect: { x: 300, y: 400, width: 60, height: 30 },
103+
},
104+
{ type: 'XCUIElementTypeImage', label: '', rect: { x: 0, y: 0, width: 999, height: 999 } },
105+
]),
106+
{ x: 10, y: 20, width: 350, height: 410 },
107+
);
108+
assert.equal(
109+
resolveSnapshotBounds([
110+
{ type: 'XCUIElementTypeButton', label: 'A', rect: { x: 0, y: 0, width: 0, height: 50 } },
111+
]),
112+
null,
113+
);
114+
});

src/daemon/__tests__/screenshot-crop.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { writeSolidPng } from './screenshot-runtime-fixture.ts';
2121

2222
test('the warning composition is the single owner: partial intersection and only', () => {
2323
expect(buildScreenshotCropWarnings(undefined)).toEqual([]);
24-
expect(buildScreenshotCropWarnings({ cropped: true, partialIntersection: false })).toEqual([]);
25-
const warnings = buildScreenshotCropWarnings({ cropped: true, partialIntersection: true });
24+
expect(buildScreenshotCropWarnings({ partialIntersection: false })).toEqual([]);
25+
const warnings = buildScreenshotCropWarnings({ partialIntersection: true });
2626
expect(warnings).toHaveLength(1);
2727
expect(warnings[0]).toMatch(new RegExp(`^${SCREENSHOT_CROP_REASONS.partialIntersection}: `));
2828
});
@@ -129,7 +129,7 @@ test('an android crop runs the fresh full-tree capture once and leaves the sessi
129129
});
130130
try {
131131
const outcome = await seam.run();
132-
expect(outcome).toEqual({ cropped: true, partialIntersection: false });
132+
expect(outcome).toEqual({ partialIntersection: false });
133133
expect(await readPngSize(seam.screenshotPath)).toEqual({ width: 40, height: 20 });
134134
expect(seam.captureSnapshot).toHaveBeenCalledTimes(1);
135135
const options = seam.captureSnapshot.mock.calls[0]?.[0].options;
@@ -153,7 +153,7 @@ test('an iOS simulator crop projects the points-space frame into the 3x capture'
153153
});
154154
try {
155155
const outcome = await seam.run();
156-
expect(outcome).toEqual({ cropped: true, partialIntersection: false });
156+
expect(outcome).toEqual({ partialIntersection: false });
157157
expect(await readPngSize(seam.screenshotPath)).toEqual({ width: 300, height: 120 });
158158
} finally {
159159
seam.dispose();
@@ -169,7 +169,7 @@ test('a frame that runs past the image is clipped and reported partial', async (
169169
});
170170
try {
171171
const outcome = await seam.run();
172-
expect(outcome).toEqual({ cropped: true, partialIntersection: true });
172+
expect(outcome).toEqual({ partialIntersection: true });
173173
expect(await readPngSize(seam.screenshotPath)).toEqual({ width: 20, height: 20 });
174174
} finally {
175175
seam.dispose();

src/daemon/screenshot-crop.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ import { runtimeExecutionFromContext } from './snapshot-runtime-capture-input.ts
2121
import type { BoundScreenshotRuntime } from './screenshot-runtime-binding.ts';
2222
import type { SessionState } from './types.ts';
2323

24-
export type ScreenshotCropOutcome = Readonly<{
25-
cropped: true;
26-
partialIntersection: boolean;
27-
}>;
24+
export type ScreenshotCropOutcome = Readonly<{ partialIntersection: boolean }>;
2825

2926
const CROP_PARTIAL_INTERSECTION_WARNING = `${SCREENSHOT_CROP_REASONS.partialIntersection}: the selector frame extends past the captured image; the crop was clipped to the image frame`;
3027

@@ -100,7 +97,7 @@ export async function cropScreenshotToSelector(params: {
10097
box.width < projected.width ||
10198
box.height < projected.height;
10299
await cropPngFile(screenshotPath, box);
103-
return { cropped: true, partialIntersection };
100+
return { partialIntersection };
104101
}
105102

106103
/** Resolve the selector to exactly one framed node, or fail with a typed crop reason. */

0 commit comments

Comments
 (0)