Files
Jokimbe-ComfyUI-DrawThings-…/e2e/nodeRef.ts
T
2025-09-19 22:34:09 -07:00

311 lines
11 KiB
TypeScript

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<string[]> {
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 } });
await this.page.waitForTimeout(this.delay)
}
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, nodeName: 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.keyboard.type(nodeName)
await this.page
.getByLabel("Option List")
.getByText(nodeName)
.click();
}
}
async function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}