// What the editor's browser tests share: the built page served once per file, // and reading the surface (its transform, a device's place on the screen, the // document's drawing through the development page's filiusDev). import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { Page } from '@playwright/test'; import { build, preview, type PreviewServer } from 'vite'; import type { DesktopView, DeviceSheet, TopologyView } from '@mdg-filius/doc'; const config = join(dirname(fileURLToPath(import.meta.url)), '../vite.config.ts'); /** The editor built into `outDir`, as the image's build builds it. */ export async function buildEditor(outDir: string): Promise { await build({ configFile: config, logLevel: 'silent', build: { outDir, emptyOutDir: true } }); } export async function serveEditor(): Promise<{ readonly base: string; readonly close: () => Promise }> { await build({ configFile: config, logLevel: 'silent' }); const server: PreviewServer = await preview({ configFile: config, preview: { port: 0 }, logLevel: 'silent' }); return { base: server.resolvedUrls!.local[0]!, close: () => new Promise((resolve) => server.httpServer.close(() => resolve())), }; } export async function openEditor(page: Page, base: string, example: string, query = ''): Promise { await page.goto(`${base}?example=${example}${query}`); await page.waitForSelector('svg.filius-canvas'); await page.waitForFunction(() => (window as unknown as { filiusDev?: unknown }).filiusDev !== undefined); } /** The document's drawing. */ export function drawing(page: Page): Promise { return page.evaluate(() => (window as unknown as { filiusDev: { view: () => TopologyView } }).filiusDev.view()); } /** How many changes the document has had since the page opened it. */ export function documentUpdates(page: Page): Promise { return page.evaluate(() => (window as unknown as { filiusDev: { updates: () => number } }).filiusDev.updates()); } /** What the device sheet reads of a device. */ export function sheetOf(page: Page, id: string): Promise { return page.evaluate((each) => (window as unknown as { filiusDev: { sheet: (id: string) => DeviceSheet | undefined } }).filiusDev.sheet(each), id); } /** What a computer's desktop reads of the document. */ export function desktopOf(page: Page, id: string): Promise { return page.evaluate((each) => (window as unknown as { filiusDev: { desktop: (id: string) => DesktopView | undefined } }).filiusDev.desktop(each), id); } /** A device's button as data-box says it (surface pixels). */ export async function boxOf(page: Page, id: string): Promise<{ x: number; y: number; width: number; height: number }> { const [x, y, width, height] = (await page.locator(`g.device[data-node="${id}"]`).getAttribute('data-box'))!.split(' ').map(Number) as [number, number, number, number]; return { x, y, width, height }; } /** Where a device's icon's middle is on the screen. */ export async function iconCentre(page: Page, id: string): Promise<{ x: number; y: number }> { const box = (await page.locator(`g.device[data-node="${id}"] image`).boundingBox())!; return { x: box.x + box.width / 2, y: box.y + box.height / 2 }; } /** A point of a device's button on the screen, by its share of the button's width and height. */ export async function onDevice(page: Page, id: string, across = 0.5, down = 0.4): Promise<{ x: number; y: number }> { const box = (await page.locator(`g.device[data-node="${id}"] > rect`).first().boundingBox())!; return { x: box.x + box.width * across, y: box.y + box.height * down }; } /** * In the simulation: a tap chooses the device, its three-dot button opens its * menu, the item opens its sheet — a desktop, a table, the data exchange. A * sheet already open covers the surface; it is closed first. With `touch`, * by finger. */ export async function showFromMenu(page: Page, id: string, item: string | RegExp, touch = false): Promise { const open = page.locator('.simulation-sheet__close'); if ((await open.count()) > 0) await (touch ? open.tap() : open.click()); const at = await onDevice(page, id); // In the simulation a tap opens the device's menu. await (touch ? page.touchscreen.tap(at.x, at.y) : page.mouse.click(at.x, at.y)); const entry = page.getByRole('menuitem', { name: item, exact: true }); await (touch ? entry.tap() : entry.click()); } /** A computer's desktop as the simulation's sheet (showFromMenu), in either language; its section. */ export async function showDesktop(page: Page, id: string, touch = false) { await showFromMenu(page, id, /^(Desktop anzeigen|Show desktop)$/, touch); return page.locator(`section[data-desktop="${id}"]`); } /** The surface's scale. */ export async function scaleOf(page: Page): Promise { return Number(await page.locator('[data-zoom]').getAttribute('data-zoom')); } /** Drags with the mouse from one point to another in steps. */ export async function drag(page: Page, from: { x: number; y: number }, to: { x: number; y: number }): Promise { await page.mouse.move(from.x, from.y); await page.mouse.down(); await page.mouse.move(to.x, to.y, { steps: 8 }); await page.mouse.up(); }