From 5c4d6d031743f97a7cfbb4f1a9966a62a7cbde7a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 18:06:12 +0200 Subject: [PATCH] turn an assertion as retryable with expect.poll --- playwright/spa-helpers.ts | 26 ++++++++++------ playwright/widget/federated-call.test.ts | 10 +------ .../federation-oldest-membership-bug.spec.ts | 15 +++------- .../widget/hotswap-legacy-compat.test.ts | 30 ++++++------------- playwright/widget/huddle-call.test.ts | 22 ++------------ playwright/widget/test-helpers.ts | 30 +++++++++++++++++-- 6 files changed, 62 insertions(+), 71 deletions(-) diff --git a/playwright/spa-helpers.ts b/playwright/spa-helpers.ts index 4c6d83c1..1756959e 100644 --- a/playwright/spa-helpers.ts +++ b/playwright/spa-helpers.ts @@ -127,15 +127,23 @@ async function expectVideoTilesCount(page: Page, count: number): Promise { // There should be 5 video elements, visible and autoplaying await expect(page.locator("video")).toHaveCount(count); - const blockDisplayCount = await page - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(count); + await expect + .poll( + async () => { + return await page + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => window.getComputedStyle(v).display === "block", + ).length, + ); + }, + { + timeout: 10000, + }, + ) + .toBe(count); } export const SpaHelpers = { diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts index 7685e486..f3d3d965 100644 --- a/playwright/widget/federated-call.test.ts +++ b/playwright/widget/federated-call.test.ts @@ -72,15 +72,7 @@ modePairs.forEach(([rtcMode1, rtcMode2]) => { const videoElements = await frame.locator("video").all(); expect(videoElements.length).toBe(2); - const blockDisplayCount = await frame - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(2); + await TestHelpers.expectVisibleVideoCount(frame, 2); } // await florian.page.pause(); diff --git a/playwright/widget/federation-oldest-membership-bug.spec.ts b/playwright/widget/federation-oldest-membership-bug.spec.ts index 70442e05..ab5c70fc 100644 --- a/playwright/widget/federation-oldest-membership-bug.spec.ts +++ b/playwright/widget/federation-oldest-membership-bug.spec.ts @@ -75,18 +75,11 @@ widgetTest( await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); // There should be 2 video elements, visible and autoplaying - const videoElements = await frame.locator("video").all(); - expect(videoElements.length).toBe(2); + await expect(frame.locator("video")).toHaveCount(2, { + timeout: 10000, + }); - const blockDisplayCount = await frame - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(2); + await TestHelpers.expectVisibleVideoCount(frame, 2); } }, ); diff --git a/playwright/widget/hotswap-legacy-compat.test.ts b/playwright/widget/hotswap-legacy-compat.test.ts index f58a562d..667ae011 100644 --- a/playwright/widget/hotswap-legacy-compat.test.ts +++ b/playwright/widget/hotswap-legacy-compat.test.ts @@ -71,32 +71,20 @@ widgetTest( }); // There should be 2 video elements, visible and autoplaying - await expect(frame.locator("video")).toHaveCount(2); + await expect(frame.locator("video")).toHaveCount(2, { + timeout: 10000, + }); - const blockDisplayCount = await frame - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(2); + await TestHelpers.expectVisibleVideoCount(frame, 2); } // now we switch the mode for timo (second joiner on multi-sfu HOST2 but currently HOST1) await TestHelpers.setEmbeddedElementCallRtcMode(timo.page, "compat"); await timo.page.waitForTimeout(3000); - const blockDisplayCount = await timo.page - .locator('iframe[title="Element Call"]') - .contentFrame() - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(2); + + await TestHelpers.expectVisibleVideoCount( + timo.page.locator('iframe[title="Element Call"]').contentFrame(), + 2, + ); }, ); diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index ec3c816a..029849be 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -93,15 +93,7 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { await expect(frame.locator("video")).toHaveCount(5); await expect(frame.locator("video[autoplay]")).toHaveCount(5); - const blockDisplayCount = await frame - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - expect(blockDisplayCount).toBe(5); + await TestHelpers.expectVisibleVideoCount(frame, 5); }), ); @@ -128,18 +120,10 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { timeout: 10000, }); - const blockDisplayCount = await frame - .locator("video") - .evaluateAll( - (videos: Element[]) => - videos.filter( - (v: Element) => window.getComputedStyle(v).display === "block", - ).length, - ); - // out of 5 ONLY 4 are visible (display:block) !! // XXX we need to be better at our HTML markup and accessibility, it would make // this kind of stuff way easier to test if we could look out for aria attributes. - expect(blockDisplayCount).toBe(4); + // ✅ Retryable assertion for visible videos + await TestHelpers.expectVisibleVideoCount(frame, 4); } }); diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 8463e421..598bdc65 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -10,6 +10,7 @@ import { expect, type JSHandle, type Page, + type FrameLocator, } from "@playwright/test"; import { type MatrixClient } from "matrix-js-sdk"; @@ -110,8 +111,8 @@ export class TestHelpers { await expect( page.getByRole("heading", { name: `Welcome ${username}` }), ).toBeVisible({ - // Increase timeout here - timeout: 10000, + // Increase timeout here :/ flaky + timeout: 15000, }); await this.maybeDismissBrowserNotSupportedToast(page); @@ -322,4 +323,29 @@ export class TestHelpers { ): Promise { await page.getByRole("option", { name: `Open room ${roomName}` }).click(); } + + public static async expectVisibleVideoCount( + frame: FrameLocator, + count: number, + ): Promise { + // ✅ Retryable assertion for visible videos + await expect + .poll( + async () => { + return await frame + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => + window.getComputedStyle(v).display === "block", + ).length, + ); + }, + { + timeout: 10000, + }, + ) + .toBe(count); + } }