Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
42 lines
3.1 KiB
TypeScript
42 lines
3.1 KiB
TypeScript
import { expect, type Page } from '@playwright/test';
|
|
import type { NativeMainReceipt } from '../../protocol/native-workflow';
|
|
export function finiteLocation(value: unknown): asserts value is [number, number, number] {
|
|
expect(Array.isArray(value)).toBe(true); expect(value).toHaveLength(3);
|
|
for (const coordinate of value as unknown[]) { expect(typeof coordinate).toBe('number'); expect(Number.isFinite(coordinate)).toBe(true); }
|
|
}
|
|
export async function latestMainReceipt(page: Page): Promise<NativeMainReceipt | null> {
|
|
const output = await page.locator('.native-workflow-output').textContent(); expect(typeof output).toBe('string');
|
|
const line = output!.split('\n').filter(value => value.startsWith('[MainReceipt] ')).at(-1);
|
|
if (!line) return null;
|
|
const value = JSON.parse(line.slice('[MainReceipt] '.length)) as NativeMainReceipt;
|
|
expect(value !== null && typeof value === 'object' && !Array.isArray(value)).toBe(true);
|
|
expect(typeof value.command).toBe('string'); expect(Number.isSafeInteger(value.sequence)).toBe(true); expect(value.sequence).toBeGreaterThanOrEqual(0);
|
|
expect(value.activeObject === null || value.activeObject === undefined || typeof value.activeObject === 'string').toBe(true);
|
|
expect(Array.isArray(value.objects)).toBe(true);
|
|
for (const object of value.objects) { expect(object !== null && typeof object === 'object').toBe(true); expect(typeof object.name).toBe('string'); finiteLocation(object.location); }
|
|
return value;
|
|
}
|
|
export async function receiptAfter(page: Page, action: () => Promise<unknown>, status = 'SUCCEEDED'): Promise<NativeMainReceipt> {
|
|
const previous = (await latestMainReceipt(page))?.sequence ?? -1;
|
|
await action();
|
|
await expect.poll(async () => (await latestMainReceipt(page))?.sequence ?? -1, { timeout: 30000 }).toBeGreaterThan(previous);
|
|
await expect(page.locator('.native-workflow-controls output')).toHaveText('READY');
|
|
await expect(page.locator('.native-session-panel')).toHaveAttribute('data-command-status', status);
|
|
return (await latestMainReceipt(page))!;
|
|
}
|
|
type WebMain = { requestId: string; revision: number; nodes: { name: string; type: string; transform: { translation: [number, number, number] } }[] };
|
|
export async function webMain(page: Page): Promise<WebMain> {
|
|
const value = await page.evaluate(() => (window as any).__nativeWorkflowMain) as WebMain;
|
|
expect(value !== null && typeof value === 'object').toBe(true); expect(typeof value.requestId).toBe('string');
|
|
expect(Number.isSafeInteger(value.revision)).toBe(true); expect(Array.isArray(value.nodes)).toBe(true);
|
|
for (const node of value.nodes) { expect(typeof node.name).toBe('string'); expect(typeof node.type).toBe('string'); finiteLocation(node.transform?.translation); }
|
|
return value;
|
|
}
|
|
export async function expectWebLocation(page: Page, name: string, expected: [number, number, number]) {
|
|
finiteLocation(expected);
|
|
for (let axis = 0; axis < 3; axis++) await expect.poll(async () => {
|
|
const node = (await webMain(page)).nodes.find(item => item.name === name); expect(node).toBeDefined();
|
|
return node!.transform.translation[axis];
|
|
}, { timeout: 30000 }).toBeCloseTo(expected[axis], 5);
|
|
}
|