import { Page } from "@playwright/test"; import { centerOnPoint } from './util'; export async function getNodeRef( page: Page, node: number | string | ((node) => boolean), options?: { doNotThrow?: boolean; } ) { const nodeId = await page.evaluate((node) => { if (typeof node === "number") { return window.app.graph._nodes[node]?.id; } else if (typeof node === "string") { return window.app.graph._nodes.find((n) => n.type === node)?.id; } else if (typeof node === "function") { return window.app.graph._nodes.find(node)?.id; } return null; }, node); if (nodeId === null || nodeId === undefined) { if (options?.doNotThrow) return undefined; throw new Error(`Node not found: ${node}`); } return new NodeRef(nodeId, page); } export async function addNode(page: Page, path: string[], x: number, y: number) { await centerOnPoint(page, x, y); await page.waitForTimeout(200) const canvasSize = await page.locator("#graph-canvas").boundingBox() await page.locator("#graph-canvas").click({ position: { x: canvasSize!.width / 2, y: canvasSize!.height / 2, }, button: "right", }) await page.getByRole("menuitem", { name: "Add node" }).first().click(); for (const p of path) { const menu = await page.locator(".litecontextmenu").last() await menu.getByText(p, { exact: true }).click(); } } export class NodeRef { readonly id: number | string; readonly page: Page; tag?: string delay: number = 100; constructor(id: number | string, page: Page, tag?: string) { this.page = page; this.id = id; this.tag = tag } async clickWidget(name: string) { const { view, pos, widgetPos, size } = await this.page.evaluate( ([nodeId, name]) => { const view: [number, number, number, number] = window.app.canvas.visible_area; const node = app.graph.getNodeById(nodeId); const pos: [number, number, number, number] = node._posSize; const widget = node.widgets.find((w) => w.name === name); if (!widget) { throw new Error(`Widget not found: ${name}`); } const widgetPos: number = widget.y; const canvas = window.app.canvas.canvas.getBoundingClientRect(); const size = { width: canvas.width, height: canvas.height, }; return { view, pos, widgetPos, size }; }, [this.id, name] ); const getPos = ([x, y]: [number, number]) => { // view: [viewX, viewY, viewWidth, viewHeight] // size: { width, height } const [viewX, viewY, viewWidth, viewHeight] = view; const { width: canvasWidth, height: canvasHeight } = size; const canvasX = ((x - viewX) / viewWidth) * canvasWidth; const canvasY = ((y - viewY) / viewHeight) * canvasHeight; return [canvasX, canvasY]; }; // try and click the widget const widgetX = pos[0] + pos[2] / 2; const widgetY = pos[1] + widgetPos + 10; const [clickX, clickY] = getPos([widgetX, widgetY]); await this.page .locator("#graph-canvas") .click({ position: { x: clickX, y: clickY } }); } async isWidgetVisible(name: string); async isWidgetVisible(names: string[]); async isWidgetVisible(arg: string | string[]) { const names = Array.isArray(arg) ? arg : [arg]; const widgets = await this.page.evaluate( async ([nodeId, names, delay]) => { await new Promise((resolve) => setTimeout(resolve, delay)); const node = app.graph.getNodeById(nodeId); const isVisible: boolean[] = []; for (const name of names) { const w = node.widgets.find((w) => w.name === name); if (!w || w.hidden) { isVisible.push(false); continue; } isVisible.push(true); } return isVisible; }, [this.id, names, this.delay] ); if (Array.isArray(arg)) { return widgets; } else { return widgets[0]; } return widgets.every((v) => v === true); } async getVisibleWidgets(): Promise { const widgets = await this.page.evaluate( async ([nodeId, delay]) => { await new Promise((resolve) => setTimeout(resolve, delay)); const node = app.graph.getNodeById(nodeId); const widgetNames = node.widgets.filter(w => !w.hidden).map(w => w.name) return widgetNames }, [this.id, this.delay] ); return widgets } async isWidgetDisabled(name: string); async isWidgetDisabled(names: string[]); async isWidgetDisabled(arg: string | string[]) { const names = Array.isArray(arg) ? arg : [arg]; const widgets = await this.page.evaluate( async ([nodeId, names, delay]) => { await new Promise((resolve) => setTimeout(resolve, delay)); const node = app.graph.getNodeById(nodeId); const isDisabled: boolean[] = []; for (const name of names) { const w = node.widgets.find((w) => w.name === name); if (!w || w.disabled !== true) { isDisabled.push(false); continue; } isDisabled.push(true); } return isDisabled; }, [this.id, names, this.delay] ); if (Array.isArray(arg)) { return widgets; } else { return widgets[0]; } } async getWidgetValue(name: string) { return await this.page.evaluate( ([nodeId, name]) => { const node = app.graph.getNodeById(nodeId); const widget = node.widgets.find((w) => w.name === name); if (!widget) throw new Error(`Widget not found: ${name}`); return widget.value; }, [this.id, name] ); } async selectWidgetOption(widget: string, option: string | RegExp) { await this.clickWidget(widget); const item = await this.page.getByRole("menuitem", { name: option }).first() await item.scrollIntoViewIfNeeded() await item.click({position: { x: 2, y: 2 } }); } async setWidgetValue(widget: string, value: number) { await this.clickWidget(widget); await this.page.locator('*:focus').fill(String(value)) await this.page.locator('*:focus').press('Enter') // await this.page.locator('.graphdialog>textbox').fill(String(value)) // await this.page.locator('.graphdialog>textbox').press('Enter') } async getNodeColor() { throw new Error("not yet implemented") } async connectOutput(output: string, node: NodeRef, input: string) { throw new Error("not yet implemented") } async disconnectInput(input: string) { throw new Error("not yet implemented") } async getWidgetOptions(widget: string): Promise<{content: string, value: unknown, disabled?: boolean}[]> { return await this.page.evaluate( ([nodeId, name]) => { const node = app.graph.getNodeById(nodeId); const widget = node.widgets.find((w) => w.name === name); if (!widget) throw new Error(`Widget not found: ${name}`); return widget.options.values; }, [this.id, widget] ); } async centerNode() { await this.page.evaluate( async ([nodeId]) => { const node = app.graph.getNodeById(nodeId); app.canvas.selectNode(node) await new Promise((resolve) => setTimeout(resolve, 200)); }, [this.id]) await this.page.locator("#graph-canvas").press('.') await wait(400) } async addOutputNode(outputName: string, nodePath: string[]) { const { view, pos, outputBox, size } = await this.page.evaluate( ([nodeId, outputName]) => { const view: [number, number, number, number] = window.app.canvas.visible_area; const node = app.graph.getNodeById(nodeId); const pos: [number, number, number, number] = node._posSize; const output = node.outputs.find((o) => o.name === outputName); if (!output) { throw new Error(`Output not found: ${outputName}`); } const outputBox: number = output.boundingRect; const canvas = window.app.canvas.canvas.getBoundingClientRect(); const size = { width: canvas.width, height: canvas.height, }; return { view, pos, outputBox, size }; }, [this.id, outputName] ); const getPos = ([x, y]: [number, number]) => { // view: [viewX, viewY, viewWidth, viewHeight] // size: { width, height } const [viewX, viewY, viewWidth, viewHeight] = view; const { width: canvasWidth, height: canvasHeight } = size; const canvasX = ((x - viewX) / viewWidth) * canvasWidth; const canvasY = ((y - viewY) / viewHeight) * canvasHeight; return [canvasX, canvasY]; }; // try and click the output const outputX = outputBox[0] + outputBox[2] / 2 const outputY = outputBox[1] + outputBox[3] / 2 const [clickX, clickY] = getPos([outputX, outputY]); await this.page .locator("#graph-canvas") .hover({ position: { x: clickX, y: clickY } }); await this.page.mouse.down() await this.page .locator("#graph-canvas") .hover({ position: { x: clickX + 50, y: clickY } }); await this.page.mouse.up() await this.page.getByRole("menuitem", { name: "Add node" }).first().click(); for (const p of nodePath) { const menu = await this.page.locator(".litecontextmenu").last() await menu.getByText(p, { exact: true }).click(); } } } async function wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); }