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 { 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, status = 'SUCCEEDED'): Promise { 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 { 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); }