Files
callstack__agent-device/test/integration/provider-scenarios/linux-world.ts
Michał Pierzchała 59d28e8446 refactor: add provider-first device lab tests (#542)
* 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
2026-05-18 14:50:52 +02:00

195 lines
5.7 KiB
TypeScript

import fs from 'node:fs';
import assert from 'node:assert/strict';
import { listLinuxDevices } from '../../../src/platforms/linux/devices.ts';
import { createLocalLinuxToolProvider } from '../../../src/platforms/linux/tool-provider.ts';
import type { DeviceInfo } from '../../../src/utils/device.ts';
import { validPng } from './assertions.ts';
import { PROVIDER_SCENARIO_LINUX } from './fixtures.ts';
import {
createProviderScenarioHarness,
restoreEnv,
type ProviderScenarioHarness,
} from './harness.ts';
import type { FlatToolCall } from './providers.ts';
export type LinuxDesktopWorld = {
daemon: ProviderScenarioHarness;
localLinuxDevices: DeviceInfo[];
toolCalls: Array<[string, string[]]>;
desktopCalls: Array<[string, string]>;
semanticCalls: FlatToolCall[];
close: () => Promise<void>;
};
export async function createLinuxDesktopWorld(): Promise<LinuxDesktopWorld> {
const previousSessionType = process.env.XDG_SESSION_TYPE;
const previousWaylandDisplay = process.env.WAYLAND_DISPLAY;
const previousAuthHook = process.env.AGENT_DEVICE_HTTP_AUTH_HOOK;
const previousPlatform = process.platform;
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
assert.deepEqual(await listLinuxDevices(), []);
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
process.env.XDG_SESSION_TYPE = 'x11';
delete process.env.WAYLAND_DISPLAY;
delete process.env.AGENT_DEVICE_HTTP_AUTH_HOOK;
const localLinuxDevices = await listLinuxDevices();
const toolCalls: Array<[string, string[]]> = [];
const desktopCalls: Array<[string, string]> = [];
const semanticCalls: FlatToolCall[] = [];
let clipboardText = '';
const linuxToolProvider = createLocalLinuxToolProvider({
whichCommand: async (cmd) =>
cmd === 'gnome-calculator' || cmd === 'xdotool' || cmd === 'wmctrl',
runCommand: async (cmd, args) => {
toolCalls.push([cmd, args]);
return { stdout: '', stderr: '', exitCode: 0 };
},
desktop: {
openTarget: async (target) => {
desktopCalls.push(['open', target]);
},
closeApp: async (app) => {
desktopCalls.push(['close', app]);
},
},
accessibility: {
captureTree: async (surface) => {
semanticCalls.push(['accessibility', surface]);
return {
nodes: linuxCalculatorSnapshotNodes(),
truncated: false,
surface,
};
},
},
clipboard: {
readText: async () => {
semanticCalls.push(['clipboard', 'read']);
return clipboardText;
},
writeText: async (text) => {
semanticCalls.push(['clipboard', 'write', text]);
clipboardText = text;
},
},
screenshot: {
capture: async (outPath, options) => {
semanticCalls.push([
'screenshot',
outPath,
String(options?.fullscreen ?? ''),
String(options?.stabilize ?? ''),
String(options?.surface ?? ''),
]);
fs.writeFileSync(outPath, validPng());
},
},
input: {
click: async (x, y, button) => {
semanticCalls.push(['input', 'click', String(x), String(y), button]);
},
doubleClick: async (x, y) => {
semanticCalls.push(['input', 'double-click', String(x), String(y)]);
},
longPress: async (x, y, durationMs) => {
semanticCalls.push(['input', 'long-press', String(x), String(y), String(durationMs)]);
},
drag: async (x1, y1, x2, y2, durationMs) => {
semanticCalls.push([
'input',
'drag',
String(x1),
String(y1),
String(x2),
String(y2),
String(durationMs),
]);
},
scroll: async (direction, options) => {
semanticCalls.push([
'input',
'scroll',
direction,
String(options?.amount ?? ''),
String(options?.pixels ?? ''),
]);
},
typeText: async (text, options) => {
semanticCalls.push(['input', 'type', text, String(options?.delayMs ?? 0)]);
},
key: async (combo) => {
semanticCalls.push(['input', 'key', combo]);
},
},
});
const daemon = await createProviderScenarioHarness({
linuxToolProvider: () => linuxToolProvider,
deviceInventoryProvider: async () => [PROVIDER_SCENARIO_LINUX],
});
let closed = false;
return {
daemon,
localLinuxDevices,
toolCalls,
desktopCalls,
semanticCalls,
close: async () => {
if (closed) return;
closed = true;
await daemon.close();
Object.defineProperty(process, 'platform', { value: previousPlatform, configurable: true });
restoreEnv('XDG_SESSION_TYPE', previousSessionType);
restoreEnv('WAYLAND_DISPLAY', previousWaylandDisplay);
restoreEnv('AGENT_DEVICE_HTTP_AUTH_HOOK', previousAuthHook);
},
};
}
function linuxCalculatorSnapshotNodes(): Array<{
index: number;
role: string;
label: string;
rect: { x: number; y: number; width: number; height: number };
enabled: boolean;
hittable: boolean;
depth: number;
parentIndex?: number;
}> {
return [
{
index: 0,
role: 'frame',
label: 'Calculator',
rect: { x: 0, y: 0, width: 320, height: 480 },
enabled: true,
hittable: true,
depth: 0,
},
{
index: 1,
role: 'push button',
label: '5',
rect: { x: 40, y: 80, width: 40, height: 40 },
enabled: true,
hittable: true,
depth: 1,
parentIndex: 0,
},
{
index: 2,
role: 'push button',
label: 'Clear',
rect: { x: 90, y: 80, width: 70, height: 40 },
enabled: true,
hittable: true,
depth: 1,
parentIndex: 0,
},
];
}