103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
/*
|
|||
|
|
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 Locator, test } from "@playwright/test";
|
||
|
|
|
||
|
|
import { createUserAndRoom, expectWithin, startHarness } from "./harness.ts";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Element Call embedded as a React component, driven through the development
|
||
|
|
* harness in `component/dev`.
|
||
|
|
*
|
||
|
|
* What these cover that the widget tests cannot is everything that follows from
|
||
|
|
* sharing a page with a host: whether Element Call stays inside the space it
|
||
|
|
* was given, and whether two of it can exist at once. As a widget, the iframe
|
||
|
|
* guaranteed both.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/** The settings button, whichever of the two the footer is currently showing. */
|
||
|
|
function settingsButton(pane: Locator): Locator {
|
||
|
|
return pane
|
||
|
|
.getByTestId("settings-bottom-left")
|
||
|
|
.or(pane.getByTestId("settings-bottom-center"))
|
||
|
|
.filter({ visible: true })
|
||
|
|
.first();
|
||
|
|
}
|
||
|
|
|
||
|
|
test("holds a call between two components on one page", async ({ page }) => {
|
||
|
|
const { username, roomId } = await createUserAndRoom("twocomponents");
|
||
|
|
const panes = await startHarness(page, username, roomId);
|
||
|
|
|
||
|
|
// Each component shows a lobby of its own, and neither has joined anything
|
||
|
|
// just by being rendered
|
||
|
|
for (const index of [0, 1])
|
||
|
|
await expect(panes.nth(index).getByTestId("lobby_joinCall")).toBeVisible({
|
||
|
|
timeout: 60_000,
|
||
|
|
});
|
||
|
|
|
||
|
|
for (const index of [0, 1])
|
||
|
|
await panes.nth(index).getByTestId("lobby_joinCall").click();
|
||
|
|
|
||
|
|
// Two devices of one account, so each component should see itself and the
|
||
|
|
// other. This is the part that proves two Element Calls in one page are two
|
||
|
|
// calls, and not one shared thing wearing two hats.
|
||
|
|
for (const index of [0, 1])
|
||
|
|
await expect(panes.nth(index).getByTestId("videoTile")).toHaveCount(2, {
|
||
|
|
timeout: 60_000,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test("keeps its modals inside the container it was given", async ({ page }) => {
|
||
|
|
const { username, roomId } = await createUserAndRoom("containment");
|
||
|
|
const panes = await startHarness(page, username, roomId);
|
||
|
|
const pane = panes.first();
|
||
|
|
const container = pane.getByTestId("call-container");
|
||
|
|
|
||
|
|
await pane.getByTestId("lobby_joinCall").click({ timeout: 60_000 });
|
||
|
|
await expect(pane.getByTestId("footer-container")).toBeVisible({
|
||
|
|
timeout: 60_000,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Both of these are positioned `fixed`, and were centred on the window
|
||
|
|
// rather than the container until it was made a containing block. The
|
||
|
|
// settings dialog spilled over the host's interface; the reaction picker sat
|
||
|
|
// at 82vh, which put it below the container entirely and so out of sight.
|
||
|
|
await settingsButton(pane).click();
|
||
|
|
await expectWithin(pane.getByRole("dialog"), container);
|
||
|
|
await pane.getByTestId("modal_close").click();
|
||
|
|
|
||
|
|
await pane.getByRole("button", { name: "Reactions" }).click();
|
||
|
|
await expectWithin(
|
||
|
|
pane.getByRole("dialog", { name: "Pick reaction" }),
|
||
|
|
container,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("tells its host what it is doing", async ({ page }) => {
|
||
|
|
const { username, roomId } = await createUserAndRoom("hostbridge");
|
||
|
|
const panes = await startHarness(page, username, roomId);
|
||
|
|
const pane = panes.first();
|
||
|
|
const log = page.getByTestId("bridge-log");
|
||
|
|
|
||
|
|
// Every component reports to its host through the bridge, whether that host
|
||
|
|
// is a widget container or an application embedding it directly
|
||
|
|
await expect(log).toContainText("contentLoaded", { timeout: 60_000 });
|
||
|
|
|
||
|
|
await pane.getByTestId("lobby_joinCall").click({ timeout: 60_000 });
|
||
|
|
await expect(log).toContainText("notifyJoined", { timeout: 60_000 });
|
||
|
|
await expect(log).toContainText("setAlwaysOnScreen(true)", {
|
||
|
|
timeout: 60_000,
|
||
|
|
});
|
||
|
|
|
||
|
|
// And takes instructions back: the host asking for a mute should come back
|
||
|
|
// as the component reporting the new state
|
||
|
|
await pane.getByRole("button", { name: "Mute" }).click();
|
||
|
|
await expect(log).toContainText("notifyDeviceMute(audio: false", {
|
||
|
|
timeout: 30_000,
|
||
|
|
});
|
||
|
|
});
|