UNPKG

graph-explorer

Version:

Graph Explorer can be used to explore and RDF graphs in SPARQL endpoints or on the web.

180 lines (165 loc) 5.62 kB
import type { Page } from "@playwright/test"; export interface PageProblems { consoleErrors: string[]; pageErrors: string[]; failedImages: string[]; } const isSameOrigin = (url: string) => url.startsWith("http://localhost:") || url.startsWith("/"); /** * Starts collecting console/page errors. Must be called before navigating. * Third-party resources (e.g. the font-awesome CDN used by the demo pages) are * ignored so the assertions stay stable on flaky networks. */ export function collectPageProblems(page: Page): PageProblems { const problems: PageProblems = { consoleErrors: [], pageErrors: [], failedImages: [], }; page.on("console", (message) => { if (message.type() !== "error") { return; } const location = message.location()?.url ?? ""; if (location && !isSameOrigin(location)) { return; } problems.consoleErrors.push(message.text()); }); page.on("pageerror", (error) => problems.pageErrors.push(error.message)); page.on("response", (response) => { const url = response.url(); if (!isSameOrigin(url) || response.status() < 400) { return; } if (/\.(svg|png|jpe?g|gif)(\?|$)/i.test(url)) { problems.failedImages.push(`${response.status()} ${url}`); } }); return problems; } /** Opens the bundled local demo (deterministic, no network data source). */ export async function openLocalDemo(page: Page): Promise<void> { await page.goto("/", { waitUntil: "load" }); await page.waitForSelector(".graph-explorer-class-leaf"); } /** Drags the first available instance onto the canvas to create an element. */ export async function createElementOnCanvas( page: Page, targetPosition = { x: 500, y: 300 } ): Promise<void> { await page.locator(".graph-explorer-class-leaf__body").first().click(); await page.waitForSelector(".graph-explorer-list-element-view"); const created = page.locator(".graph-explorer-overlayed-element").first(); // the browser occasionally drops the simulated HTML5 drag, so retry for (let attempt = 0; attempt < 3; attempt++) { await page .locator(".graph-explorer-list-element-view") .first() .dragTo(page.locator(".graph-explorer-paper-area").first(), { targetPosition, force: true, }); try { await created.waitFor({ state: "visible", timeout: 5_000 }); return; } catch { // fall through and try the drag again } } throw new Error("could not create an element on the canvas after 3 attempts"); } /** * The paper area adjusts and re-centres its scroll position asynchronously * after mounting (and after content changes). Panning before that settles gets * overwritten, so wait until the scroll offsets stop moving. */ export async function waitForCanvasSettled(page: Page): Promise<void> { await page.evaluate(() => { delete (window as unknown as Record<string, unknown>).__geScroll; }); await page.waitForFunction( () => { const area = document.querySelector(".graph-explorer-paper-area__area"); if (!area) { return false; } const store = window as unknown as { __geScroll?: { left: number; top: number; stable: number }; }; const previous = store.__geScroll; const current = { left: area.scrollLeft, top: area.scrollTop, stable: 0, }; if ( previous && previous.left === current.left && previous.top === current.top ) { current.stable = previous.stable + 1; } store.__geScroll = current; // stay stable long enough to outlast the mount/centring animation return current.stable >= 5; }, undefined, { timeout: 15_000, polling: 150 } ); } export interface ElementPlacement { /** position on screen, in viewport pixels */ left: number; top: number; width: number; height: number; /** inline transform, i.e. the element's position in paper coordinates */ transform: string; } /** * Where the first diagram element sits, both on screen and within the diagram. * * Panning is asserted through this rather than through `scrollLeft`/`scrollTop`: * `PaperArea` re-measures the paper on content/viewport changes and rewrites the * scroll offsets (see its `componentDidUpdate`) to keep content visually in * place, so the raw scroll numbers can jump at any time. The on-screen position * is the value that is actually preserved, and the paper-space `transform` * distinguishes "the canvas moved" from "the element was dragged". */ export async function elementPlacement(page: Page): Promise<ElementPlacement> { const element = page.locator(".graph-explorer-overlayed-element").first(); const box = await element.boundingBox(); if (!box) { throw new Error("no diagram element is visible on the canvas"); } const transform = await element.evaluate( (node) => (node as HTMLElement).style.transform ); return { left: box.x, top: box.y, width: box.width, height: box.height, transform, }; } /** Presses the given mouse button and drags in steps so move handlers run. */ export async function dragWithButton( page: Page, button: "left" | "middle", from: { x: number; y: number }, delta: { x: number; y: number } ): Promise<void> { await page.mouse.move(from.x, from.y); await page.mouse.down({ button }); const steps = 5; for (let step = 1; step <= steps; step++) { await page.mouse.move( from.x + (delta.x * step) / steps, from.y + (delta.y * step) / steps ); } await page.mouse.up({ button }); }