-
Notifications
You must be signed in to change notification settings - Fork 28
test: harden false-positive e2e assertions (#1229) #1303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,15 @@ async function getPanzoomTransform(page: Page) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function setPanzoomTransform(page: Page, transform: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.evaluate((nextTransform) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const panzoomContainer = document.querySelector(".panzoom-container"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (panzoomContainer) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (panzoomContainer as HTMLElement).style.transform = nextTransform; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, transform); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test.describe("Rack Context Menu Focus", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test.beforeEach(async ({ page }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await gotoWithRack(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,12 +37,23 @@ test.describe("Rack Context Menu Focus", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Rack should be visible | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page.locator(".rack-container").first()).toBeVisible(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Get the initial transform | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Force a non-focused transform so Focus must change it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await setPanzoomTransform(page, "matrix(0.4, 0, 0, 0.4, 0, 0)"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformBefore = await getPanzoomTransform(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformBefore).toBeTruthy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformBefore?.scale).toBe(0.4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Right-click on the rack-svg (inside the dual view) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.locator(".rack-svg").first().click({ button: "right" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Open the canvas context menu directly on the rack element. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.evaluate(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rack = document.querySelector(".rack-svg"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!rack) throw new Error("Could not find rack svg"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rack.dispatchEvent( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new MouseEvent("contextmenu", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bubbles: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancelable: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Programmatic The event is dispatched without 🛠️ Proposed fix — use Playwright's built-in right-click- // Open the canvas context menu directly on the rack element.
- await page.evaluate(() => {
- const rack = document.querySelector(".rack-svg");
- if (!rack) throw new Error("Could not find rack svg");
- rack.dispatchEvent(
- new MouseEvent("contextmenu", {
- bubbles: true,
- cancelable: true,
- }),
- );
- });
+ // Open the canvas context menu via a native right-click on the rack.
+ await page.locator(".rack-svg").first().click({ button: "right" });🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wait for context menu to appear | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page.locator(".context-menu-content")).toBeVisible(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -43,22 +63,30 @@ test.describe("Rack Context Menu Focus", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(focusItem).toBeVisible(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await focusItem.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Focus recalculates and applies optimal zoom/pan for the rack. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Even from the initial position, Focus will center the rack. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wait for transform to potentially change (animation may take time) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformAfter = await getPanzoomTransform(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter).toBeTruthy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter?.scale).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter?.x).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter?.y).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).toPass({ timeout: 1000 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .poll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformAfter = await getPanzoomTransform(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!transformBefore || !transformAfter) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transformAfter.scale !== transformBefore.scale || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transformAfter.x !== transformBefore.x || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transformAfter.y !== transformBefore.y | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Duplicate polling logic — extract into a shared helper. The ♻️ Proposed refactor+async function assertTransformChanged(
+ page: Page,
+ transformBefore: { scale: number; x: number; y: number },
+) {
+ await expect
+ .poll(
+ async () => {
+ const transformAfter = await getPanzoomTransform(page);
+ if (!transformAfter) return false;
+ return (
+ transformAfter.scale !== transformBefore.scale ||
+ transformAfter.x !== transformBefore.x ||
+ transformAfter.y !== transformBefore.y
+ );
+ },
+ { timeout: 5000 },
+ )
+ .toBe(true);
+}Then replace both polling blocks with: - await expect
- .poll(async () => {
- const transformAfter = await getPanzoomTransform(page);
- if (!transformBefore || !transformAfter) return false;
- return (
- transformAfter.scale !== transformBefore.scale ||
- transformAfter.x !== transformBefore.x ||
- transformAfter.y !== transformBefore.y
- );
- })
- .toBe(true);
+ await assertTransformChanged(page, transformBefore!);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| test("Focus option in Racks panel context menu works", async ({ page }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Rack should be visible | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(page.locator(".rack-container").first()).toBeVisible(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Force a non-focused transform so Focus must change it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await setPanzoomTransform(page, "matrix(0.4, 0, 0, 0.4, 0, 0)"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformBefore = await getPanzoomTransform(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformBefore).toBeTruthy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformBefore?.scale).toBe(0.4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Switch to the Racks tab in the sidebar | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This test requires the sidebar to be visible (desktop viewport) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const racksTab = page.locator('button[role="tab"]:has-text("Racks")'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,13 +109,17 @@ test.describe("Rack Context Menu Focus", () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Click Focus - this triggers the focusRack function via callback chain | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await focusItem.click(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Verify the transform exists (Focus was applied) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformAfter = await getPanzoomTransform(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter).toBeTruthy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter?.scale).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter?.x).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(transformAfter?.y).toBeDefined(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).toPass({ timeout: 1000 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await expect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .poll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformAfter = await getPanzoomTransform(page); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!transformBefore || !transformAfter) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transformAfter.scale !== transformBefore.scale || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transformAfter.x !== transformBefore.x || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transformAfter.y !== transformBefore.y | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toBe(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 290
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 99
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 91
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 5310
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 6337
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 3767
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 4256
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 2554
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 46
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 2161
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 4070
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 1851
🏁 Script executed:
Repository: RackulaLives/Rackula
Length of output: 154
Move
transformBeforecapture immediately before the Focus click to avoid false positives from panzoom's RAF loop.setPanzoomTransformwrites only to the CSSstyle.transform; it does not update the panzoom library's internal state. The multiple async operations between capturingtransformBefore(line 42) and clicking Focus (line 64)—dispatching the contextmenu event, waiting for.context-menu-content, finding and assertingfocusItem—create await points where panzoom's RAF loop continuously synchronizes the CSS to panzoom's actual internal state. This can overwrite the manually set CSS before Focus fires.If Focus is broken and doesn't update panzoom's internal state, the test will still pass because
transformBefore(captured earlier) now holds a stale value that differs from the current CSS, making the poll succeed despite the failure.Fix: Move `transformBefore` capture to immediately before the click action
In Test 1 (lines 40–64), move the capture block:
Apply the same pattern to Test 2 (lines 84–110): move the capture block to immediately before line 110's
focusItem.click().🤖 Prompt for AI Agents