mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Test the standalone knock flow in Playwright
A knock call room, created over the CS API, walks through the lobby's join states: request to join, the waiting copy with its withdrawal link, a second request, and an invite from the room's moderator that lands the user in the call. A decline, a ban with its reason, and a room whose join rule the app cannot act on are covered too. An invite-only room asserts what happens today: Synapse serves no summary for it, so the refused join is a full-screen error rather than a lobby state. The `spaUser` fixture registers over the admin API and logs in through the form, and serves `config.devenv.json` itself, since Playwright reuses a dev server started with another config. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
1c8930daa0
commit
58b9e2d983
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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 } from "@playwright/test";
|
||||
|
||||
import { spaTest as test, type SpaUser } from "./fixtures/spa-user.ts";
|
||||
import {
|
||||
banUser,
|
||||
createCallRoom,
|
||||
inviteUser,
|
||||
kickUser,
|
||||
membershipOf,
|
||||
} from "./utils/matrix-api.ts";
|
||||
|
||||
/** The app URL for a call known only by its room ID. */
|
||||
const callUrl = (roomId: string): string =>
|
||||
`/room/#?roomId=${encodeURIComponent(roomId)}`;
|
||||
|
||||
/** Reads the membership the server holds for `user`, as a moderator sees it. */
|
||||
const membershipPoll =
|
||||
(moderator: SpaUser, roomId: string, user: SpaUser) =>
|
||||
async (): Promise<string | undefined> =>
|
||||
await membershipOf(moderator.accessToken, roomId, user.mxId);
|
||||
|
||||
test("Ask to join a call, withdraw, ask again and be let in", async ({
|
||||
page,
|
||||
spaUser,
|
||||
registerUser,
|
||||
}) => {
|
||||
test.slow();
|
||||
const moderator = await registerUser("moderator");
|
||||
const roomId = await createCallRoom({
|
||||
accessToken: moderator.accessToken,
|
||||
name: "Knock call",
|
||||
joinRule: "knock",
|
||||
});
|
||||
const membership = membershipPoll(moderator, roomId, spaUser);
|
||||
|
||||
await page.goto(callUrl(roomId));
|
||||
|
||||
const joinButton = page.getByTestId("lobby_joinCall");
|
||||
await expect(joinButton).toHaveText("Request to join call");
|
||||
await expect(joinButton).toBeEnabled();
|
||||
await expect(page.locator("video")).toBeVisible();
|
||||
|
||||
await joinButton.click();
|
||||
|
||||
await expect(joinButton).toHaveText("Request to join sent");
|
||||
await expect(joinButton).toBeDisabled();
|
||||
await expect(page.getByTestId("lobby_joinMessage")).toContainText(
|
||||
"You will receive an invite to join the call if your request is accepted.",
|
||||
);
|
||||
await expect.poll(membership).toBe("knock");
|
||||
|
||||
await page.getByTestId("lobby_cancelRequest").click();
|
||||
|
||||
await expect(joinButton).toHaveText("Request to join call");
|
||||
await expect(joinButton).toBeEnabled();
|
||||
await expect(page.getByTestId("lobby_joinMessage")).toBeHidden();
|
||||
await expect.poll(membership).toBe("leave");
|
||||
|
||||
await joinButton.click();
|
||||
|
||||
await expect(joinButton).toHaveText("Request to join sent");
|
||||
await expect.poll(membership).toBe("knock");
|
||||
|
||||
await inviteUser(moderator.accessToken, roomId, spaUser.mxId);
|
||||
|
||||
// The accepted request joins the room and skips a second lobby.
|
||||
await expect(page.getByTestId("incall_leave")).toBeVisible({
|
||||
timeout: 60_000,
|
||||
});
|
||||
await expect(joinButton).toBeHidden();
|
||||
});
|
||||
|
||||
test("A declined request is shown in the lobby", async ({
|
||||
page,
|
||||
spaUser,
|
||||
registerUser,
|
||||
}) => {
|
||||
const moderator = await registerUser("moderator");
|
||||
const roomId = await createCallRoom({
|
||||
accessToken: moderator.accessToken,
|
||||
name: "Declining call",
|
||||
joinRule: "knock",
|
||||
});
|
||||
|
||||
await page.goto(callUrl(roomId));
|
||||
await page.getByTestId("lobby_joinCall").click();
|
||||
await expect.poll(membershipPoll(moderator, roomId, spaUser)).toBe("knock");
|
||||
|
||||
await kickUser(moderator.accessToken, roomId, spaUser.mxId);
|
||||
|
||||
const message = page.getByTestId("lobby_joinMessage");
|
||||
await expect(message).toContainText("Access denied");
|
||||
await expect(message).toContainText("Your request to join was declined.");
|
||||
await expect(page.getByTestId("lobby_joinCall")).toBeHidden();
|
||||
// The camera preview outlives the refusal.
|
||||
await expect(page.locator("video")).toBeVisible();
|
||||
});
|
||||
|
||||
test("A ban is shown in the lobby, with its reason", async ({
|
||||
page,
|
||||
spaUser,
|
||||
registerUser,
|
||||
}) => {
|
||||
const moderator = await registerUser("moderator");
|
||||
const roomId = await createCallRoom({
|
||||
accessToken: moderator.accessToken,
|
||||
name: "Banning call",
|
||||
joinRule: "knock",
|
||||
});
|
||||
|
||||
await page.goto(callUrl(roomId));
|
||||
await page.getByTestId("lobby_joinCall").click();
|
||||
await expect.poll(membershipPoll(moderator, roomId, spaUser)).toBe("knock");
|
||||
|
||||
await banUser(moderator.accessToken, roomId, spaUser.mxId, "Wrong call");
|
||||
|
||||
const message = page.getByTestId("lobby_joinMessage");
|
||||
await expect(message).toContainText("Banned");
|
||||
await expect(message).toContainText("You have been banned from the room.");
|
||||
await expect(message).toContainText("Reason: Wrong call");
|
||||
await expect(page.getByTestId("lobby_joinCall")).toBeHidden();
|
||||
});
|
||||
|
||||
test("A call that takes no requests says so in the lobby", async ({
|
||||
page,
|
||||
spaUser,
|
||||
registerUser,
|
||||
}) => {
|
||||
const moderator = await registerUser("moderator");
|
||||
// Synapse serves an MSC3266 summary for knock and knock_restricted rooms
|
||||
// only, so a `knock_restricted` room, whose rule Element Call cannot act on
|
||||
// without knowing the user's other rooms, is the one shape that reaches the
|
||||
// lobby with nothing to offer.
|
||||
const roomId = await createCallRoom({
|
||||
accessToken: moderator.accessToken,
|
||||
name: "Space call",
|
||||
joinRule: "knock_restricted",
|
||||
});
|
||||
|
||||
await page.goto(callUrl(roomId));
|
||||
|
||||
await expect(page.getByTestId("lobby_joinMessage")).toContainText(
|
||||
"You need an invite to join this call.",
|
||||
);
|
||||
await expect(page.getByTestId("lobby_joinCall")).toBeHidden();
|
||||
await expect(page.locator("video")).toBeVisible();
|
||||
});
|
||||
|
||||
test("An invite-only call cannot be reached at all", async ({
|
||||
page,
|
||||
spaUser,
|
||||
registerUser,
|
||||
}) => {
|
||||
const moderator = await registerUser("moderator");
|
||||
const roomId = await createCallRoom({
|
||||
accessToken: moderator.accessToken,
|
||||
name: "Private call",
|
||||
joinRule: "invite",
|
||||
});
|
||||
|
||||
await page.goto(callUrl(roomId));
|
||||
|
||||
// Synapse serves no room summary for an invite-only room, so Element Call
|
||||
// falls back to joining as if the room were public and the refusal lands on
|
||||
// the error page rather than in the lobby.
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Something went wrong" }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByTestId("lobby_joinCall")).toBeHidden();
|
||||
});
|
||||
Reference in New Issue
Block a user