playwright: Federated call between legacy and compat

This commit is contained in:
Valere
2026-01-14 17:58:51 +01:00
parent 27e351270c
commit 0d1488af4c
4 changed files with 159 additions and 12 deletions

View File

@@ -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<UserBaseFixture>;
addUser: (username: string, host: string) => Promise<UserBaseFixture>;
}
// Minimal config.json for the local element-web instance
@@ -174,12 +172,14 @@ export const widgetTest = test.extend<MyFixtures>({
addUser: async ({ browser }, use) => {
await use(
async (
username: string /**, homeserver?: string*/,
username: string,
host: string = HOST1,
): Promise<UserBaseFixture> => {
const uniqueSuffix = Date.now();
const { page, clientHandle, mxId } = await TestHelpers.registerUser(
browser,
`${username.toLowerCase()}_${uniqueSuffix}`,
host,
);
return {
mxId,

View File

@@ -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();
},
);

View File

@@ -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, [

View File

@@ -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<void> {
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<void> {
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<MatrixClient>;
@@ -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<void> {
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<void> {
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