Merge pull request #3683 from element-hq/valere/playwright/spa_sticky_test
playwright: Test call in SPA mode with matrix 2.0
This commit is contained in:
@@ -7,6 +7,8 @@ Please see LICENSE in the repository root for full details.
|
|||||||
|
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import { SpaHelpers } from "./spa-helpers.ts";
|
||||||
|
|
||||||
test("Sign up a new account, then login, then logout", async ({ browser }) => {
|
test("Sign up a new account, then login, then logout", async ({ browser }) => {
|
||||||
const userId = `test_user-id_${Date.now()}`;
|
const userId = `test_user-id_${Date.now()}`;
|
||||||
|
|
||||||
@@ -69,12 +71,7 @@ test("As a guest, create a call, share link and other join", async ({
|
|||||||
// ========
|
// ========
|
||||||
// ARRANGE: The first user creates a call as guest, join it, then click the invite button to copy the invite link
|
// ARRANGE: The first user creates a call as guest, join it, then click the invite button to copy the invite link
|
||||||
// ========
|
// ========
|
||||||
await creatorPage.getByTestId("home_callName").click();
|
await SpaHelpers.createCall(creatorPage, "Inviter", "Welcome");
|
||||||
await creatorPage.getByTestId("home_callName").fill("Welcome");
|
|
||||||
await creatorPage.getByTestId("home_displayName").click();
|
|
||||||
await creatorPage.getByTestId("home_displayName").fill("Inviter");
|
|
||||||
await creatorPage.getByTestId("home_go").click();
|
|
||||||
await expect(creatorPage.locator("video")).toBeVisible();
|
|
||||||
|
|
||||||
// join
|
// join
|
||||||
await creatorPage.getByTestId("lobby_joinCall").click();
|
await creatorPage.getByTestId("lobby_joinCall").click();
|
||||||
@@ -82,19 +79,7 @@ test("As a guest, create a call, share link and other join", async ({
|
|||||||
await creatorPage.getByRole("radio", { name: "Spotlight" }).check();
|
await creatorPage.getByRole("radio", { name: "Spotlight" }).check();
|
||||||
|
|
||||||
// Get the invite link
|
// Get the invite link
|
||||||
await creatorPage.getByRole("button", { name: "Invite" }).click();
|
const inviteLink = await SpaHelpers.getCallInviteLink(creatorPage);
|
||||||
await expect(
|
|
||||||
creatorPage.getByRole("heading", { name: "Invite to this call" }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(creatorPage.getByRole("img", { name: "QR Code" })).toBeVisible();
|
|
||||||
await expect(creatorPage.getByTestId("modal_inviteLink")).toBeVisible();
|
|
||||||
await expect(creatorPage.getByTestId("modal_inviteLink")).toBeVisible();
|
|
||||||
await creatorPage.getByTestId("modal_inviteLink").click();
|
|
||||||
|
|
||||||
const inviteLink = (await creatorPage.evaluate(
|
|
||||||
"navigator.clipboard.readText()",
|
|
||||||
)) as string;
|
|
||||||
expect(inviteLink).toContain("room/#/");
|
|
||||||
|
|
||||||
// ========
|
// ========
|
||||||
// ACT: The other user use the invite link to join the call as a guest
|
// ACT: The other user use the invite link to join the call as a guest
|
||||||
@@ -103,13 +88,7 @@ test("As a guest, create a call, share link and other join", async ({
|
|||||||
reducedMotion: "reduce",
|
reducedMotion: "reduce",
|
||||||
});
|
});
|
||||||
const guestPage = await guestInviteeContext.newPage();
|
const guestPage = await guestInviteeContext.newPage();
|
||||||
|
await SpaHelpers.joinCallFromInviteLink(guestPage, inviteLink);
|
||||||
await guestPage.goto(inviteLink);
|
|
||||||
await guestPage.getByTestId("joincall_displayName").fill("Invitee");
|
|
||||||
await expect(guestPage.getByTestId("joincall_joincall")).toBeVisible();
|
|
||||||
await guestPage.getByTestId("joincall_joincall").click();
|
|
||||||
await guestPage.getByTestId("lobby_joinCall").click();
|
|
||||||
await guestPage.getByRole("radio", { name: "Spotlight" }).check();
|
|
||||||
|
|
||||||
// ========
|
// ========
|
||||||
// ASSERT: check that there are two members in the call
|
// ASSERT: check that there are two members in the call
|
||||||
|
|||||||
40
playwright/spa-call-sticky.spec.ts
Normal file
40
playwright/spa-call-sticky.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
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 { test } from "@playwright/test";
|
||||||
|
|
||||||
|
import { SpaHelpers } from "./spa-helpers";
|
||||||
|
|
||||||
|
test("One to One call using matrix rtc 2.0 aka sticky events", async ({
|
||||||
|
browser,
|
||||||
|
page,
|
||||||
|
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",
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
|
||||||
|
await SpaHelpers.createCall(page, "John Doe", "HelloCall", true, "2_0");
|
||||||
|
|
||||||
|
const inviteLink = await SpaHelpers.getCallInviteLink(page);
|
||||||
|
|
||||||
|
// Other
|
||||||
|
const guestInviteeContext = await browser.newContext({
|
||||||
|
reducedMotion: "reduce",
|
||||||
|
});
|
||||||
|
const guestPage = await guestInviteeContext.newPage();
|
||||||
|
|
||||||
|
await guestPage.goto("/");
|
||||||
|
|
||||||
|
await SpaHelpers.joinCallFromInviteLink(guestPage, inviteLink);
|
||||||
|
|
||||||
|
await SpaHelpers.expectVideoTilesCount(page, 2);
|
||||||
|
await SpaHelpers.expectVideoTilesCount(guestPage, 2);
|
||||||
|
});
|
||||||
148
playwright/spa-helpers.ts
Normal file
148
playwright/spa-helpers.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
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, type Page } from "@playwright/test";
|
||||||
|
|
||||||
|
import { type RtcMode } from "./widget/test-helpers.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and join a call from the SPA home page.
|
||||||
|
*
|
||||||
|
* @param page - The Playwright page object
|
||||||
|
* @param userName - The display name to use for the call
|
||||||
|
* @param callName - The name of the call to create
|
||||||
|
* @param autoJoin - Whether to automatically join the call after creating it
|
||||||
|
* @param mode - The RTC mode to use for the call
|
||||||
|
*/
|
||||||
|
async function createCall(
|
||||||
|
page: Page,
|
||||||
|
userName: string,
|
||||||
|
callName: string,
|
||||||
|
autoJoin: boolean = false,
|
||||||
|
mode: RtcMode | undefined = undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
await page.getByTestId("home_callName").click();
|
||||||
|
await page.getByTestId("home_callName").fill(callName);
|
||||||
|
await page.getByTestId("home_displayName").click();
|
||||||
|
await page.getByTestId("home_displayName").fill(userName);
|
||||||
|
await page.getByTestId("home_go").click();
|
||||||
|
|
||||||
|
await expect(page.locator("video")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("lobby_joinCall")).toBeVisible();
|
||||||
|
|
||||||
|
if (mode) {
|
||||||
|
await setRtcModeFromSettings(page, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoJoin) {
|
||||||
|
// Join the call
|
||||||
|
await page.getByTestId("lobby_joinCall").click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the invite link for the current call.
|
||||||
|
*/
|
||||||
|
async function getCallInviteLink(page: Page): Promise<string> {
|
||||||
|
await page.getByRole("button", { name: "Invite" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Invite to this call" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("img", { name: "QR Code" })).toBeVisible();
|
||||||
|
await expect(page.getByTestId("modal_inviteLink")).toBeVisible();
|
||||||
|
await expect(page.getByTestId("modal_inviteLink")).toBeVisible();
|
||||||
|
await page.getByTestId("modal_inviteLink").click();
|
||||||
|
|
||||||
|
const inviteLink = (await page.evaluate(
|
||||||
|
"navigator.clipboard.readText()",
|
||||||
|
)) as string;
|
||||||
|
expect(inviteLink).toContain("room/#/");
|
||||||
|
|
||||||
|
return inviteLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Join a call from an invitation link.
|
||||||
|
* @param page - The Playwright page object
|
||||||
|
* @param inviteLink - The invite link to join
|
||||||
|
* @param displayName - The display name to use when joining the call
|
||||||
|
* @param mode - The RTC mode to use for the call
|
||||||
|
*/
|
||||||
|
async function joinCallFromInviteLink(
|
||||||
|
page: Page,
|
||||||
|
inviteLink: string,
|
||||||
|
displayName: string = "Invitee",
|
||||||
|
mode: RtcMode | undefined = undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
await page.goto(inviteLink);
|
||||||
|
await page.getByTestId("joincall_displayName").fill(displayName);
|
||||||
|
await expect(page.getByTestId("joincall_joincall")).toBeVisible();
|
||||||
|
await page.getByTestId("joincall_joincall").click();
|
||||||
|
|
||||||
|
if (mode) {
|
||||||
|
await setRtcModeFromSettings(page, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByTestId("lobby_joinCall").click();
|
||||||
|
await page.getByRole("radio", { name: "Spotlight" }).check();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setRtcModeFromSettings(
|
||||||
|
page: Page,
|
||||||
|
mode: RtcMode,
|
||||||
|
): Promise<void> {
|
||||||
|
await page.getByRole("button", { name: "Settings" }).click();
|
||||||
|
await page.getByRole("tab", { name: "Preferences" }).click();
|
||||||
|
await page.getByText("Developer mode", { exact: true }).check(); // Idempotent: won't uncheck if already checked
|
||||||
|
|
||||||
|
// Move to Developer tab now
|
||||||
|
await page.getByRole("tab", { name: "Developer" }).click();
|
||||||
|
if (mode == "legacy") {
|
||||||
|
await page.getByText("Legacy: state events").click();
|
||||||
|
} else if (mode == "2_0") {
|
||||||
|
await page.getByText("Matrix 2.0").click();
|
||||||
|
} else {
|
||||||
|
// compat
|
||||||
|
await page.getByText("Compatibility: state events").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByTestId("modal_close").click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expect a certain number of video tiles to be present and visible.
|
||||||
|
*/
|
||||||
|
async function expectVideoTilesCount(page: Page, count: number): Promise<void> {
|
||||||
|
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();
|
||||||
|
|
||||||
|
// There should be 5 video elements, visible and autoplaying
|
||||||
|
const videoElements = await page.locator("video").all();
|
||||||
|
expect(videoElements.length).toBe(count);
|
||||||
|
|
||||||
|
const blockDisplayCount = await page
|
||||||
|
.locator("video")
|
||||||
|
.evaluateAll(
|
||||||
|
(videos: Element[]) =>
|
||||||
|
videos.filter(
|
||||||
|
(v: Element) => window.getComputedStyle(v).display === "block",
|
||||||
|
).length,
|
||||||
|
);
|
||||||
|
expect(blockDisplayCount).toBe(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SpaHelpers = {
|
||||||
|
createCall,
|
||||||
|
getCallInviteLink,
|
||||||
|
joinCallFromInviteLink,
|
||||||
|
expectVideoTilesCount,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user