test: Stabilize canvas zoom assertion after route changes (#36694)

Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
n8n-cat-bot[bot]
2026-08-20 13:48:55 +00:00
committed by GitHub
parent dfae315eca
commit 9a899b67df
2 changed files with 42 additions and 26 deletions
@@ -79,40 +79,24 @@ export class CanvasComposer {
async zoomInAndCheckNodes(): Promise<void> {
await this.n8n.canvas.getCanvasNodes().first().waitFor();
const measureFirstNodeWidth = async () =>
await this.n8n.page.evaluate(() => {
const firstNode = document.querySelector('[data-test-id="canvas-node"]');
if (!firstNode) {
throw new Error('Canvas node not found during measurement');
}
return firstNode.getBoundingClientRect().width;
});
// Wait for the animated fit-to-view to settle before capturing the baseline.
let previousWidth = Number.NaN;
await expect
.poll(async () => {
const width = await measureFirstNodeWidth();
const settled = width === previousWidth;
previousWidth = width;
return settled;
})
.toBe(true);
const initialNodeSize = await measureFirstNodeWidth();
// After a route change the editor runs an animated fit-to-view. Wait for the
// viewport transform to stop moving before capturing the baseline zoom, so we
// don't measure against a pre-animation value.
await this.n8n.canvas.waitForCanvasZoomSettled();
const initialZoom = await this.n8n.canvas.getCanvasZoomLevel();
for (let i = 0; i < 4; i++) {
await this.n8n.canvas.clickZoomInButton();
}
// Poll the width until the animated zoom transition settles.
// Poll the zoom scale until the animated zoom-in transition settles.
await expect
.poll(measureFirstNodeWidth, {
.poll(async () => await this.n8n.canvas.getCanvasZoomLevel(), {
message:
"Zoom functionality not working: nodes didn't scale properly. " +
`Initial: ${initialNodeSize.toFixed(1)}px`,
"Zoom functionality not working: canvas didn't scale in. " +
`Initial zoom: ${initialZoom.toFixed(3)}`,
})
.toBeGreaterThan(initialNodeSize * 1.5);
.toBeGreaterThan(initialZoom * 1.5);
}
/**
@@ -929,6 +929,38 @@ export class CanvasPage extends BasePage {
});
}
/**
* Wait for the canvas viewport transform (zoom/pan) to stop changing.
* After a route change the editor runs an animated fit-to-view. Sparse
* expect.poll sampling can catch two equal values in the pre-animation window
* and report "settled" before the animation starts; this checks every frame
* inside the page so it can't miss the in-flight transition.
*/
async waitForCanvasZoomSettled(stableFrames = 5): Promise<void> {
await this.page.waitForFunction(
(needed) => {
const el = document.querySelector('.vue-flow__transformationpane.vue-flow__container');
if (!el) return false;
const transform = getComputedStyle(el).transform;
const w = window as unknown as { n8nZoomSettle?: { last: string; count: number } };
const state = (w.n8nZoomSettle ??= { last: '', count: 0 });
if (transform === state.last) {
state.count += 1;
} else {
state.last = transform;
state.count = 0;
}
if (state.count >= needed) {
delete w.n8nZoomSettle;
return true;
}
return false;
},
stableFrames,
{ polling: 'raf', timeout: 10_000 },
);
}
waitingForTriggerEvent() {
return this.getExecuteWorkflowButton().getByText('Waiting for trigger event');
}