diff --git a/playwright/widget/federation-oldest-membership-bug.spec.ts b/playwright/widget/federation-oldest-membership-bug.spec.ts new file mode 100644 index 00000000..70442e05 --- /dev/null +++ b/playwright/widget/federation-oldest-membership-bug.spec.ts @@ -0,0 +1,92 @@ +/* +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 { expect, test } from "@playwright/test"; + +import { widgetTest } from "../fixtures/widget-user"; +import { HOST1, HOST2, TestHelpers } from "./test-helpers"; + +widgetTest( + "Bug new joiner was not publishing on correct SFU", + async ({ addUser, browserName }) => { + test.skip( + browserName === "firefox", + "This is a bug in the old widget, not a browser problem.", + ); + + test.slow(); + + // 2 users in federation + const florian = await addUser("floriant", HOST1); + const timo = await addUser("timo", HOST2); + + // Florian creates a room and invites Timo to it + const roomName = "Call Room"; + await TestHelpers.createRoom(roomName, florian.page, [timo.mxId]); + + // Timo joins the room + await TestHelpers.acceptRoomInvite(roomName, timo.page); + + // Ensure we are in legacy mode (should be the default) + await TestHelpers.openWidgetSetEmbeddedElementCallRtcModeCloseWidget( + florian.page, + "legacy", + ); + await TestHelpers.openWidgetSetEmbeddedElementCallRtcModeCloseWidget( + timo.page, + "legacy", + ); + + // Let timo create a call + await TestHelpers.startCallInCurrentRoom(timo.page, false); + await TestHelpers.joinCallFromLobby(timo.page); + + // We want to simulate that the oldest membership authentication is way slower than + // the preffered auth. + // In this setup, timo advertised$ transport will be it's own, and the active will be the one from florian + await florian.page.route( + "**/matrix-rtc.othersite.m.localhost/livekit/jwt/**", + async (route) => { + await new Promise((resolve) => setTimeout(resolve, 2000)); // 5 second delay + await route.continue(); + }, + ); + + // Florian joins the call + await expect(florian.page.getByTestId("join-call-button")).toBeVisible(); + await florian.page.getByTestId("join-call-button").click(); + await TestHelpers.joinCallFromLobby(florian.page); + + await florian.page.waitForTimeout(3000); + await timo.page.waitForTimeout(3000); + + // We should see 2 video tiles everywhere now + for (const user of [timo, florian]) { + const frame = user.page + .locator('iframe[title="Element Call"]') + .contentFrame(); + await expect(frame.getByTestId("videoTile")).toHaveCount(2); + + // No one should be waiting for media + 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); + + const blockDisplayCount = await frame + .locator("video") + .evaluateAll( + (videos: Element[]) => + videos.filter( + (v: Element) => window.getComputedStyle(v).display === "block", + ).length, + ); + expect(blockDisplayCount).toBe(2); + } + }, +);