mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
59d28e8446
* refactor: add provider-first device lab tests * refactor: tighten device lab provider seams * test: cover provider lab contracts * docs: record device lab harness direction * ci: run device lab integration tests * test: move device lab under integration * test: extract device lab helpers * refactor: centralize apps filter defaults * test: drop lab-covered unit tests * test: fold platform happy paths into device lab * test: reuse device lab helpers * test: move device lab to in-process harness * test: replace session handler cases with device lab * test: harden device lab scenario contracts * docs: define unit test retention policy * test: expand provider device lab coverage * test: harden provider device lab coverage * test: cover manifest install and runner session contracts * chore: remove unused provider cleanup code * test: split android find device lab scenario * test: track provider lab architecture progress * test: clarify provider lab roadmap progress * test: advance provider lab session coverage * test: move menubar click routing to device lab * test: move menubar snapshots to device lab * refactor: centralize screenshot flag plumbing * refactor: colocate screenshot flag metadata * test: cover all public commands in device lab * test: move macos wait success to device lab * test: drop redundant perf and diff units * test: move push payload paths to device lab * test: move network parsing to device lab * test: move log cleanup to device lab * test: move log restart and boot to device lab * test: move ios physical boot to device lab * test: cover perf startup in device lab * test: extract android and ios device lab worlds * test: trim device lab world surface * test: split snapshot capture unit coverage * test: deepen device lab coverage and trim handler units * test: clean up device lab migration scaffolding * test: report device lab public command coverage * refactor: make Apple provider seams semantic * refactor: tighten device inventory and Linux provider seams * refactor: tighten request provider scoping * refactor: add semantic macos host provider * test: broaden device lab find coverage * test: cover workflow flags in device lab * refactor: promote linux input provider seam * test: clarify device lab flag coverage * test: classify snapshot force-full progress * test: enforce device lab progress in ci * test: stabilize device lab ci * test: move packaged metro smoke to integration * test: drop stale provider seam coverage * test: harden provider scope regression coverage * refactor: remove stale platform barrels * refactor: keep linux clipboard and screenshots semantic * refactor: move macos host tools behind provider * fix: honor remote artifact output paths * test: deepen runtime coverage for daemon and runner paths * test: share loopback test helpers * refactor: make daemon runtime importable * fix: honor replay target metadata * chore: tighten final device lab quality gates * test: share device lab setup helpers * test: remove generic apple lab fallback * test: deduplicate device lab helpers * chore: tighten fallow duplication signal * refactor: share apple diagnostic helpers * fix: detect active android ime during fill verification * test: consolidate provider-backed integration suite * ci: fix fallow and iOS smoke setup * chore: consolidate cleanup after ci fixes * test: split vitest unit and integration projects * docs: mention MCP discovery metadata * docs: add agent skills context pointers * fix: close provider recording coverage gaps * fix: restore mcp compatibility smoke * test: cover provider edge regressions * test: consolidate loopback helpers * docs: remove stale provider routing reference * fix: harden final provider review issues * chore: defer mcp cleanup from provider refactor
247 lines
9.1 KiB
TypeScript
247 lines
9.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { test } from 'vitest';
|
|
import { assertFlatToolCall, assertPngFile } from './assertions.ts';
|
|
import { PROVIDER_SCENARIO_LINUX } from './fixtures.ts';
|
|
import { createProviderScenarioTempPath, withProviderScenarioResource } from './harness.ts';
|
|
import { createLinuxDesktopWorld } from './linux-world.ts';
|
|
import { runProviderScenario } from './scenario.ts';
|
|
|
|
test('Provider-backed integration Linux desktop flow uses semantic desktop and input providers', async () => {
|
|
await withProviderScenarioResource(
|
|
createLinuxDesktopWorld,
|
|
async ({ daemon, desktopCalls, localLinuxDevices, semanticCalls, toolCalls }) => {
|
|
const screenshotPath = createProviderScenarioTempPath(
|
|
'agent-device-provider-scenario-linux',
|
|
'png',
|
|
);
|
|
assert.equal(localLinuxDevices[0]?.platform, 'linux');
|
|
assert.equal(localLinuxDevices[0]?.target, 'desktop');
|
|
|
|
try {
|
|
const devices = await daemon.client().devices.list({ platform: 'linux' });
|
|
assert.equal(devices.length, 1);
|
|
assert.equal(devices[0]?.platform, 'linux');
|
|
assert.equal(devices[0]?.id, PROVIDER_SCENARIO_LINUX.id);
|
|
assert.equal(devices[0]?.target, 'desktop');
|
|
|
|
await runProviderScenario(daemon, [
|
|
{
|
|
name: 'open calculator app',
|
|
command: 'open',
|
|
positionals: ['gnome-calculator'],
|
|
flags: { platform: 'linux' },
|
|
},
|
|
{
|
|
name: 'capture interactive snapshot',
|
|
command: 'snapshot',
|
|
flags: { snapshotInteractiveOnly: true },
|
|
assert: (snapshot) => {
|
|
const digitFive = snapshot.json?.result?.data?.nodes?.find(
|
|
(node: { label?: string }) => node.label === '5',
|
|
);
|
|
assert.equal(digitFive?.ref, 'e2', JSON.stringify(snapshot.json));
|
|
},
|
|
},
|
|
{
|
|
name: 'scope snapshot to calculator frame with depth limit',
|
|
command: 'snapshot',
|
|
flags: { snapshotScope: '@e1', snapshotDepth: 0 },
|
|
assert: (snapshot) => {
|
|
assert.deepEqual(
|
|
snapshot.json?.result?.data?.nodes?.map((node: { label?: string }) => node.label),
|
|
['Calculator'],
|
|
);
|
|
},
|
|
},
|
|
{
|
|
name: 'scope snapshot to ref from previous broad snapshot source',
|
|
command: 'snapshot',
|
|
flags: { snapshotScope: '@e3' },
|
|
assert: (snapshot) => {
|
|
assert.deepEqual(
|
|
snapshot.json?.result?.data?.nodes?.map((node: { label?: string }) => node.label),
|
|
['Clear'],
|
|
);
|
|
},
|
|
},
|
|
{
|
|
name: 'refresh broad interactive snapshot after scoped output',
|
|
command: 'snapshot',
|
|
flags: { snapshotInteractiveOnly: true },
|
|
},
|
|
{
|
|
name: 'press snapshot ref',
|
|
command: 'press',
|
|
positionals: ['@e2'],
|
|
expectData: { x: 60, y: 100 },
|
|
},
|
|
{
|
|
name: 'read snapshot ref text through Linux accessibility',
|
|
command: 'get',
|
|
positionals: ['text', '@e2'],
|
|
expectData: { text: '5' },
|
|
},
|
|
{
|
|
name: 'press coordinates',
|
|
command: 'press',
|
|
positionals: ['42', '84'],
|
|
expectData: { x: 42, y: 84 },
|
|
},
|
|
{
|
|
name: 'secondary click coordinates',
|
|
command: 'click',
|
|
positionals: ['42', '84'],
|
|
flags: { clickButton: 'secondary' },
|
|
expectData: { button: 'secondary' },
|
|
},
|
|
{
|
|
name: 'middle click coordinates',
|
|
command: 'click',
|
|
positionals: ['42', '84'],
|
|
flags: { clickButton: 'middle' },
|
|
expectData: { button: 'middle' },
|
|
},
|
|
{
|
|
name: 'double tap coordinates',
|
|
command: 'press',
|
|
positionals: ['42', '84'],
|
|
flags: { doubleTap: true },
|
|
expectData: { doubleTap: true },
|
|
},
|
|
{
|
|
name: 'focus coordinates',
|
|
command: 'focus',
|
|
positionals: ['42', '84'],
|
|
expectData: { x: 42, y: 84 },
|
|
},
|
|
{
|
|
name: 'long press coordinates',
|
|
command: 'longpress',
|
|
positionals: ['42', '84', '1'],
|
|
},
|
|
{
|
|
name: 'swipe coordinates',
|
|
command: 'swipe',
|
|
positionals: ['10', '20', '30', '40', '16'],
|
|
expectData: { timingMode: 'direct' },
|
|
},
|
|
{
|
|
name: 'fill snapshot ref',
|
|
command: 'fill',
|
|
positionals: ['@e2', 'Seven'],
|
|
flags: { delayMs: 1 },
|
|
expectData: { text: 'Seven' },
|
|
},
|
|
{
|
|
name: 'fill coordinates',
|
|
command: 'fill',
|
|
positionals: ['42', '84', 'Eight'],
|
|
flags: { delayMs: 1 },
|
|
expectData: { x: 42, y: 84, text: 'Eight' },
|
|
},
|
|
{
|
|
name: 'scroll by pixels',
|
|
command: 'scroll',
|
|
positionals: ['down'],
|
|
flags: { pixels: 45 },
|
|
expectData: { pixels: 45 },
|
|
},
|
|
{
|
|
name: 'scroll up',
|
|
command: 'scroll',
|
|
positionals: ['up'],
|
|
expectData: { direction: 'up' },
|
|
},
|
|
{
|
|
name: 'type text',
|
|
command: 'type',
|
|
positionals: ['5'],
|
|
expectData: { text: '5' },
|
|
},
|
|
{
|
|
name: 'write clipboard',
|
|
command: 'clipboard',
|
|
positionals: ['write', 'linux otp 314159'],
|
|
expectData: { textLength: 16 },
|
|
},
|
|
{
|
|
name: 'read clipboard',
|
|
command: 'clipboard',
|
|
positionals: ['read'],
|
|
expectData: { text: 'linux otp 314159' },
|
|
},
|
|
{
|
|
name: 'capture screenshot artifact',
|
|
command: 'screenshot',
|
|
positionals: [screenshotPath],
|
|
flags: {
|
|
screenshotFullscreen: true,
|
|
screenshotNoStabilize: true,
|
|
},
|
|
expectData: { path: screenshotPath },
|
|
assert: () => {
|
|
assertPngFile(screenshotPath);
|
|
},
|
|
},
|
|
{ name: 'navigate back', command: 'back' },
|
|
{ name: 'show desktop', command: 'home' },
|
|
]);
|
|
|
|
const actions = daemon.session()?.actions ?? [];
|
|
assert.ok(
|
|
actions.some(
|
|
(action) =>
|
|
action.command === 'fill' &&
|
|
action.positionals.join(' ') === '@e2 Seven' &&
|
|
action.flags.delayMs === 1,
|
|
),
|
|
'Expected ref fill action to be recorded on the session',
|
|
);
|
|
assert.ok(
|
|
actions.some(
|
|
(action) =>
|
|
action.command === 'fill' &&
|
|
action.positionals.join(' ') === '42 84 Eight' &&
|
|
action.flags.delayMs === 1,
|
|
),
|
|
'Expected coordinate fill action to be recorded on the session',
|
|
);
|
|
|
|
const close = await daemon.callCommand('close', ['gnome-calculator']);
|
|
assert.equal(close.statusCode, 200, JSON.stringify(close.json));
|
|
assert.deepEqual(desktopCalls, [
|
|
['open', 'gnome-calculator'],
|
|
['close', 'gnome-calculator'],
|
|
]);
|
|
assertFlatToolCall(semanticCalls, ['accessibility', 'frontmost-app']);
|
|
assertFlatToolCall(semanticCalls, ['clipboard', 'write', 'linux otp 314159']);
|
|
assertFlatToolCall(semanticCalls, ['clipboard', 'read']);
|
|
assertFlatToolCall(semanticCalls, ['screenshot', screenshotPath, 'true', 'false', 'app']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'click', '60', '100', 'primary']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'click', '42', '84', 'primary']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'click', '42', '84', 'secondary']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'click', '42', '84', 'middle']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'double-click', '42', '84']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'long-press', '42', '84', '1']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'drag', '10', '20', '30', '40', '16']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'type', 'Seven', '1']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'type', 'Eight', '1']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'type', '5', '0']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'key', 'ctrl+a']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'key', 'alt+Left']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'key', 'super+d']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'scroll', 'down', '', '45']);
|
|
assertFlatToolCall(semanticCalls, ['input', 'scroll', 'up', '', '']);
|
|
assert.deepEqual(
|
|
toolCalls,
|
|
[],
|
|
'Expected Linux Provider-backed integration input to stay semantic',
|
|
);
|
|
} finally {
|
|
fs.rmSync(screenshotPath, { force: true });
|
|
}
|
|
},
|
|
);
|
|
});
|