Skip to content

Commit 899f11d

Browse files
gkorlandCopilot
andcommitted
Tighten Playwright graph interaction checks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 064bc33 commit 899f11d

1 file changed

Lines changed: 62 additions & 19 deletions

File tree

e2e/logic/POM/codeGraph.ts

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export default class CodeGraph extends BasePage {
2424
return (selector: string) => this.container.locator(selector);
2525
}
2626

27+
private get activeGraphGetterName(): "graphDesktop" | "graphMobile" {
28+
return this.isMobile ? "graphMobile" : "graphDesktop";
29+
}
30+
2731
/* NavBar Locators*/
2832
private get falkorDBLogo(): Locator {
2933
return this.scopedLocator("//*[img[@alt='FalkorDB']]")
@@ -417,19 +421,31 @@ export default class CodeGraph extends BasePage {
417421

418422
/* CodeGraph functionality */
419423
async selectGraph(graph: string | number): Promise<void> {
424+
const previousGraphSnapshot = await this.getActiveGraphSnapshot();
420425
await interactWhenVisible(this.comboBoxbtn, (el) => el.click(), 'ComboBox button');
421426
if (typeof graph === 'number') {
422427
await interactWhenVisible(this.selectGraphInComboBoxById(graph.toString()), (el) => el.click(), `Graph option ${graph}`);
423428
} else {
424429
await interactWhenVisible(this.selectGraphInComboBoxByName(graph), (el) => el.click(), `Graph option ${graph}`);
425430
}
426-
const graphGetter = this.isMobile ? "graphMobile" : "graphDesktop";
427-
await this.page.waitForFunction((getterName) => {
431+
await this.page.waitForFunction(({ getterName, previousGraphId, previousNodeIds }) => {
428432
const getter = (window as any)[getterName];
429433
const graphData = typeof getter === "function" ? getter() : null;
430434
const nodes = graphData?.elements?.nodes || graphData?.nodes;
431-
return Array.isArray(nodes) && nodes.length > 0;
432-
}, graphGetter, { timeout: 10000 });
435+
if (!Array.isArray(nodes) || nodes.length === 0) return false;
436+
437+
const currentGraphId = (window as any).graph?.Id != null ? String((window as any).graph.Id) : null;
438+
if (currentGraphId !== previousGraphId) return true;
439+
440+
const currentNodeIds = nodes.map((node: { id: string | number }) => String(node.id)).sort();
441+
if (currentNodeIds.length !== previousNodeIds.length) return true;
442+
443+
return currentNodeIds.some((id: string, index: number) => id !== previousNodeIds[index]);
444+
}, {
445+
getterName: this.activeGraphGetterName,
446+
previousGraphId: previousGraphSnapshot.graphId,
447+
previousNodeIds: previousGraphSnapshot.nodeIds,
448+
}, { timeout: 10000 });
433449
await this.waitForCanvasViewportToSettle();
434450
}
435451

@@ -507,19 +523,26 @@ export default class CodeGraph extends BasePage {
507523
await this.waitForCanvasAnimationToEnd();
508524
const boundingBox = await this.canvasElement.boundingBox();
509525
if (!boundingBox) throw new Error("Canvas bounding box not found");
510-
const targetX = Math.min(Math.max(x, boundingBox.x + 1), boundingBox.x + boundingBox.width - 1);
511-
const targetY = Math.min(Math.max(y, boundingBox.y + 1), boundingBox.y + boundingBox.height - 1);
526+
const maxX = boundingBox.x + boundingBox.width;
527+
const maxY = boundingBox.y + boundingBox.height;
528+
if (x < boundingBox.x || x > maxX || y < boundingBox.y || y > maxY) {
529+
throw new Error(
530+
`Node click coordinates (${x}, ${y}) are outside canvas bounds ` +
531+
`[${boundingBox.x}, ${maxX}] x [${boundingBox.y}, ${maxY}]`
532+
);
533+
}
534+
512535
for (let attempt = 1; attempt <= 3; attempt++) {
513-
await this.page.mouse.move(targetX, targetY);
536+
await this.page.mouse.move(x, y);
514537
await this.page.waitForTimeout(500);
515-
await this.page.mouse.click(targetX, targetY, { button: 'right' });
516-
if (await this.elementMenu.isVisible()) {
538+
await this.page.mouse.click(x, y, { button: 'right' });
539+
if (await waitForElementToBeVisible(this.elementMenuButton("View Node"), 100, 5)) {
517540
return;
518541
}
519542
await this.page.waitForTimeout(1000);
520543
}
521544

522-
throw new Error(`Failed to click, elementMenu not visible after multiple attempts.`);
545+
throw new Error(`Failed to open node context menu at (${x}, ${y}); "View Node" did not appear after multiple attempts.`);
523546
}
524547

525548

@@ -624,18 +647,18 @@ export default class CodeGraph extends BasePage {
624647

625648
private async waitForGraphData(): Promise<any> {
626649
await this.waitForCanvasAnimationToEnd();
627-
// Wait for the graph data to be available
628-
await this.page.waitForFunction(() => {
629-
const data = (window as any).graphDesktop?.();
630-
return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) ||
631-
(data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0));
632-
}, { timeout: 5000 });
633-
634-
// Safety guard: wait for engine to fully stop and data to settle
650+
await this.page.waitForFunction((getterName) => {
651+
const getter = (window as any)[getterName];
652+
const data = typeof getter === "function" ? getter() : null;
653+
const nodes = data?.elements?.nodes || data?.nodes;
654+
return Array.isArray(nodes) && nodes.length > 0;
655+
}, this.activeGraphGetterName, { timeout: 5000 });
635656
await this.page.waitForTimeout(3000);
636657
await this.waitForCanvasAnimationToEnd();
637658

638-
return await this.page.evaluate(() => (window as any).graphDesktop());
659+
const graphData = await this.getActiveGraphData();
660+
if (!graphData) throw new Error(`Graph data not available from ${this.activeGraphGetterName}`);
661+
return graphData;
639662
}
640663

641664
async getGraphNodes(): Promise<any[]> {
@@ -814,4 +837,24 @@ export default class CodeGraph extends BasePage {
814837

815838
throw new Error("Canvas viewport did not settle after graph selection");
816839
}
840+
841+
private async getActiveGraphData(): Promise<any | null> {
842+
return await this.page.evaluate((getterName) => {
843+
const getter = (window as any)[getterName];
844+
return typeof getter === "function" ? getter() : null;
845+
}, this.activeGraphGetterName);
846+
}
847+
848+
private async getActiveGraphSnapshot(): Promise<{ graphId: string | null; nodeIds: string[] }> {
849+
return await this.page.evaluate((getterName) => {
850+
const getter = (window as any)[getterName];
851+
const graphData = typeof getter === "function" ? getter() : null;
852+
const nodes = graphData?.elements?.nodes || graphData?.nodes || [];
853+
854+
return {
855+
graphId: (window as any).graph?.Id != null ? String((window as any).graph.Id) : null,
856+
nodeIds: Array.isArray(nodes) ? nodes.map((node: { id: string | number }) => String(node.id)).sort() : [],
857+
};
858+
}, this.activeGraphGetterName);
859+
}
817860
}

0 commit comments

Comments
 (0)