Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/sdk/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { ANDROID_CONTENT_RECOVERY_REASONS } from '@agent-device/contracts/android-snapshot-quality';
import { throwDaemonError } from '@agent-device/kernel/errors';
import { AppError, isUnreadableCaptureContentError, normalizeAgentDeviceError } from './index.ts';

// Public-entry contract, through `./index.ts`: a client polling with `snapshot` classifies the
// error it caught after the daemon wire round trip, not the producer's in-process error.

function caughtAfterWire(thrown: AppError): unknown {
try {
throwDaemonError(normalizeAgentDeviceError(thrown));
} catch (error) {
return error;
}
return assert.fail('throwDaemonError returned');
}

test('an Android content verdict stays unreadable across the daemon wire', () => {
for (const reason of ANDROID_CONTENT_RECOVERY_REASONS) {
const verdict = new AppError('COMMAND_FAILED', 'insufficient app content', {
androidSnapshotHelperFailureReason: reason,
retriable: true,
});

assert.equal(isUnreadableCaptureContentError(caughtAfterWire(verdict)), true, reason);
}
});

test('a retriable mechanism failure is not an unreadable capture', () => {
const adbDrop = new AppError('COMMAND_FAILED', 'adb connection dropped', {
reason: 'connection_dropped',
retriable: true,
});

assert.equal(isUnreadableCaptureContentError(caughtAfterWire(adbDrop)), false);
});
1 change: 1 addition & 0 deletions src/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {
normalizeAgentDeviceError,
} from '@agent-device/kernel/errors';
export { centerOfRect } from '@agent-device/kernel/snapshot';
export { isUnreadableCaptureContentError } from '@agent-device/contracts/android-snapshot-quality';

// The types the root's two functions are written in. `createAgentDeviceClient` returns
// `AgentDeviceClient`, whose every option and result type is a named type of
Expand Down
1 change: 1 addition & 0 deletions test/integration/installed-package-metro.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ test('installed package exposes Node APIs and packaged companion tunnel entrypoi
'createAgentDeviceClient',
'createLocalArtifactAdapter',
'isAgentDeviceError',
'isUnreadableCaptureContentError',
'normalizeAgentDeviceError',
]);
assert.equal(imports.rootClientSnapshot, 'function');
Expand Down
4 changes: 4 additions & 0 deletions website/docs/docs/client-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Entry points for driving devices from Node.js:
- `createAgentDeviceClient(options?)`
- `createLocalArtifactAdapter(options?)`
- `AppError`, `isAgentDeviceError(error)`, `normalizeAgentDeviceError(error)`
- `isUnreadableCaptureContentError(error)`: true when an Android helper capture found no readable
app content yet (`empty-helper-output`, `system-window-only`, `content-poor-app-window`), for
example while the app is still loading. Polling callers may keep polling. False for every other
platform, backend, and mechanism failure.
- `centerOfRect(rect)`
- types: `AgentDeviceClient`, `AgentDeviceClientConfig`, `AgentDeviceDevice`
- types: every option and result type of the client's methods, among them `AppOpenOptions`,
Expand Down
Loading