From 4211405e7b419cec745076ff137244150b9b6057 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 3 Apr 2026 16:09:30 +0200 Subject: [PATCH 01/28] Use synapse API to register instead doing via UI --- backend/dev_homeserver-othersite.yaml | 3 + backend/dev_homeserver.yaml | 3 + backend/dev_nginx.conf | 21 ++- backend/playwright_homeserver-othersite.yaml | 3 + backend/playwright_homeserver.yaml | 3 + playwright.config.ts | 11 ++ playwright/utils/synapse-admin.ts | 142 +++++++++++++++++++ playwright/widget/test-helpers.ts | 50 ++++--- 8 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 playwright/utils/synapse-admin.ts diff --git a/backend/dev_homeserver-othersite.yaml b/backend/dev_homeserver-othersite.yaml index 81e775ca..7eb8f294 100644 --- a/backend/dev_homeserver-othersite.yaml +++ b/backend/dev_homeserver-othersite.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/backend/dev_homeserver.yaml b/backend/dev_homeserver.yaml index dc7b42c8..0aea2ece 100644 --- a/backend/dev_homeserver.yaml +++ b/backend/dev_homeserver.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index d3ddbc53..fdefa3bd 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -28,11 +28,18 @@ server { # Reason: the lk-jwt-service uses the federation API for the openid token # verification, which requires TLS location ~ ^(/_matrix|/_synapse/client) { - proxy_pass "http://homeserver:8008"; + proxy_pass "http://homeserver:8008"; proxy_http_version 1.1; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $host; + proxy_set_header Host $host; + } + location ~ ^(/_matrix|/_synapse/admin) { + proxy_pass "http://homeserver:8008"; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; } error_page 500 502 503 504 /50x.html; @@ -73,7 +80,15 @@ server { proxy_http_version 1.1; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $host; + proxy_set_header Host $host; + } + + location ~ ^(/_matrix|/_synapse/admin) { + proxy_pass "http://homeserver-1:18008"; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; } error_page 500 502 503 504 /50x.html; diff --git a/backend/playwright_homeserver-othersite.yaml b/backend/playwright_homeserver-othersite.yaml index 35640ae9..86c77b35 100644 --- a/backend/playwright_homeserver-othersite.yaml +++ b/backend/playwright_homeserver-othersite.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/backend/playwright_homeserver.yaml b/backend/playwright_homeserver.yaml index a83247cd..8f437524 100644 --- a/backend/playwright_homeserver.yaml +++ b/backend/playwright_homeserver.yaml @@ -50,6 +50,9 @@ max_event_delay_duration: 24h enable_registration: true enable_registration_without_verification: true +# Shared secret for admin user registration via API (for testing only!) +registration_shared_secret: "test_shared_secret_for_local_dev_only" + report_stats: false serve_server_wellknown: true diff --git a/playwright.config.ts b/playwright.config.ts index 4fb86b95..84afed64 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -7,11 +7,22 @@ Please see LICENSE in the repository root for full details. */ import { defineConfig, devices } from "@playwright/test"; +import { join } from "path"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; const baseURL = process.env.USE_DOCKER ? "http://localhost:8080" : "https://localhost:3000"; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// Needed by the synapse admin API called in fixtures +process.env.NODE_EXTRA_CA_CERTS = join( + __dirname, + "backend/dev_tls_local-ca.crt", +); + /** * See https://playwright.dev/docs/test-configuration. */ diff --git a/playwright/utils/synapse-admin.ts b/playwright/utils/synapse-admin.ts new file mode 100644 index 00000000..b1d0039c --- /dev/null +++ b/playwright/utils/synapse-admin.ts @@ -0,0 +1,142 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { createHmac } from "crypto"; + +/** + * Response from Synapse registration API + */ +export interface SynapseRegistrationResponse { + access_token: string; + user_id: string; + home_server: string; + device_id: string; +} + +/** + * Utility class for interacting with Synapse Admin API + * This provides fast user registration without going through the UI + * + * @see https://matrix-org.github.io/synapse/latest/admin_api/register_api.html + */ +export class SynapseAdmin { + public constructor( + private baseUrl: string = "https://synapse.m.localhost", + private sharedSecret: string = "test_shared_secret_for_local_dev_only", + ) {} + + /** + * Register a user using the Synapse Admin API + * This is much faster than going through the UI registration flow + * + * @param username - The username (localpart) for the new user + * @param password - The password for the new user + * @param displayName - Optional display name (defaults to username) + * @param admin - Whether the user should be an admin (defaults to false) + * @returns Registration response containing access token and user ID + */ + public async registerUser( + username: string, + password: string, + displayName?: string, + admin: boolean = false, + ): Promise { + // Get a nonce first + const nonce = await this.getNonce(); + + // Generate the HMAC + const mac = this.generateMac(username, password, admin, nonce); + + // Make the registration request + const response = await fetch(`${this.baseUrl}/_synapse/admin/v1/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + nonce, + username, + password, + displayname: displayName || username, + admin, + mac, + }), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error( + `Failed to register user ${username}: ${response.status} ${error}`, + ); + } + + return response.json(); + } + + /** + * Get a nonce for registration + * The nonce is required for the HMAC calculation + * + * @returns A nonce string + */ + private async getNonce(): Promise { + const response = await fetch(`${this.baseUrl}/_synapse/admin/v1/register`, { + method: "GET", + }); + + if (!response.ok) { + throw new Error( + `Failed to get nonce: ${response.status} ${await response.text()}`, + ); + } + + const data = await response.json(); + return data.nonce; + } + + /** + * Generate HMAC for shared secret registration + * This is the authentication mechanism for the admin API + * + * @param username - The username + * @param password - The password + * @param admin - Whether the user is an admin + * @param nonce - The nonce from the server + * @returns The HMAC hex string + */ + private generateMac( + username: string, + password: string, + admin: boolean, + nonce: string, + ): string { + const mac = createHmac("sha1", this.sharedSecret); + mac.update(nonce); + mac.update("\x00"); + mac.update(username); + mac.update("\x00"); + mac.update(password); + mac.update("\x00"); + mac.update(admin ? "admin" : "notadmin"); + + return mac.digest("hex"); + } + + /** + * Create a new SynapseAdmin instance for a different homeserver + * + * @param baseUrl - The base URL of the homeserver + * @param sharedSecret - The shared secret (defaults to test secret) + * @returns A new SynapseAdmin instance + */ + public static forHomeserver( + baseUrl: string, + sharedSecret: string = "test_shared_secret_for_local_dev_only", + ): SynapseAdmin { + return new SynapseAdmin(baseUrl, sharedSecret); + } +} diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index ff5fa7e2..d0674f8a 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -13,6 +13,8 @@ import { } from "@playwright/test"; import { type MatrixClient } from "matrix-js-sdk"; +import { SynapseAdmin } from "../utils/synapse-admin.ts"; + const PASSWORD = "foobarbaz1!"; export const HOST1 = "https://app.m.localhost/#/welcome"; @@ -74,29 +76,41 @@ export class TestHelpers { clientHandle: JSHandle; mxId: string; }> { + // Determine which homeserver to use based on the host + const synapseBaseUrl = + host === HOST2 + ? "https://synapse.othersite.m.localhost" + : "https://synapse.m.localhost"; + + // Register user via Synapse Admin API to speed things up + const synapseAdmin = SynapseAdmin.forHomeserver(synapseBaseUrl); + const credentials = await synapseAdmin.registerUser( + username, + PASSWORD, + username, + ); + + // STEP 2: Open browser and login const userContext = await browser.newContext({ reducedMotion: "reduce", }); const page = await userContext.newPage(); - await page.goto(host); - await page.getByRole("link", { name: "Create Account" }).click(); + await page.goto(host + "/#/login"); + + await page.getByRole("link", { name: "Sign in" }).click(); + await page.getByRole("textbox", { name: "Username" }).fill(username); - await page.getByRole("textbox", { name: "Password", exact: true }).click(); - await page - .getByRole("textbox", { name: "Password", exact: true }) - .fill(PASSWORD); - await page.getByRole("textbox", { name: "Confirm password" }).click(); - await page - .getByRole("textbox", { name: "Confirm password" }) - .fill(PASSWORD); - await page.getByRole("button", { name: "Register" }).click(); + await page.getByRole("textbox", { name: "Password" }).fill(PASSWORD); + await page.getByRole("button", { name: "Sign in" }).click(); + + // 😤For reasons web is staying on an infinite loading page after login, so we reload the page + // Super annoying to have to wait... + await page.waitForTimeout(2000); + await page.reload(); await expect( page.getByRole("heading", { name: `Welcome ${username}` }), - ).toBeVisible({ - // Increase timeout as registration can be slow :/ - timeout: 15_000, - }); + ).toBeVisible(); await this.maybeDismissBrowserNotSupportedToast(page); await this.maybeDismissServiceWorkerWarningToast(page); @@ -106,11 +120,7 @@ export class TestHelpers { const clientHandle = await page.evaluateHandle(() => window.mxMatrixClientPeg.get(), ); - const mxId = (await clientHandle.evaluate( - (cli: MatrixClient) => cli.getUserId(), - clientHandle, - ))!; - + const mxId = credentials.user_id; return { page, clientHandle, mxId }; } From 24e721f572625ffd55da82d765194e79639918f6 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 7 Apr 2026 13:43:19 +0200 Subject: [PATCH 02/28] fix web stuck problem! go to / and not to #/login --- playwright/widget/test-helpers.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index d0674f8a..2279e20e 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -95,7 +95,7 @@ export class TestHelpers { reducedMotion: "reduce", }); const page = await userContext.newPage(); - await page.goto(host + "/#/login"); + await page.goto(host); await page.getByRole("link", { name: "Sign in" }).click(); @@ -103,11 +103,6 @@ export class TestHelpers { await page.getByRole("textbox", { name: "Password" }).fill(PASSWORD); await page.getByRole("button", { name: "Sign in" }).click(); - // 😤For reasons web is staying on an infinite loading page after login, so we reload the page - // Super annoying to have to wait... - await page.waitForTimeout(2000); - await page.reload(); - await expect( page.getByRole("heading", { name: `Welcome ${username}` }), ).toBeVisible(); From 0127040a8c88f60e6ad1009804994f8fc3cb316a Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 7 Apr 2026 15:43:55 +0200 Subject: [PATCH 03/28] Remove slow? no registration anymore --- playwright/mobile/create-call-mobile.spec.ts | 1 - playwright/widget/huddle-call.test.ts | 2 -- playwright/widget/pip-call-button-interaction.test.ts | 2 -- playwright/widget/pip-call.test.ts | 2 -- playwright/widget/simple-create.spec.ts | 2 -- playwright/widget/voice-call-dm.spec.ts | 6 ------ 6 files changed, 15 deletions(-) diff --git a/playwright/mobile/create-call-mobile.spec.ts b/playwright/mobile/create-call-mobile.spec.ts index b66ad6c4..1d9d3af0 100644 --- a/playwright/mobile/create-call-mobile.spec.ts +++ b/playwright/mobile/create-call-mobile.spec.ts @@ -53,7 +53,6 @@ test("@mobile Start a new call then leave and show the feedback screen", async ( mobileTest( "Test earpiece overlay in controlledAudioDevices mode", async ({ asMobile, browser }) => { - test.slow(); // Triples the timeout const { creatorPage, inviteLink } = asMobile; // ======== diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 9c7cfc80..ea5d64b6 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -16,8 +16,6 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - test.slow(); // We are registering multiple users here, give it more time - const valere = await addUser("Valere", HOST1); const timo = await addUser("Timo", HOST1); const robin = await addUser("Robin", HOST1); diff --git a/playwright/widget/pip-call-button-interaction.test.ts b/playwright/widget/pip-call-button-interaction.test.ts index 5e59f822..95aa4196 100644 --- a/playwright/widget/pip-call-button-interaction.test.ts +++ b/playwright/widget/pip-call-button-interaction.test.ts @@ -16,8 +16,6 @@ widgetTest("Footer interaction in PiP", async ({ addUser, browserName }) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - test.slow(); - const valere = await addUser("Valere", HOST1); const callRoom = "CallRoom"; diff --git a/playwright/widget/pip-call.test.ts b/playwright/widget/pip-call.test.ts index 63ba2050..ef19a36e 100644 --- a/playwright/widget/pip-call.test.ts +++ b/playwright/widget/pip-call.test.ts @@ -16,8 +16,6 @@ widgetTest("Put call in PIP", async ({ addUser, browserName }) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - test.slow(); - const valere = await addUser("Valere", HOST1); const timo = await addUser("Timo", HOST1); diff --git a/playwright/widget/simple-create.spec.ts b/playwright/widget/simple-create.spec.ts index 4686e99d..6b5d17bb 100644 --- a/playwright/widget/simple-create.spec.ts +++ b/playwright/widget/simple-create.spec.ts @@ -17,8 +17,6 @@ widgetTest.skip( ); widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => { - test.slow(); // Triples the timeout - const { brooks, whistler } = asWidget; await TestHelpers.startCallInCurrentRoom(brooks.page, false); diff --git a/playwright/widget/voice-call-dm.spec.ts b/playwright/widget/voice-call-dm.spec.ts index a4e6255b..7a13c58b 100644 --- a/playwright/widget/voice-call-dm.spec.ts +++ b/playwright/widget/voice-call-dm.spec.ts @@ -20,8 +20,6 @@ widgetTest( "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - test.slow(); // Triples the timeout - const { brooks, whistler } = asWidget; await TestHelpers.startCallInCurrentRoom(brooks.page, true); @@ -113,8 +111,6 @@ widgetTest( "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - test.slow(); // Triples the timeout - const { brooks, whistler } = asWidget; await TestHelpers.startCallInCurrentRoom(brooks.page, false); @@ -200,8 +196,6 @@ widgetTest( "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - test.slow(); // Triples the timeout - const { brooks, whistler } = asWidget; await TestHelpers.startCallInCurrentRoom(brooks.page, false); From 701edd952273de7ba77bd3309cdcb8227eb793f2 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 Apr 2026 17:54:10 +0200 Subject: [PATCH 04/28] fixup lint --- playwright/widget/simple-create.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/widget/simple-create.spec.ts b/playwright/widget/simple-create.spec.ts index 6b5d17bb..c8fb8013 100644 --- a/playwright/widget/simple-create.spec.ts +++ b/playwright/widget/simple-create.spec.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { expect, test } from "@playwright/test"; +import { expect } from "@playwright/test"; import { widgetTest } from "../fixtures/widget-user.ts"; import { TestHelpers } from "./test-helpers.ts"; From 754a42ffd1ff2f9eff8f7999b07457386ba588e8 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 Apr 2026 18:14:51 +0200 Subject: [PATCH 05/28] increase default timeout on huddle test --- playwright/widget/huddle-call.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index ea5d64b6..7bc94f81 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -11,6 +11,9 @@ import { widgetTest } from "../fixtures/widget-user.ts"; import { HOST1, TestHelpers } from "./test-helpers.ts"; widgetTest("Create and join a group call", async ({ addUser, browserName }) => { + // increase the timeouts, it is a long test and it is annoying to retry from the beginning for a single timeout. + test.slow(); + test.skip( browserName === "firefox", "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", From 0e2d2c6f72e8953a3d0be2c26093bc66e1070ad6 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 21 Apr 2026 18:36:40 +0200 Subject: [PATCH 06/28] try paralelize registrations? --- playwright/widget/federated-call.test.ts | 6 ++++-- playwright/widget/huddle-call.test.ts | 14 ++++++++------ playwright/widget/screen-share.test.ts | 8 +++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts index fda58250..2b17a706 100644 --- a/playwright/widget/federated-call.test.ts +++ b/playwright/widget/federated-call.test.ts @@ -26,8 +26,10 @@ modePairs.forEach(([rtcMode1, rtcMode2]) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - const florian = await addUser("floriant", HOST1); - const timo = await addUser("timo", HOST2); + const [florian, timo] = await Promise.all([ + addUser("florian", HOST1), + addUser("timo", HOST2), + ]); const roomName = "Call Room"; diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 7bc94f81..07f12d8a 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { expect, test } from "@playwright/test"; import { widgetTest } from "../fixtures/widget-user.ts"; -import { HOST1, TestHelpers } from "./test-helpers.ts"; +import { HOST1, HOST2, TestHelpers } from "./test-helpers.ts"; widgetTest("Create and join a group call", async ({ addUser, browserName }) => { // increase the timeouts, it is a long test and it is annoying to retry from the beginning for a single timeout. @@ -19,11 +19,13 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); - const valere = await addUser("Valere", HOST1); - const timo = await addUser("Timo", HOST1); - const robin = await addUser("Robin", HOST1); - const halfshot = await addUser("Halfshot", HOST1); - const florian = await addUser("florian", HOST1); + const [valere, timo, robin, halfshot, florian] = await Promise.all([ + addUser("Valere", HOST1), + addUser("Timo", HOST1), + addUser("Robin", HOST1), + addUser("Halfshot", HOST1), + addUser("florian", HOST1), + ]); const roomName = "Group Call Room"; await TestHelpers.createRoom(roomName, valere.page, [ diff --git a/playwright/widget/screen-share.test.ts b/playwright/widget/screen-share.test.ts index c60ef697..f8437e1f 100644 --- a/playwright/widget/screen-share.test.ts +++ b/playwright/widget/screen-share.test.ts @@ -18,9 +18,11 @@ widgetTest("Sharing screen in group call", async ({ addUser, browserName }) => { test.slow(); // We are registering multiple users here, give it more time - const alice = await addUser("Alice", HOST1); - const bob = await addUser("Bob", HOST1); - const carol = await addUser("Carol", HOST1); + const [alice, bob, carol] = await Promise.all([ + addUser("Alice", HOST1), + addUser("Bob", HOST1), + addUser("Carol", HOST1), + ]); const roomName = "Meeting Room"; await TestHelpers.createRoom(roomName, alice.page, [bob.mxId, carol.mxId]); From 55de3fd216ef84c0501754c6f704a9f8f492063b Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 09:03:57 +0200 Subject: [PATCH 07/28] tests optimizations --- .../widget/hotswap-legacy-compat.test.ts | 12 +-- playwright/widget/huddle-call.test.ts | 93 ++++++++++--------- playwright/widget/test-helpers.ts | 5 +- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/playwright/widget/hotswap-legacy-compat.test.ts b/playwright/widget/hotswap-legacy-compat.test.ts index e4695624..f58a562d 100644 --- a/playwright/widget/hotswap-legacy-compat.test.ts +++ b/playwright/widget/hotswap-legacy-compat.test.ts @@ -65,15 +65,13 @@ widgetTest( .contentFrame(); await expect(frame.getByTestId("videoTile")).toHaveCount(2); - // There are no other options than to wait for all media to be ready? - // Or it is too flaky :/ - await user.page.waitForTimeout(3000); - // No one should be waiting for media - await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); + // Wait for "Waiting for media..." to disappear (with timeout) + await expect(frame.getByText("Waiting for media...")).not.toBeVisible({ + timeout: 10000, // Maximum time to wait + }); // 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); const blockDisplayCount = await frame .locator("video") diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 07f12d8a..262b66db 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details. import { expect, test } from "@playwright/test"; import { widgetTest } from "../fixtures/widget-user.ts"; -import { HOST1, HOST2, TestHelpers } from "./test-helpers.ts"; +import { HOST1, TestHelpers } from "./test-helpers.ts"; widgetTest("Create and join a group call", async ({ addUser, browserName }) => { // increase the timeouts, it is a long test and it is annoying to retry from the beginning for a single timeout. @@ -50,52 +50,56 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { await TestHelpers.joinCallFromLobby(valere.page); - for (const user of [timo, robin, halfshot, florian]) { - await TestHelpers.joinCallInCurrentRoom(user.page); - } + await Promise.all( + [timo, robin, halfshot, florian].map(async (user) => { + await TestHelpers.joinCallInCurrentRoom(user.page); + }), + ); - for (const user of [timo, robin, halfshot, florian]) { - const frame = user.page - .locator('iframe[title="Element Call"]') - .contentFrame(); - // No lobby, should start with video on - await expect( - frame.getByRole("switch", { name: "Stop video", checked: true }), - ).toBeVisible(); - } + await Promise.all( + [timo, robin, halfshot, florian].map(async (user) => { + const frame = user.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + await expect( + frame.getByRole("switch", { name: "Stop video", checked: true }), + ).toBeVisible(); + }), + ); // We should see 5 video tiles everywhere now - for (const user of [valere, timo, robin, halfshot, florian]) { - const frame = user.page - .locator('iframe[title="Element Call"]') - .contentFrame(); - await expect(frame.getByTestId("videoTile")).toHaveCount(5); - for (const participant of [valere, timo, robin, halfshot, florian]) { - // Check the names are correct - await expect(frame.getByText(participant.displayName)).toBeVisible(); - } + await Promise.all( + [valere, timo, robin, halfshot, florian].map(async (user) => { + const frame = user.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + await expect(frame.getByTestId("videoTile")).toHaveCount(5); + for (const participant of [valere, timo, robin, halfshot, florian]) { + // Check the names are correct + await expect(frame.getByText(participant.displayName)).toBeVisible(); + } - // There is no other options than to wait for all media to be ready? - // Or it is too flaky :/ - await user.page.waitForTimeout(5000); - // No one should be waiting for media - await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); + // No one should be waiting for media + await expect(frame.getByText("Waiting for media...")).not.toBeVisible({ + // Use a bigger timeout here + timeout: 10000, + }); - // There should be 5 video elements, visible and autoplaying - const videoElements = await frame.locator("video").all(); - expect(videoElements.length).toBe(5); - await expect(frame.locator("video[autoplay]")).toHaveCount(5); + // There should be 5 video elements, visible and autoplaying + 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); - } + const blockDisplayCount = await frame + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => window.getComputedStyle(v).display === "block", + ).length, + ); + expect(blockDisplayCount).toBe(5); + }), + ); // Quickly test muting one participant to see it reflects and that our asserts works const florianFrame = florian.page @@ -111,15 +115,14 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { await expect(florianVideoButton).toHaveAccessibleName("Start video"); await expect(florianVideoButton).not.toBeChecked(); - // wait a bit for the state to propagate - await valere.page.waitForTimeout(3000); { const frame = valere.page .locator('iframe[title="Element Call"]') .contentFrame(); - const videoElements = await frame.locator("video").all(); - expect(videoElements.length).toBe(5); + await expect(frame.locator("video")).toHaveCount(5, { + timeout: 10000, + }); const blockDisplayCount = await frame .locator("video") diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 2279e20e..e37e4b5b 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -105,7 +105,10 @@ export class TestHelpers { await expect( page.getByRole("heading", { name: `Welcome ${username}` }), - ).toBeVisible(); + ).toBeVisible({ + // Increase timeout here + timeout: 10000, + }); await this.maybeDismissBrowserNotSupportedToast(page); await this.maybeDismissServiceWorkerWarningToast(page); From f9dc4d9b82c17fcadd711c4088b64fd677632b8b Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 14:14:54 +0200 Subject: [PATCH 08/28] Update a tohaveCount() timeout that made a test flak 2 times --- playwright/widget/huddle-call.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 262b66db..ce4b9be7 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -73,7 +73,9 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { const frame = user.page .locator('iframe[title="Element Call"]') .contentFrame(); - await expect(frame.getByTestId("videoTile")).toHaveCount(5); + await expect(frame.getByTestId("videoTile")).toHaveCount(5, { + timeout: 10000, + }); for (const participant of [valere, timo, robin, halfshot, florian]) { // Check the names are correct await expect(frame.getByText(participant.displayName)).toBeVisible(); From 5dd1c76d1cb49178d34bf7410713363a8e1a6830 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 14:42:35 +0200 Subject: [PATCH 09/28] increase timeout of flaky test --- playwright/widget/pip-call.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/playwright/widget/pip-call.test.ts b/playwright/widget/pip-call.test.ts index ef19a36e..dab2d5fd 100644 --- a/playwright/widget/pip-call.test.ts +++ b/playwright/widget/pip-call.test.ts @@ -45,7 +45,10 @@ widgetTest("Put call in PIP", async ({ addUser, browserName }) => { // check that the video is on await expect( frame.getByRole("switch", { name: "Stop video", checked: true }), - ).toBeVisible(); + ).toBeVisible({ + // Increase timeout, as this expect was flaky + timeout: 10000, + }); // Switch to the other room, the call should go to PIP await TestHelpers.switchToRoomNamed(valere.page, "DoubleTask"); From 879526ce5e023a08e5e44d9fba2ead1e6cd49095 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 15:17:40 +0200 Subject: [PATCH 10/28] adjust flaky timeouts --- playwright/spa-helpers.ts | 7 +++---- playwright/widget/simple-create.spec.ts | 2 +- playwright/widget/test-helpers.ts | 8 ++++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/playwright/spa-helpers.ts b/playwright/spa-helpers.ts index 648f6e6e..e909cb6a 100644 --- a/playwright/spa-helpers.ts +++ b/playwright/spa-helpers.ts @@ -119,11 +119,10 @@ async function setRtcModeFromSettings( async function expectVideoTilesCount(page: Page, count: number): Promise { await expect(page.getByTestId("videoTile")).toHaveCount(2); - // There are no other options than to wait for all media to be ready? - // Or it is too flaky :/ - await page.waitForTimeout(3000); // No one should be waiting for media - await expect(page.getByText("Waiting for media...")).not.toBeVisible(); + await expect(page.getByText("Waiting for media...")).not.toBeVisible({ + timeout: 10000, + }); // There should be 5 video elements, visible and autoplaying const videoElements = await page.locator("video").all(); diff --git a/playwright/widget/simple-create.spec.ts b/playwright/widget/simple-create.spec.ts index c8fb8013..b01bdd3b 100644 --- a/playwright/widget/simple-create.spec.ts +++ b/playwright/widget/simple-create.spec.ts @@ -81,7 +81,7 @@ widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => { .locator('iframe[title="Element Call"]') .contentFrame() .getByTestId("incall_leave") - .click(); + .click({ timeout: 10000 }); await expect(whistler.page.locator(".mx_BasicMessageComposer")).toBeVisible(); await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible(); diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index e37e4b5b..9f60e2d5 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -59,7 +59,9 @@ export class TestHelpers { ): Promise { // This is the header button that notifies about an ongoing call const label = audioOnly ? "Voice call started" : "Video call started"; - await expect(page.getByText(label)).toBeVisible(); + await expect(page.getByText(label)).toBeVisible({ + timeout: 10000, + }); await expect(page.getByRole("button", { name: "Join" })).toBeVisible(); await page.getByRole("button", { name: "Join" }).click(); } @@ -97,7 +99,9 @@ export class TestHelpers { const page = await userContext.newPage(); await page.goto(host); - await page.getByRole("link", { name: "Sign in" }).click(); + await page.getByRole("link", { name: "Sign in" }).click({ + timeout: 10000, + }); await page.getByRole("textbox", { name: "Username" }).fill(username); await page.getByRole("textbox", { name: "Password" }).fill(PASSWORD); From f355aa1ed7dd4343f728421f2137c73dd712f7e9 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 15:40:06 +0200 Subject: [PATCH 11/28] adjust timeout --- playwright/spa-helpers.ts | 3 +-- playwright/widget/pip-call.test.ts | 2 +- playwright/widget/simple-create.spec.ts | 2 +- playwright/widget/test-helpers.ts | 4 +++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/playwright/spa-helpers.ts b/playwright/spa-helpers.ts index e909cb6a..4c6d83c1 100644 --- a/playwright/spa-helpers.ts +++ b/playwright/spa-helpers.ts @@ -125,8 +125,7 @@ async function expectVideoTilesCount(page: Page, count: number): Promise { }); // There should be 5 video elements, visible and autoplaying - const videoElements = await page.locator("video").all(); - expect(videoElements.length).toBe(count); + await expect(page.locator("video")).toHaveCount(count); const blockDisplayCount = await page .locator("video") diff --git a/playwright/widget/pip-call.test.ts b/playwright/widget/pip-call.test.ts index dab2d5fd..def2c088 100644 --- a/playwright/widget/pip-call.test.ts +++ b/playwright/widget/pip-call.test.ts @@ -47,7 +47,7 @@ widgetTest("Put call in PIP", async ({ addUser, browserName }) => { frame.getByRole("switch", { name: "Stop video", checked: true }), ).toBeVisible({ // Increase timeout, as this expect was flaky - timeout: 10000, + timeout: 15000, }); // Switch to the other room, the call should go to PIP diff --git a/playwright/widget/simple-create.spec.ts b/playwright/widget/simple-create.spec.ts index b01bdd3b..a7ad8208 100644 --- a/playwright/widget/simple-create.spec.ts +++ b/playwright/widget/simple-create.spec.ts @@ -81,7 +81,7 @@ widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => { .locator('iframe[title="Element Call"]') .contentFrame() .getByTestId("incall_leave") - .click({ timeout: 10000 }); + .click({ timeout: 15000 }); await expect(whistler.page.locator(".mx_BasicMessageComposer")).toBeVisible(); await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible(); diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 9f60e2d5..8463e421 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -193,7 +193,9 @@ export class TestHelpers { await page.getByRole("menuitem", { name: "New Room" }).click(); await page.getByRole("textbox", { name: "Name" }).fill(name); await page.getByRole("button", { name: "Create room" }).click(); - await expect(page.getByText("You created this room.")).toBeVisible(); + await expect(page.getByText("You created this room.")).toBeVisible({ + timeout: 10000, + }); await expect(page.getByText("Encryption enabled")).toBeVisible(); await TestHelpers.maybeDismissKeyBackupToast(page); From 854cfdfe1badb681168d7d2c16fe4e6c4fd401ce Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 16:51:04 +0200 Subject: [PATCH 12/28] adjust timeouts --- playwright/spa-call-sticky.spec.ts | 8 ++++++-- playwright/widget/screen-share.test.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/playwright/spa-call-sticky.spec.ts b/playwright/spa-call-sticky.spec.ts index 328a65da..1dbda735 100644 --- a/playwright/spa-call-sticky.spec.ts +++ b/playwright/spa-call-sticky.spec.ts @@ -115,8 +115,12 @@ test("One to One rejoin after improper leave does not crash EC", async ({ await guestPage.getByTestId("lobby_joinCall").click(); // We cannot use the `expectVideoTilesCount` helper here since one of them is expected to show waiting for media - await expect(page.getByTestId("videoTile")).toHaveCount(3); - await expect(guestPage.getByTestId("videoTile")).toHaveCount(2); + await expect(page.getByTestId("videoTile")).toHaveCount(3, { + timeout: 10000, + }); + await expect(guestPage.getByTestId("videoTile")).toHaveCount(2, { + timeout: 10000, + }); }); function isStickySend(url: string): boolean { diff --git a/playwright/widget/screen-share.test.ts b/playwright/widget/screen-share.test.ts index f8437e1f..c6b03c3f 100644 --- a/playwright/widget/screen-share.test.ts +++ b/playwright/widget/screen-share.test.ts @@ -52,7 +52,7 @@ widgetTest("Sharing screen in group call", async ({ addUser, browserName }) => { // Expect 3 video tiles await expect(frame.locator("video")).toHaveCount(3, { - timeout: 5000, + timeout: 10000, }); } From 1b9682869aa2007c8711ff1b37c8b4866e2878a3 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 17:25:14 +0200 Subject: [PATCH 13/28] adjust timeouts --- playwright/widget/federated-call.test.ts | 11 ++++++----- playwright/widget/huddle-call.test.ts | 4 +++- playwright/widget/pip-call.test.ts | 4 +++- playwright/widget/simple-create.spec.ts | 8 ++++++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts index 2b17a706..7685e486 100644 --- a/playwright/widget/federated-call.test.ts +++ b/playwright/widget/federated-call.test.ts @@ -59,13 +59,14 @@ modePairs.forEach(([rtcMode1, rtcMode2]) => { const frame = user.page .locator('iframe[title="Element Call"]') .contentFrame(); - await expect(frame.getByTestId("videoTile")).toHaveCount(2); + await expect(frame.getByTestId("videoTile")).toHaveCount(2, { + timeout: 10000, + }); - // There are no other options than to wait for all media to be ready? - // Or it is too flaky :/ - await user.page.waitForTimeout(3000); // No one should be waiting for media - await expect(frame.getByText("Waiting for media...")).not.toBeVisible(); + await expect(frame.getByText("Waiting for media...")).not.toBeVisible({ + timeout: 10000, + }); // There should be 2 video elements, visible and autoplaying const videoElements = await frame.locator("video").all(); diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index ce4b9be7..ec3c816a 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -63,7 +63,9 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { .contentFrame(); await expect( frame.getByRole("switch", { name: "Stop video", checked: true }), - ).toBeVisible(); + ).toBeVisible({ + timeout: 10000, + }); }), ); diff --git a/playwright/widget/pip-call.test.ts b/playwright/widget/pip-call.test.ts index def2c088..4abee3cf 100644 --- a/playwright/widget/pip-call.test.ts +++ b/playwright/widget/pip-call.test.ts @@ -64,8 +64,10 @@ widgetTest("Put call in PIP", async ({ addUser, browserName }) => { const frame = valere.page .locator('iframe[title="Element Call"]') .contentFrame(); + + await expect(frame.locator("video")).toHaveCount(1, { timeout: 10000 }); + const videoElements = await frame.locator("video").all(); - expect(videoElements.length).toBe(1); const pipVideo = videoElements[0]; await expect(pipVideo).toHaveCSS("object-fit", "cover"); diff --git a/playwright/widget/simple-create.spec.ts b/playwright/widget/simple-create.spec.ts index a7ad8208..86324eeb 100644 --- a/playwright/widget/simple-create.spec.ts +++ b/playwright/widget/simple-create.spec.ts @@ -83,6 +83,10 @@ widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => { .getByTestId("incall_leave") .click({ timeout: 15000 }); - await expect(whistler.page.locator(".mx_BasicMessageComposer")).toBeVisible(); - await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible(); + await expect(whistler.page.locator(".mx_BasicMessageComposer")).toBeVisible({ + timeout: 10000, + }); + await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible({ + timeout: 10000, + }); }); From 5c4d6d031743f97a7cfbb4f1a9966a62a7cbde7a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 18:06:12 +0200 Subject: [PATCH 14/28] 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); + } } From 78e7adc4c6c774072f2b642bcf5dda87004bf307 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 18:49:51 +0200 Subject: [PATCH 15/28] remove redundant toBeVisible before click --- playwright/widget/test-helpers.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 598bdc65..f4a360a7 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -29,8 +29,10 @@ export class TestHelpers { voice: boolean = false, ): Promise { const buttonName = voice ? "Voice call" : "Video call"; - await expect(page.getByRole("button", { name: buttonName })).toBeVisible(); - await page.getByRole("button", { name: buttonName }).click(); + + await page.getByRole("button", { name: buttonName }).click({ + timeout: 5000, + }); await expect( page.getByRole("menuitem", { name: "Element Call" }), @@ -63,8 +65,9 @@ export class TestHelpers { await expect(page.getByText(label)).toBeVisible({ timeout: 10000, }); - await expect(page.getByRole("button", { name: "Join" })).toBeVisible(); - await page.getByRole("button", { name: "Join" }).click(); + await page.getByRole("button", { name: "Join" }).click({ + timeout: 5000, + }); } /** @@ -191,7 +194,9 @@ export class TestHelpers { .getByRole("button", { name: "New conversation" }) .click(); - await page.getByRole("menuitem", { name: "New Room" }).click(); + await page.getByRole("menuitem", { name: "New Room" }).click({ + timeout: 5000, + }); await page.getByRole("textbox", { name: "Name" }).fill(name); await page.getByRole("button", { name: "Create room" }).click(); await expect(page.getByText("You created this room.")).toBeVisible({ @@ -226,9 +231,12 @@ export class TestHelpers { roomName: string, page: Page, ): Promise { - await expect(page.getByRole("option", { name: roomName })).toBeVisible(); - await page.getByRole("option", { name: roomName }).click(); - await page.getByRole("button", { name: "Accept" }).click(); + await page.getByRole("option", { name: roomName }).click({ + timeout: 10000, + }); + await page.getByRole("button", { name: "Accept" }).click({ + timeout: 5000, + }); await expect( page.getByRole("main").getByRole("heading", { name: roomName }), From 4d8e4c66eb29f667b63fbb7200af7213183d7784 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 19:30:02 +0200 Subject: [PATCH 16/28] adjust time out --- playwright/fixtures/widget-user.ts | 4 +++- playwright/widget/federated-call.test.ts | 2 ++ playwright/widget/huddle-call.test.ts | 2 +- playwright/widget/test-helpers.ts | 8 ++++++-- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index 31422fcc..67f66560 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -91,7 +91,9 @@ export const widgetTest = test.extend({ await ewPage1 .getByRole("button", { name: "Invite to this room", exact: true }) - .click(); + .click({ + timeout: 10000, + }); await expect( ewPage1.getByRole("heading", { name: "Invite to Welcome Room" }), ).toBeVisible(); diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts index f3d3d965..560636a5 100644 --- a/playwright/widget/federated-call.test.ts +++ b/playwright/widget/federated-call.test.ts @@ -26,6 +26,8 @@ modePairs.forEach(([rtcMode1, rtcMode2]) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); + test.slow(); + const [florian, timo] = await Promise.all([ addUser("florian", HOST1), addUser("timo", HOST2), diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 029849be..7faba352 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -76,7 +76,7 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { .locator('iframe[title="Element Call"]') .contentFrame(); await expect(frame.getByTestId("videoTile")).toHaveCount(5, { - timeout: 10000, + timeout: 15000, }); for (const participant of [valere, timo, robin, halfshot, florian]) { // Check the names are correct diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index f4a360a7..cabb6293 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -256,8 +256,12 @@ export class TestHelpers { page: Page, mode: RtcMode, ): Promise { - await page.getByRole("button", { name: "Video call" }).click(); - await page.getByRole("menuitem", { name: "Element Call" }).click(); + await page.getByRole("button", { name: "Video call" }).click({ + timeout: 5000, + }); + await page.getByRole("menuitem", { name: "Element Call" }).click({ + timeout: 5000, + }); await TestHelpers.setEmbeddedElementCallRtcMode(page, mode); await page.getByRole("button", { name: "Close lobby" }).click(); From 21b78d6dad696f063cf4fda3005cf18b558c5f62 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 19:30:15 +0200 Subject: [PATCH 17/28] test something in the reconnect bug --- playwright/sfu-reconnect-bug.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/playwright/sfu-reconnect-bug.spec.ts b/playwright/sfu-reconnect-bug.spec.ts index 9f666f0f..046f623d 100644 --- a/playwright/sfu-reconnect-bug.spec.ts +++ b/playwright/sfu-reconnect-bug.spec.ts @@ -91,8 +91,14 @@ test("When creator left, avoid reconnect to the same SFU", async ({ // the creator leaves the call await creatorPage.getByTestId("incall_leave").click(); - await guestCPage.waitForTimeout(2000); // https://github.com/element-hq/element-call/issues/3344 // The app used to request a new jwt token then to reconnect to the SFU expect(wsConnectionCount).toBe(1); + await expect + .poll(() => wsConnectionCount, { + message: + "Expected only 1 WebSocket connection (no reconnection after creator left)", + timeout: 5000, // Check for 5 seconds that it stays at 1 + }) + .toBe(1); }); From 6d70247acb68ec9c231f4dce751dbe1828a9ac95 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 20:13:00 +0200 Subject: [PATCH 18/28] this test should be slow (taking more than 30s) --- playwright/widget/pip-call.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playwright/widget/pip-call.test.ts b/playwright/widget/pip-call.test.ts index 4abee3cf..b18252c1 100644 --- a/playwright/widget/pip-call.test.ts +++ b/playwright/widget/pip-call.test.ts @@ -16,6 +16,8 @@ widgetTest("Put call in PIP", async ({ addUser, browserName }) => { "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", ); + test.slow(); + const valere = await addUser("Valere", HOST1); const timo = await addUser("Timo", HOST1); From 6edc4084c682133a0c9965051f3614410209c257 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 20:16:59 +0200 Subject: [PATCH 19/28] adjust timeouts --- playwright/widget/test-helpers.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index cabb6293..5d277539 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -34,11 +34,9 @@ export class TestHelpers { timeout: 5000, }); - await expect( - page.getByRole("menuitem", { name: "Element Call" }), - ).toBeVisible(); - - await page.getByRole("menuitem", { name: "Element Call" }).click(); + await page.getByRole("menuitem", { name: "Element Call" }).click({ + timeout: 10000, + }); } public static async joinCallFromLobby(page: Page): Promise { @@ -260,7 +258,7 @@ export class TestHelpers { timeout: 5000, }); await page.getByRole("menuitem", { name: "Element Call" }).click({ - timeout: 5000, + timeout: 10000, }); await TestHelpers.setEmbeddedElementCallRtcMode(page, mode); From e5c11eee13a0bbdb84262ad9c39f9ddba3ac68cc Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 20:27:30 +0200 Subject: [PATCH 20/28] fix bad assert for reconnect --- playwright/sfu-reconnect-bug.spec.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/playwright/sfu-reconnect-bug.spec.ts b/playwright/sfu-reconnect-bug.spec.ts index 046f623d..78e89fe6 100644 --- a/playwright/sfu-reconnect-bug.spec.ts +++ b/playwright/sfu-reconnect-bug.spec.ts @@ -94,11 +94,7 @@ test("When creator left, avoid reconnect to the same SFU", async ({ // https://github.com/element-hq/element-call/issues/3344 // The app used to request a new jwt token then to reconnect to the SFU expect(wsConnectionCount).toBe(1); - await expect - .poll(() => wsConnectionCount, { - message: - "Expected only 1 WebSocket connection (no reconnection after creator left)", - timeout: 5000, // Check for 5 seconds that it stays at 1 - }) - .toBe(1); + // Wait a bit to be sure that if there was a reconnect, it would have happened by now + await guestCPage.waitForTimeout(6000); + expect(wsConnectionCount).toBe(1); }); From 84ac0cc47d52ae2d422a78af1f86d669f40fa508 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 22 Apr 2026 20:56:42 +0200 Subject: [PATCH 21/28] mark test as slow --- playwright/widget/simple-create.spec.ts | 4 +++- playwright/widget/test-helpers.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/playwright/widget/simple-create.spec.ts b/playwright/widget/simple-create.spec.ts index 86324eeb..31afb31e 100644 --- a/playwright/widget/simple-create.spec.ts +++ b/playwright/widget/simple-create.spec.ts @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { expect } from "@playwright/test"; +import { expect, test } from "@playwright/test"; import { widgetTest } from "../fixtures/widget-user.ts"; import { TestHelpers } from "./test-helpers.ts"; @@ -17,6 +17,8 @@ widgetTest.skip( ); widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => { + test.slow(); + const { brooks, whistler } = asWidget; await TestHelpers.startCallInCurrentRoom(brooks.page, false); diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 5d277539..b8e57856 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -105,8 +105,12 @@ export class TestHelpers { timeout: 10000, }); - await page.getByRole("textbox", { name: "Username" }).fill(username); - await page.getByRole("textbox", { name: "Password" }).fill(PASSWORD); + await page.getByRole("textbox", { name: "Username" }).fill(username, { + timeout: 10000, + }); + await page.getByRole("textbox", { name: "Password" }).fill(PASSWORD, { + timeout: 10000, + }); await page.getByRole("button", { name: "Sign in" }).click(); await expect( From d9dc61a49dab6a3183fd6b0fcd0d8b3e906715f2 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 23 Apr 2026 09:50:01 +0200 Subject: [PATCH 22/28] mark test as slow --- playwright/widget/hotswap-legacy-compat.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/playwright/widget/hotswap-legacy-compat.test.ts b/playwright/widget/hotswap-legacy-compat.test.ts index 667ae011..ed6f1508 100644 --- a/playwright/widget/hotswap-legacy-compat.test.ts +++ b/playwright/widget/hotswap-legacy-compat.test.ts @@ -27,6 +27,7 @@ import { HOST1, HOST2, TestHelpers } from "./test-helpers"; widgetTest( `Test swapping publisher from ${HOST1} to ${HOST2}`, async ({ addUser, browserName }) => { + test.slow(); test.skip( browserName === "firefox", "The is test is not working on firefox CI environment. No mic/audio device inputs so cam/mic are disabled", From a7788a2bfa9ec16d3614e35bf6c3e17918019b74 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 23 Apr 2026 11:10:46 +0200 Subject: [PATCH 23/28] Dismiss the new invite confirmation modal --- playwright/fixtures/widget-user.ts | 2 ++ playwright/widget/test-helpers.ts | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index 67f66560..68aef8d9 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -106,6 +106,7 @@ export const widgetTest = test.extend({ await ewPage1.getByRole("dialog").getByRole("textbox").fill(whistlerMxId); await ewPage1.getByRole("dialog").getByRole("textbox").click(); await ewPage1.getByRole("button", { name: "Invite" }).click(); + await TestHelpers.dismissInviteUnknownUserModal(ewPage1); // Accept the invite await expect( @@ -128,6 +129,7 @@ export const widgetTest = test.extend({ await ewPage1.getByRole("textbox", { name: "Search" }).click(); await ewPage1.getByRole("textbox", { name: "Search" }).fill(whistlerMxId); await ewPage1.getByRole("button", { name: "Go" }).click(); + await TestHelpers.dismissInviteUnknownUserModalDM(ewPage1); // Wait and send the first message to create the DM await expect( diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index b8e57856..4dd14fb1 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -221,6 +221,7 @@ export class TestHelpers { } await page.getByRole("button", { name: "Invite" }).click(); + await TestHelpers.dismissInviteUnknownUserModal(page); } } @@ -338,6 +339,28 @@ export class TestHelpers { await page.getByRole("option", { name: `Open room ${roomName}` }).click(); } + public static async dismissInviteUnknownUserModal(page: Page): Promise { + await expect( + page.getByRole("heading", { name: "Invite new contacts to this" }), + ).toBeVisible(); + await page.getByRole("button", { name: "Invite" }).click({ + timeout: 5000, + }); + } + + public static async dismissInviteUnknownUserModalDM( + page: Page, + ): Promise { + await expect( + page.getByRole("heading", { + name: "Start a chat with this new contact?", + }), + ).toBeVisible(); + await page.getByRole("button", { name: "Continue" }).click({ + timeout: 5000, + }); + } + public static async expectVisibleVideoCount( frame: FrameLocator, count: number, From 6317a0647221f93e7df639c956d939916f4e2f4d Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Apr 2026 11:21:14 +0200 Subject: [PATCH 24/28] review: quick cleaning --- playwright/spa-helpers.ts | 2 +- playwright/widget/huddle-call.test.ts | 14 +++++++------- playwright/widget/test-helpers.ts | 3 ++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/playwright/spa-helpers.ts b/playwright/spa-helpers.ts index 1756959e..7a839791 100644 --- a/playwright/spa-helpers.ts +++ b/playwright/spa-helpers.ts @@ -124,7 +124,7 @@ async function expectVideoTilesCount(page: Page, count: number): Promise { timeout: 10000, }); - // There should be 5 video elements, visible and autoplaying + // There should be `count` video elements, visible and autoplaying await expect(page.locator("video")).toHaveCount(count); await expect diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 7faba352..04418dcf 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -78,10 +78,13 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { await expect(frame.getByTestId("videoTile")).toHaveCount(5, { timeout: 15000, }); - for (const participant of [valere, timo, robin, halfshot, florian]) { - // Check the names are correct - await expect(frame.getByText(participant.displayName)).toBeVisible(); - } + + await Promise.all([ + [valere, timo, robin, halfshot, florian].map(async (user) => { + // Check the names are correct + expect(frame.getByText(participant.displayName)).toBeVisible(); + }), + ]); // No one should be waiting for media await expect(frame.getByText("Waiting for media...")).not.toBeVisible({ @@ -121,9 +124,6 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { }); // 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. - // ✅ 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 4dd14fb1..b47588ce 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -365,7 +365,8 @@ export class TestHelpers { frame: FrameLocator, count: number, ): Promise { - // ✅ Retryable assertion for visible videos + // 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. await expect .poll( async () => { From ca305b9da158623d7bddd2e9f515a525ce4198d3 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Apr 2026 11:24:00 +0200 Subject: [PATCH 25/28] fix: wrong usage of poll --- playwright/spa-helpers.ts | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/playwright/spa-helpers.ts b/playwright/spa-helpers.ts index 7a839791..46c414c9 100644 --- a/playwright/spa-helpers.ts +++ b/playwright/spa-helpers.ts @@ -127,23 +127,19 @@ async function expectVideoTilesCount(page: Page, count: number): Promise { // There should be `count` video elements, visible and autoplaying await expect(page.locator("video")).toHaveCount(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); + await expect(async () => { + const videoBlockCount = await page + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => window.getComputedStyle(v).display === "block", + ).length, + ); + expect(videoBlockCount).toBe(count); + }).toPass({ + timeout: 10000, + }); } export const SpaHelpers = { From 0d5c71bb4ec8c9630b2a61d96e10e46ce080eec2 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Apr 2026 11:29:34 +0200 Subject: [PATCH 26/28] fixup --- playwright/widget/huddle-call.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 04418dcf..68e1ba54 100644 --- a/playwright/widget/huddle-call.test.ts +++ b/playwright/widget/huddle-call.test.ts @@ -79,12 +79,12 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { timeout: 15000, }); - await Promise.all([ + await Promise.all( [valere, timo, robin, halfshot, florian].map(async (user) => { // Check the names are correct - expect(frame.getByText(participant.displayName)).toBeVisible(); + await expect(frame.getByText(user.displayName)).toBeVisible(); }), - ]); + ); // No one should be waiting for media await expect(frame.getByText("Waiting for media...")).not.toBeVisible({ From 82b7e243eb6a453c6b10c33b6bd2a9953ff7c377 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Apr 2026 12:10:24 +0200 Subject: [PATCH 27/28] This test does not need to run on both FF and chrome --- playwright/sfu-reconnect-bug.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/playwright/sfu-reconnect-bug.spec.ts b/playwright/sfu-reconnect-bug.spec.ts index 78e89fe6..119ea869 100644 --- a/playwright/sfu-reconnect-bug.spec.ts +++ b/playwright/sfu-reconnect-bug.spec.ts @@ -10,6 +10,10 @@ import { expect, test } from "@playwright/test"; test("When creator left, avoid reconnect to the same SFU", async ({ browser, }) => { + test.skip( + ({ browserName }) => browserName === "firefox", + "Browser independent", + ); // Use reduce motion to disable animations that are making the tests a bit flaky const creatorContext = await browser.newContext({ reducedMotion: "reduce" }); const creatorPage = await creatorContext.newPage(); From 2f2bdfce5b46859b54f964110df17291636e7b68 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 24 Apr 2026 12:32:41 +0200 Subject: [PATCH 28/28] fixup: wrong skip --- playwright/sfu-reconnect-bug.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/playwright/sfu-reconnect-bug.spec.ts b/playwright/sfu-reconnect-bug.spec.ts index 119ea869..9be4a3ac 100644 --- a/playwright/sfu-reconnect-bug.spec.ts +++ b/playwright/sfu-reconnect-bug.spec.ts @@ -9,11 +9,9 @@ import { expect, test } from "@playwright/test"; test("When creator left, avoid reconnect to the same SFU", async ({ browser, + browserName, }) => { - test.skip( - ({ browserName }) => browserName === "firefox", - "Browser independent", - ); + test.skip(browserName === "firefox", "Browser independent"); // Use reduce motion to disable animations that are making the tests a bit flaky const creatorContext = await browser.newContext({ reducedMotion: "reduce" }); const creatorPage = await creatorContext.newPage();