69 lines
3.7 KiB
JavaScript
69 lines
3.7 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { DesktopObserver } from '../computer-use/observer.js';
|
|
|
|
test('Phase 6 observer returns stable refs and ranked semantic matches', async () => {
|
|
const observer = new DesktopObserver({
|
|
screenshot: { capture: async () => '/tmp/frame.png' },
|
|
normalizer: { normalize: async () => ({ path: '/tmp/frame.webp' }) },
|
|
shell: { windows: async () => ({ available: true, windows: [{ id: 1 }] }), focused: async () => ({ id: 1 }) },
|
|
atspi: { tree: async () => [{ role: 'switch', name: 'Night Light', rect: [10, 20, 30, 30] }, { role: 'button', name: 'Cancel', rect: [1, 2, 3, 4] }] },
|
|
});
|
|
const bundle = await observer.observe({ includeOcr: false });
|
|
assert.equal(bundle.screenshot_path, '/tmp/frame.webp'); assert.equal(bundle.tree[0].ref, 'r1');
|
|
assert.equal(bundle.frame_source, 'screenshot');
|
|
assert.equal((await observer.find({ query: 'night light' }))[0].ref, 'r1');
|
|
const zoom = await observer.zoom({ ref: 'r1' }); assert.deepEqual(zoom.rect, [10, 20, 30, 30]);
|
|
});
|
|
|
|
test('observer returns when the Shell helper never connects', async () => {
|
|
const observer = new DesktopObserver({
|
|
screenshot: { capture: async () => '/tmp/frame.png' },
|
|
normalizer: { normalize: async () => ({ path: '/tmp/frame.webp' }) },
|
|
shell: { connect: () => new Promise(() => {}), windows: async () => assert.fail('shell windows'), focused: async () => assert.fail('shell focused') },
|
|
atspi: { tree: async () => [{ role: 'label', name: 'Discord', rect: [0, 0, 10, 10] }] },
|
|
timeouts: { shell: 40 },
|
|
});
|
|
const started = Date.now();
|
|
const bundle = await observer.observe({ includeOcr: false });
|
|
assert.ok(Date.now() - started < 1000);
|
|
assert.equal(bundle.screenshot_path, '/tmp/frame.webp');
|
|
assert.equal(bundle.tree[0].name, 'Discord');
|
|
assert.match(bundle.unavailable.join(' '), /Shell helper/);
|
|
});
|
|
|
|
test('observer reads the PipeWire frame buffer instead of the Screenshot portal', async () => {
|
|
let shots = 0;
|
|
const observer = new DesktopObserver({
|
|
screenshot: { capture: async () => { shots += 1; return '/tmp/portal.png'; } },
|
|
framebuffer: { capture: async () => '/tmp/pw.png' },
|
|
normalizer: { normalize: async (input) => ({ path: `${input}.webp` }) },
|
|
shell: { windows: async () => ({ available: true, windows: [] }), focused: async () => null },
|
|
atspi: { tree: async () => [] },
|
|
});
|
|
const bundle = await observer.observe({ includeTree: false, includeOcr: false });
|
|
assert.equal(shots, 0);
|
|
assert.equal(bundle.frame_source, 'pipewire');
|
|
assert.equal(bundle.screenshot_path, '/tmp/pw.png.webp');
|
|
});
|
|
|
|
test('observer returns after a hung screenshot instead of staying on THINKING', async () => {
|
|
const observer = new DesktopObserver({
|
|
screenshot: { capture: () => new Promise(() => {}) },
|
|
shell: { windows: async () => ({ available: true, windows: [] }), focused: async () => null },
|
|
atspi: { tree: async () => [] },
|
|
timeouts: { screenshot: 40 },
|
|
});
|
|
const started = Date.now();
|
|
const bundle = await observer.observe({ includeTree: false, includeOcr: false });
|
|
assert.ok(Date.now() - started < 1000);
|
|
assert.equal(bundle.screenshot_path, null);
|
|
assert.match(bundle.unavailable.join(' '), /frame/);
|
|
});
|
|
|
|
test('Phase 6 observer reports portal failure instead of inventing a frame', async () => {
|
|
const observer = new DesktopObserver({ screenshot: { capture: async () => { throw new Error('no session bus'); } }, shell: { windows: async () => ({ available: false, windows: [] }), focused: async () => null }, atspi: { tree: async () => [] } });
|
|
const bundle = await observer.observe({ includeTree: false, includeOcr: false });
|
|
assert.equal(bundle.screenshot_path, null); assert.match(bundle.unavailable[0], /frame/);
|
|
});
|