mirror of
https://github.com/vector-im/element-call.git
synced 2026-01-18 02:32:27 +00:00
test: End to end integrated test for call widget
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -15,4 +15,4 @@ yarn-error.log
|
|||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
28
WIDGET_TEST.md
Normal file
28
WIDGET_TEST.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Testing Element-Call in widget mode
|
||||||
|
|
||||||
|
When running `yarn backend` the latest element-web develop will be deployed and served on `http://localhost:8081`.
|
||||||
|
In a development environment, you might prefer to just use the `element-web` repo directly, but this setup is useful for CI/CD testing.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
The element-web configuration is modified to:
|
||||||
|
|
||||||
|
- Enable to use the local widget instance (`element_call.url` https://localhost:3000).
|
||||||
|
- Enable the labs features (`feature_group_calls`, `feature_element_call_video_rooms`).
|
||||||
|
|
||||||
|
The default configuration used by docker-compose is in `test-container/config.json`. There is a fixture for playwright
|
||||||
|
that uses
|
||||||
|
|
||||||
|
## Running the element-web instance
|
||||||
|
|
||||||
|
It is part of the existing backend setup. To start the backend, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn backend
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open `http://localhost:8081` in your browser.
|
||||||
|
|
||||||
|
## Basic fixture
|
||||||
|
|
||||||
|
A base fixture is provided in `/playwright/fixtures/widget-user.ts` that will register two users that shares a room.
|
||||||
54
backend/ew.test.config.json
Normal file
54
backend/ew.test.config.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"default_server_config": {
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "http://synapse.localhost:8008",
|
||||||
|
"server_name": "synapse.localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable_custom_urls": false,
|
||||||
|
"disable_guests": false,
|
||||||
|
"disable_login_language_selector": false,
|
||||||
|
"disable_3pid_login": false,
|
||||||
|
"force_verification": false,
|
||||||
|
"brand": "Element",
|
||||||
|
"integrations_ui_url": "https://scalar.vector.im/",
|
||||||
|
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||||
|
"integrations_widgets_urls": [
|
||||||
|
"https://scalar.vector.im/_matrix/integrations/v1",
|
||||||
|
"https://scalar.vector.im/api",
|
||||||
|
"https://scalar-staging.vector.im/_matrix/integrations/v1",
|
||||||
|
"https://scalar-staging.vector.im/api",
|
||||||
|
"https://scalar-staging.riot.im/scalar/api"
|
||||||
|
],
|
||||||
|
"default_widget_container_height": 280,
|
||||||
|
"default_country_code": "GB",
|
||||||
|
"show_labs_settings": false,
|
||||||
|
"features": {
|
||||||
|
"feature_element_call_video_rooms": true,
|
||||||
|
"feature_video_rooms": true,
|
||||||
|
"feature_group_calls": true,
|
||||||
|
"feature_release_announcement": false
|
||||||
|
},
|
||||||
|
"default_federate": true,
|
||||||
|
"default_theme": "light",
|
||||||
|
"room_directory": {
|
||||||
|
"servers": ["matrix.org"]
|
||||||
|
},
|
||||||
|
"enable_presence_by_hs_url": {
|
||||||
|
"https://matrix.org": false,
|
||||||
|
"https://matrix-client.matrix.org": false
|
||||||
|
},
|
||||||
|
"setting_defaults": {
|
||||||
|
"breadcrumbs": true,
|
||||||
|
"feature_group_calls": true
|
||||||
|
},
|
||||||
|
"jitsi": {
|
||||||
|
"preferred_domain": "meet.element.io"
|
||||||
|
},
|
||||||
|
"element_call": {
|
||||||
|
"url": "https://localhost:3000",
|
||||||
|
"participant_limit": 8,
|
||||||
|
"brand": "Element Call"
|
||||||
|
},
|
||||||
|
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||||
|
}
|
||||||
@@ -68,6 +68,17 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- ecbackend
|
- ecbackend
|
||||||
|
|
||||||
|
element-web:
|
||||||
|
image: ghcr.io/element-hq/element-web:develop
|
||||||
|
volumes:
|
||||||
|
- ./backend/ew.test.config.json:/app/config.json
|
||||||
|
environment:
|
||||||
|
ELEMENT_WEB_PORT: 81
|
||||||
|
ports:
|
||||||
|
- "8081:81"
|
||||||
|
networks:
|
||||||
|
- ecbackend
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls_localhost_key.pem -out tls_localhost_cert.pem -subj "/C=GB/ST=London/L=London/O=Alros/OU=IT Department/CN=localhost"
|
# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls_localhost_key.pem -out tls_localhost_cert.pem -subj "/C=GB/ST=London/L=London/O=Alros/OU=IT Department/CN=localhost"
|
||||||
hostname: synapse.localhost
|
hostname: synapse.localhost
|
||||||
|
|||||||
@@ -51,6 +51,17 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- ecbackend
|
- ecbackend
|
||||||
|
|
||||||
|
element-web:
|
||||||
|
image: ghcr.io/element-hq/element-web:develop
|
||||||
|
volumes:
|
||||||
|
- ./backend/ew.test.config.json:/app/config.json
|
||||||
|
environment:
|
||||||
|
ELEMENT_WEB_PORT: 81
|
||||||
|
ports:
|
||||||
|
- "8081:81"
|
||||||
|
networks:
|
||||||
|
- ecbackend
|
||||||
|
|
||||||
synapse:
|
synapse:
|
||||||
hostname: homeserver
|
hostname: homeserver
|
||||||
image: docker.io/matrixdotorg/synapse:latest
|
image: docker.io/matrixdotorg/synapse:latest
|
||||||
|
|||||||
169
playwright/fixtures/widget-user.ts
Normal file
169
playwright/fixtures/widget-user.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
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/src";
|
||||||
|
|
||||||
|
export type UserBaseFixture = {
|
||||||
|
mxId: string;
|
||||||
|
page: Page;
|
||||||
|
clientHandle: JSHandle<MatrixClient>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BaseWidgetSetup = {
|
||||||
|
brooks: UserBaseFixture;
|
||||||
|
whistler: UserBaseFixture;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface MyFixtures {
|
||||||
|
asWidget: BaseWidgetSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PASSWORD = "foobarbaz1!";
|
||||||
|
|
||||||
|
// Minimal config.json for the local element-web instance
|
||||||
|
const CONFIG_JSON = {
|
||||||
|
default_server_config: {
|
||||||
|
"m.homeserver": {
|
||||||
|
base_url: "http://synapse.localhost:8008",
|
||||||
|
server_name: "synapse.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
element_call: {
|
||||||
|
url: "https://localhost:3000",
|
||||||
|
participant_limit: 8,
|
||||||
|
brand: "Element Call",
|
||||||
|
},
|
||||||
|
|
||||||
|
// The default language is set here for test consistency
|
||||||
|
setting_defaults: {
|
||||||
|
language: "en-GB",
|
||||||
|
feature_group_calls: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// the location tests want a map style url.
|
||||||
|
map_style_url:
|
||||||
|
"https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
|
||||||
|
|
||||||
|
features: {
|
||||||
|
// We don't want to go through the feature announcement during the e2e test
|
||||||
|
feature_release_announcement: false,
|
||||||
|
feature_element_call_video_rooms: true,
|
||||||
|
feature_video_rooms: true,
|
||||||
|
feature_group_calls: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const widgetTest = test.extend<MyFixtures>({
|
||||||
|
asWidget: async ({ browser, context }, pUse) => {
|
||||||
|
await context.route(`http://localhost:8081/config.json*`, async (route) => {
|
||||||
|
await route.fulfill({ json: CONFIG_JSON });
|
||||||
|
});
|
||||||
|
|
||||||
|
const userA = `brooks_${Date.now()}`;
|
||||||
|
const userB = `whistler_${Date.now()}`;
|
||||||
|
|
||||||
|
const user1Context = await browser.newContext({
|
||||||
|
reducedMotion: "reduce",
|
||||||
|
});
|
||||||
|
const ewPage1 = await user1Context.newPage();
|
||||||
|
// Register the first user
|
||||||
|
await ewPage1.goto("http://localhost:8081/#/welcome");
|
||||||
|
await ewPage1.getByRole("link", { name: "Create Account" }).click();
|
||||||
|
await ewPage1.getByRole("textbox", { name: "Username" }).fill(userA);
|
||||||
|
await ewPage1
|
||||||
|
.getByRole("textbox", { name: "Password", exact: true })
|
||||||
|
.fill(PASSWORD);
|
||||||
|
await ewPage1.getByRole("textbox", { name: "Confirm password" }).click();
|
||||||
|
await ewPage1
|
||||||
|
.getByRole("textbox", { name: "Confirm password" })
|
||||||
|
.fill(PASSWORD);
|
||||||
|
await ewPage1.getByRole("button", { name: "Register" }).click();
|
||||||
|
await expect(
|
||||||
|
ewPage1.getByRole("heading", { name: `Welcome ${userA}` }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
const brooksClientHandle = await ewPage1.evaluateHandle(() =>
|
||||||
|
window.mxMatrixClientPeg.get(),
|
||||||
|
);
|
||||||
|
const brooksMxId = (await brooksClientHandle.evaluate((cli) => {
|
||||||
|
return cli.getUserId();
|
||||||
|
}, brooksClientHandle))!;
|
||||||
|
|
||||||
|
const user2Context = await browser.newContext({
|
||||||
|
reducedMotion: "reduce",
|
||||||
|
});
|
||||||
|
const ewPage2 = await user2Context.newPage();
|
||||||
|
// Register the second user
|
||||||
|
await ewPage2.goto("http://localhost:8081/#/welcome");
|
||||||
|
await ewPage2.getByRole("link", { name: "Create Account" }).click();
|
||||||
|
await ewPage2.getByRole("textbox", { name: "Username" }).fill(userB);
|
||||||
|
await ewPage2
|
||||||
|
.getByRole("textbox", { name: "Password", exact: true })
|
||||||
|
.fill(PASSWORD);
|
||||||
|
await ewPage2.getByRole("textbox", { name: "Confirm password" }).click();
|
||||||
|
await ewPage2
|
||||||
|
.getByRole("textbox", { name: "Confirm password" })
|
||||||
|
.fill(PASSWORD);
|
||||||
|
await ewPage2.getByRole("button", { name: "Register" }).click();
|
||||||
|
await expect(
|
||||||
|
ewPage2.getByRole("heading", { name: `Welcome ${userB}` }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
const whistlerClientHandle = await ewPage2.evaluateHandle(() =>
|
||||||
|
window.mxMatrixClientPeg.get(),
|
||||||
|
);
|
||||||
|
const whistlerMxId = (await whistlerClientHandle.evaluate((cli) => {
|
||||||
|
return cli.getUserId();
|
||||||
|
}, whistlerClientHandle))!;
|
||||||
|
|
||||||
|
// Invite the second user
|
||||||
|
await ewPage1.getByRole("button", { name: "Add room" }).click();
|
||||||
|
await ewPage1.getByText("New room").click();
|
||||||
|
await ewPage1.getByRole("textbox", { name: "Name" }).fill("Welcome Room");
|
||||||
|
await ewPage1.getByRole("button", { name: "Create room" }).click();
|
||||||
|
await expect(ewPage1.getByText("You created this room.")).toBeVisible();
|
||||||
|
await expect(ewPage1.getByText("Encryption enabled")).toBeVisible();
|
||||||
|
|
||||||
|
await ewPage1
|
||||||
|
.getByRole("button", { name: "Invite to this room", exact: true })
|
||||||
|
.click();
|
||||||
|
await expect(
|
||||||
|
ewPage1.getByRole("heading", { name: "Invite to Welcome Room" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await ewPage1.getByRole("textbox").fill(whistlerMxId);
|
||||||
|
await ewPage1.getByRole("textbox").click();
|
||||||
|
await ewPage1.getByRole("button", { name: "Invite" }).click();
|
||||||
|
|
||||||
|
// Accept the invite
|
||||||
|
await expect(
|
||||||
|
ewPage2.getByRole("treeitem", { name: "Welcome Room" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await ewPage2.getByRole("treeitem", { name: "Welcome Room" }).click();
|
||||||
|
await ewPage2.getByRole("button", { name: "Accept" }).click();
|
||||||
|
await expect(
|
||||||
|
ewPage2.getByRole("main").getByRole("heading", { name: "Welcome Room" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Renamed use to pUse, as a workaround for eslint error that was thinking this use was a react use.
|
||||||
|
await pUse({
|
||||||
|
brooks: {
|
||||||
|
mxId: brooksMxId,
|
||||||
|
page: ewPage1,
|
||||||
|
clientHandle: brooksClientHandle,
|
||||||
|
},
|
||||||
|
whistler: {
|
||||||
|
mxId: whistlerMxId,
|
||||||
|
page: ewPage2,
|
||||||
|
clientHandle: whistlerClientHandle,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
16
playwright/global.d.ts
vendored
Normal file
16
playwright/global.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as Matrix from "matrix-js-sdk/src";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
mxMatrixClientPeg: {
|
||||||
|
get(): Matrix.MatrixClient;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
97
playwright/widget/simple-create.spec.ts
Normal file
97
playwright/widget/simple-create.spec.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector 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.ts";
|
||||||
|
|
||||||
|
widgetTest("Start a new call as widget", async ({ asWidget, browserName }) => {
|
||||||
|
test.skip(
|
||||||
|
browserName === "firefox",
|
||||||
|
"This test is not working on firefox, after hangup brooks is locked in a strange state with a blank widget",
|
||||||
|
);
|
||||||
|
|
||||||
|
const { brooks, whistler } = asWidget;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
brooks.page.getByRole("button", { name: "Video call" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await brooks.page.getByRole("button", { name: "Video call" }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
brooks.page.getByRole("menuitem", { name: "Legacy Call" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
brooks.page.getByRole("menuitem", { name: "Element Call" }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await brooks.page.getByRole("menuitem", { name: "Element Call" }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
brooks.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("lobby_joinCall"),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await brooks.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("lobby_joinCall")
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Check the join indicator on the room list
|
||||||
|
await expect(
|
||||||
|
brooks.page.locator("div").filter({ hasText: /^Joined • 1$/ }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Join from the other side
|
||||||
|
await expect(whistler.page.getByText("Video call started")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
whistler.page.getByRole("button", { name: "Join" }),
|
||||||
|
).toBeVisible();
|
||||||
|
await whistler.page.getByRole("button", { name: "Join" }).click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
whistler.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("lobby_joinCall"),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await whistler.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("lobby_joinCall")
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
whistler.page.locator("div").filter({ hasText: /^Joined • 2$/ }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
brooks.page.locator("div").filter({ hasText: /^Joined • 2$/ }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Whistler leaves
|
||||||
|
await whistler.page.waitForTimeout(1000);
|
||||||
|
await whistler.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("incall_leave")
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Brooks leaves
|
||||||
|
await brooks.page
|
||||||
|
.locator('iframe[title="Element Call"]')
|
||||||
|
.contentFrame()
|
||||||
|
.getByTestId("incall_leave")
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(whistler.page.locator(".mx_BasicMessageComposer")).toBeVisible();
|
||||||
|
await expect(brooks.page.locator(".mx_BasicMessageComposer")).toBeVisible();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user