Merge pull request #3841 from element-hq/valere/devx/use_synapse_api_for_playwright

Use synapse API to register instead doing via UI
This commit is contained in:
Valere Fedronic
2026-04-24 12:50:39 +02:00
committed by GitHub
22 changed files with 411 additions and 191 deletions

View File

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

View File

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

View File

@@ -34,6 +34,13 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
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;
@@ -76,6 +83,14 @@ server {
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;
}

View File

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

View File

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

View File

@@ -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.
*/

View File

@@ -91,7 +91,9 @@ export const widgetTest = test.extend<MyFixtures>({
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();

View File

@@ -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;
// ========

View File

@@ -9,7 +9,9 @@ import { expect, test } from "@playwright/test";
test("When creator left, avoid reconnect to the same SFU", async ({
browser,
browserName,
}) => {
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();
@@ -91,8 +93,10 @@ 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);
// 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);
});

View File

@@ -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 {

View File

@@ -119,25 +119,27 @@ async function setRtcModeFromSettings(
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();
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();
expect(videoElements.length).toBe(count);
// There should be `count` 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(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 = {

View File

@@ -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<SynapseRegistrationResponse> {
// 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<string> {
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);
}
}

View File

@@ -26,8 +26,12 @@ 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);
test.slow();
const [florian, timo] = await Promise.all([
addUser("florian", HOST1),
addUser("timo", HOST2),
]);
const roomName = "Call Room";
@@ -57,27 +61,20 @@ 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();
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();

View File

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

View File

@@ -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",
@@ -65,40 +66,26 @@ 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, {
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,
);
},
);

View File

@@ -11,18 +11,21 @@ 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",
);
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);
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, [
@@ -47,52 +50,55 @@ 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({
timeout: 10000,
});
}),
);
// 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, {
timeout: 15000,
});
// 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();
// 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);
const blockDisplayCount = await frame
.locator("video")
.evaluateAll(
(videos: Element[]) =>
videos.filter(
(v: Element) => window.getComputedStyle(v).display === "block",
).length,
await Promise.all(
[valere, timo, robin, halfshot, florian].map(async (user) => {
// Check the names are correct
await expect(frame.getByText(user.displayName)).toBeVisible();
}),
);
expect(blockDisplayCount).toBe(5);
}
// 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
await expect(frame.locator("video")).toHaveCount(5);
await expect(frame.locator("video[autoplay]")).toHaveCount(5);
await TestHelpers.expectVisibleVideoCount(frame, 5);
}),
);
// Quickly test muting one participant to see it reflects and that our asserts works
const florianFrame = florian.page
@@ -108,28 +114,16 @@ 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);
const blockDisplayCount = await frame
.locator("video")
.evaluateAll(
(videos: Element[]) =>
videos.filter(
(v: Element) => window.getComputedStyle(v).display === "block",
).length,
);
await expect(frame.locator("video")).toHaveCount(5, {
timeout: 10000,
});
// 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);
await TestHelpers.expectVisibleVideoCount(frame, 4);
}
});

View File

@@ -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";

View File

@@ -47,7 +47,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: 15000,
});
// Switch to the other room, the call should go to PIP
await TestHelpers.switchToRoomNamed(valere.page, "DoubleTask");
@@ -63,8 +66,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");

View File

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

View File

@@ -17,7 +17,7 @@ widgetTest.skip(
);
widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => {
test.slow(); // Triples the timeout
test.slow();
const { brooks, whistler } = asWidget;
@@ -83,8 +83,12 @@ widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => {
.locator('iframe[title="Element Call"]')
.contentFrame()
.getByTestId("incall_leave")
.click();
.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,
});
});

View File

@@ -10,9 +10,12 @@ import {
expect,
type JSHandle,
type Page,
type FrameLocator,
} 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";
@@ -26,14 +29,14 @@ export class TestHelpers {
voice: boolean = false,
): Promise<void> {
const buttonName = voice ? "Voice call" : "Video call";
await expect(page.getByRole("button", { name: buttonName })).toBeVisible();
await page.getByRole("button", { name: buttonName }).click();
await expect(
page.getByRole("menuitem", { name: "Element Call" }),
).toBeVisible();
await page.getByRole("button", { name: buttonName }).click({
timeout: 5000,
});
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<void> {
@@ -57,9 +60,12 @@ export class TestHelpers {
): Promise<void> {
// 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.getByRole("button", { name: "Join" })).toBeVisible();
await page.getByRole("button", { name: "Join" }).click();
await expect(page.getByText(label)).toBeVisible({
timeout: 10000,
});
await page.getByRole("button", { name: "Join" }).click({
timeout: 5000,
});
}
/**
@@ -74,28 +80,44 @@ export class TestHelpers {
clientHandle: JSHandle<MatrixClient>;
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.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("link", { name: "Sign in" }).click({
timeout: 10000,
});
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(
page.getByRole("heading", { name: `Welcome ${username}` }),
).toBeVisible({
// Increase timeout as registration can be slow :/
timeout: 15_000,
// Increase timeout here :/ flaky
timeout: 15000,
});
await this.maybeDismissBrowserNotSupportedToast(page);
@@ -106,11 +128,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 };
}
@@ -178,10 +196,14 @@ 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();
await expect(page.getByText("You created this room.")).toBeVisible({
timeout: 10000,
});
await expect(page.getByText("Encryption enabled")).toBeVisible();
await TestHelpers.maybeDismissKeyBackupToast(page);
@@ -212,9 +234,12 @@ export class TestHelpers {
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 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 }),
@@ -234,8 +259,12 @@ export class TestHelpers {
page: Page,
mode: RtcMode,
): Promise<void> {
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: 10000,
});
await TestHelpers.setEmbeddedElementCallRtcMode(page, mode);
await page.getByRole("button", { name: "Close lobby" }).click();
@@ -331,4 +360,30 @@ export class TestHelpers {
timeout: 5000,
});
}
public static async expectVisibleVideoCount(
frame: FrameLocator,
count: number,
): Promise<void> {
// 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 () => {
return await frame
.locator("video")
.evaluateAll(
(videos: Element[]) =>
videos.filter(
(v: Element) =>
window.getComputedStyle(v).display === "block",
).length,
);
},
{
timeout: 10000,
},
)
.toBe(count);
}
}

View File

@@ -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);