diff --git a/playwright/fixtures/widget-user.ts b/playwright/fixtures/widget-user.ts index eeddda47..31422fcc 100644 --- a/playwright/fixtures/widget-user.ts +++ b/playwright/fixtures/widget-user.ts @@ -9,7 +9,7 @@ Please see LICENSE in the repository root for full details. import { type Page, test, expect, type JSHandle } from "@playwright/test"; import type { MatrixClient } from "matrix-js-sdk"; -import { TestHelpers } from "../widget/test-helpers.ts"; +import { HOST1, TestHelpers } from "../widget/test-helpers.ts"; export type UserBaseFixture = { mxId: string; @@ -26,9 +26,7 @@ export type BaseWidgetSetup = { export interface MyFixtures { asWidget: BaseWidgetSetup; callType: "room" | "dm"; - addUser: ( - username: string /**, homeserver: string*/, - ) => Promise; + addUser: (username: string, host: string) => Promise; } // Minimal config.json for the local element-web instance @@ -174,12 +172,14 @@ export const widgetTest = test.extend({ addUser: async ({ browser }, use) => { await use( async ( - username: string /**, homeserver?: string*/, + username: string, + host: string = HOST1, ): Promise => { const uniqueSuffix = Date.now(); const { page, clientHandle, mxId } = await TestHelpers.registerUser( browser, `${username.toLowerCase()}_${uniqueSuffix}`, + host, ); return { mxId, diff --git a/playwright/widget/federated-call.test.ts b/playwright/widget/federated-call.test.ts new file mode 100644 index 00000000..c7f6ac18 --- /dev/null +++ b/playwright/widget/federated-call.test.ts @@ -0,0 +1,71 @@ +/* +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( + "Test federated call default rtc mode", + async ({ addUser, browserName }) => { + test.skip( + browserName === "firefox", + "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 roomName = "Call Room"; + + await TestHelpers.createRoom(roomName, florian.page, [timo.mxId]); + + await TestHelpers.acceptRoomInvite(roomName, timo.page); + + await florian.page.pause(); + + await TestHelpers.setEmbeddedElementCallRtcMode(florian.page, "legacy"); + await TestHelpers.setEmbeddedElementCallRtcMode(timo.page, "compat"); + + await TestHelpers.startCallInCurrentRoom(florian.page, false); + await TestHelpers.joinCallFromLobby(florian.page); + + // timo joins + await TestHelpers.joinCallInCurrentRoom(timo.page); + + // 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); + + // 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(); + + // There should be 5 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); + } + + // await florian.page.pause(); + }, +); diff --git a/playwright/widget/huddle-call.test.ts b/playwright/widget/huddle-call.test.ts index 6acf176c..177ec585 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 { TestHelpers } from "./test-helpers.ts"; +import { HOST1, TestHelpers } from "./test-helpers.ts"; widgetTest("Create and join a group call", async ({ addUser, browserName }) => { test.skip( @@ -18,11 +18,11 @@ widgetTest("Create and join a group call", async ({ addUser, browserName }) => { test.slow(); // We are registering multiple users here, give it more time - const valere = await addUser("Valere"); - const timo = await addUser("Timo"); - const robin = await addUser("Robin"); - const halfshot = await addUser("Halfshot"); - const florian = await addUser("florian"); + 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 roomName = "Group Call Room"; await TestHelpers.createRoom(roomName, valere.page, [ diff --git a/playwright/widget/test-helpers.ts b/playwright/widget/test-helpers.ts index 4ef05ef4..4d1a048a 100644 --- a/playwright/widget/test-helpers.ts +++ b/playwright/widget/test-helpers.ts @@ -15,6 +15,9 @@ import { type MatrixClient } from "matrix-js-sdk"; const PASSWORD = "foobarbaz1!"; +export const HOST1 = "https://app.m.localhost/#/welcome"; +export const HOST2 = "https://app.othersite.m.localhost/#/welcome"; + export class TestHelpers { public static async startCallInCurrentRoom( page: Page, @@ -31,12 +34,38 @@ export class TestHelpers { await page.getByRole("menuitem", { name: "Element Call" }).click(); } + public static async joinCallFromLobby(page: Page): Promise { + await expect( + page + .locator('iframe[title="Element Call"]') + .contentFrame() + .getByTestId("lobby_joinCall"), + ).toBeVisible(); + + await page + .locator('iframe[title="Element Call"]') + .contentFrame() + .getByTestId("lobby_joinCall") + .click(); + } + + public static async joinCallInCurrentRoom( + page: Page, + audioOnly: boolean = false, + ): Promise { + const label = audioOnly ? "Voice call started" : "Video call started"; + await expect(page.getByText(label)).toBeVisible(); + await expect(page.getByRole("button", { name: "Join" })).toBeVisible(); + await page.getByRole("button", { name: "Join" }).click(); + } + /** * Registers a new user and returns page, clientHandle and mxId. */ public static async registerUser( browser: Browser, username: string, + host: string = HOST1, ): Promise<{ page: Page; clientHandle: JSHandle; @@ -46,7 +75,7 @@ export class TestHelpers { reducedMotion: "reduce", }); const page = await userContext.newPage(); - await page.goto("http://localhost:8081/#/welcome"); + await page.goto(host); await page.getByRole("link", { name: "Create Account" }).click(); await page.getByRole("textbox", { name: "Username" }).fill(username); await page @@ -128,6 +157,53 @@ export class TestHelpers { } } + public static async acceptRoomInvite( + 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 expect( + page.getByRole("main").getByRole("heading", { name: roomName }), + ).toBeVisible(); + } + + /** + * Opens the widget and then goes to the settings to set the RTC mode. + * then closes the widget lobby. + * + * WORKS IF A ROOM IS CURRENTLY OPENED IN THE PAGE + */ + public static async setEmbeddedElementCallRtcMode( + page: Page, + mode: "legacy" | "compat" | "2_0", + ): Promise { + await page.getByRole("button", { name: "Video call" }).click(); + await page.getByRole("menuitem", { name: "Element Call" }).click(); + const iframe = page.locator('iframe[title="Element Call"]').contentFrame(); + + await iframe.getByRole("button", { name: "Settings" }).click(); + await iframe.getByRole("tab", { name: "Preferences" }).click(); + + // await iframe.getByText("Developer mode", { exact: true }).click(); + await iframe.getByText("Developer mode", { exact: true }).check(); // Idempotent: won't uncheck if already checked + + // Move to Developer tab now + await iframe.getByRole("tab", { name: "Developer" }).click(); + if (mode === "legacy") { + await iframe.getByText("Legacy: state events").click(); + } else if (mode === "2_0") { + await iframe.getByText("Matrix 2.0").click(); + } else { + // compat + await iframe.getByText("Compatibility: state events").click(); + } + await iframe.getByTestId("modal_close").click(); + await page.getByRole("button", { name: "Close lobby" }).click(); + } + /** * Sets the current Element Web app to use the dev Element Call URL. * @param page - The EW page