Skip to content

Commit 9acc1d1

Browse files
authored
Merge pull request #3123 from element-hq/valere/playwright_widget_tests
Test: End to end integrated test for ElementCall in widget mode
2 parents 622d91d + 0fbde40 commit 9acc1d1

File tree

8 files changed

+387
-1
lines changed

8 files changed

+387
-1
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ yarn-error.log
2626
/test-results/
2727
/playwright-report/
2828
/blob-report/
29-
/playwright/.cache/
29+
/playwright/.cache/

WIDGET_TEST.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Testing Element-Call in widget mode
2+
3+
When running `yarn backend` the latest element-web develop will be deployed and served on `http://localhost:8081`.
4+
In a development environment, you might prefer to just use the `element-web` repo directly, but this setup is useful for CI/CD testing.
5+
6+
## Setup
7+
8+
The element-web configuration is modified to:
9+
10+
- Enable to use the local widget instance (`element_call.url` https://localhost:3000).
11+
- Enable the labs features (`feature_group_calls`, `feature_element_call_video_rooms`).
12+
13+
The default configuration used by docker-compose is in `test-container/config.json`. There is a fixture for playwright
14+
that uses
15+
16+
## Running the element-web instance
17+
18+
It is part of the existing backend setup. To start the backend, run:
19+
20+
```sh
21+
yarn backend
22+
```
23+
24+
Then open `http://localhost:8081` in your browser.
25+
26+
## Basic fixture
27+
28+
A base fixture is provided in `/playwright/fixtures/widget-user.ts` that will register two users that shares a room.

backend/ew.test.config.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"default_server_config": {
3+
"m.homeserver": {
4+
"base_url": "http://synapse.localhost:8008",
5+
"server_name": "synapse.localhost"
6+
}
7+
},
8+
"disable_custom_urls": false,
9+
"disable_guests": false,
10+
"disable_login_language_selector": false,
11+
"disable_3pid_login": false,
12+
"force_verification": false,
13+
"brand": "Element",
14+
"integrations_ui_url": "https://scalar.vector.im/",
15+
"integrations_rest_url": "https://scalar.vector.im/api",
16+
"integrations_widgets_urls": [
17+
"https://scalar.vector.im/_matrix/integrations/v1",
18+
"https://scalar.vector.im/api",
19+
"https://scalar-staging.vector.im/_matrix/integrations/v1",
20+
"https://scalar-staging.vector.im/api",
21+
"https://scalar-staging.riot.im/scalar/api"
22+
],
23+
"default_widget_container_height": 280,
24+
"default_country_code": "GB",
25+
"show_labs_settings": false,
26+
"features": {
27+
"feature_element_call_video_rooms": true,
28+
"feature_video_rooms": true,
29+
"feature_group_calls": true,
30+
"feature_release_announcement": false
31+
},
32+
"default_federate": true,
33+
"default_theme": "light",
34+
"room_directory": {
35+
"servers": ["matrix.org"]
36+
},
37+
"enable_presence_by_hs_url": {
38+
"https://matrix.org": false,
39+
"https://matrix-client.matrix.org": false
40+
},
41+
"setting_defaults": {
42+
"breadcrumbs": true,
43+
"feature_group_calls": true
44+
},
45+
"jitsi": {
46+
"preferred_domain": "meet.element.io"
47+
},
48+
"element_call": {
49+
"url": "https://localhost:3000",
50+
"participant_limit": 8,
51+
"brand": "Element Call"
52+
},
53+
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
54+
}

dev-backend-docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ services:
6868
networks:
6969
- ecbackend
7070

71+
element-web:
72+
image: ghcr.io/element-hq/element-web:develop
73+
volumes:
74+
- ./backend/ew.test.config.json:/app/config.json
75+
environment:
76+
ELEMENT_WEB_PORT: 81
77+
ports:
78+
- "8081:81"
79+
networks:
80+
- ecbackend
81+
7182
nginx:
7283
# 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"
7384
hostname: synapse.localhost

playwright-backend-docker-compose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ services:
5151
networks:
5252
- ecbackend
5353

54+
element-web:
55+
image: ghcr.io/element-hq/element-web:develop
56+
volumes:
57+
- ./backend/ew.test.config.json:/app/config.json
58+
environment:
59+
ELEMENT_WEB_PORT: 81
60+
ports:
61+
- "8081:81"
62+
networks:
63+
- ecbackend
64+
5465
synapse:
5566
hostname: homeserver
5667
image: docker.io/matrixdotorg/synapse:latest

playwright/fixtures/widget-user.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
Copyright 2025 New Vector Ltd.
3+
4+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
import { type Page, test, expect, type JSHandle } from "@playwright/test";
9+
10+
import type { MatrixClient } from "matrix-js-sdk/src";
11+
12+
export type UserBaseFixture = {
13+
mxId: string;
14+
page: Page;
15+
clientHandle: JSHandle<MatrixClient>;
16+
};
17+
18+
export type BaseWidgetSetup = {
19+
brooks: UserBaseFixture;
20+
whistler: UserBaseFixture;
21+
};
22+
23+
export interface MyFixtures {
24+
asWidget: BaseWidgetSetup;
25+
}
26+
27+
const PASSWORD = "foobarbaz1!";
28+
29+
// Minimal config.json for the local element-web instance
30+
const CONFIG_JSON = {
31+
default_server_config: {
32+
"m.homeserver": {
33+
base_url: "http://synapse.localhost:8008",
34+
server_name: "synapse.localhost",
35+
},
36+
},
37+
38+
element_call: {
39+
url: "https://localhost:3000",
40+
participant_limit: 8,
41+
brand: "Element Call",
42+
},
43+
44+
// The default language is set here for test consistency
45+
setting_defaults: {
46+
language: "en-GB",
47+
feature_group_calls: true,
48+
},
49+
50+
// the location tests want a map style url.
51+
map_style_url:
52+
"https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
53+
54+
features: {
55+
// We don't want to go through the feature announcement during the e2e test
56+
feature_release_announcement: false,
57+
feature_element_call_video_rooms: true,
58+
feature_video_rooms: true,
59+
feature_group_calls: true,
60+
},
61+
};
62+
63+
export const widgetTest = test.extend<MyFixtures>({
64+
asWidget: async ({ browser, context }, pUse) => {
65+
await context.route(`http://localhost:8081/config.json*`, async (route) => {
66+
await route.fulfill({ json: CONFIG_JSON });
67+
});
68+
69+
const userA = `brooks_${Date.now()}`;
70+
const userB = `whistler_${Date.now()}`;
71+
72+
const user1Context = await browser.newContext({
73+
reducedMotion: "reduce",
74+
});
75+
const ewPage1 = await user1Context.newPage();
76+
// Register the first user
77+
await ewPage1.goto("http://localhost:8081/#/welcome");
78+
await ewPage1.getByRole("link", { name: "Create Account" }).click();
79+
await ewPage1.getByRole("textbox", { name: "Username" }).fill(userA);
80+
await ewPage1
81+
.getByRole("textbox", { name: "Password", exact: true })
82+
.fill(PASSWORD);
83+
await ewPage1.getByRole("textbox", { name: "Confirm password" }).click();
84+
await ewPage1
85+
.getByRole("textbox", { name: "Confirm password" })
86+
.fill(PASSWORD);
87+
await ewPage1.getByRole("button", { name: "Register" }).click();
88+
await expect(
89+
ewPage1.getByRole("heading", { name: `Welcome ${userA}` }),
90+
).toBeVisible();
91+
92+
const brooksClientHandle = await ewPage1.evaluateHandle(() =>
93+
window.mxMatrixClientPeg.get(),
94+
);
95+
const brooksMxId = (await brooksClientHandle.evaluate((cli) => {
96+
return cli.getUserId();
97+
}, brooksClientHandle))!;
98+
99+
const user2Context = await browser.newContext({
100+
reducedMotion: "reduce",
101+
});
102+
const ewPage2 = await user2Context.newPage();
103+
// Register the second user
104+
await ewPage2.goto("http://localhost:8081/#/welcome");
105+
await ewPage2.getByRole("link", { name: "Create Account" }).click();
106+
await ewPage2.getByRole("textbox", { name: "Username" }).fill(userB);
107+
await ewPage2
108+
.getByRole("textbox", { name: "Password", exact: true })
109+
.fill(PASSWORD);
110+
await ewPage2.getByRole("textbox", { name: "Confirm password" }).click();
111+
await ewPage2
112+
.getByRole("textbox", { name: "Confirm password" })
113+
.fill(PASSWORD);
114+
await ewPage2.getByRole("button", { name: "Register" }).click();
115+
await expect(
116+
ewPage2.getByRole("heading", { name: `Welcome ${userB}` }),
117+
).toBeVisible();
118+
119+
const whistlerClientHandle = await ewPage2.evaluateHandle(() =>
120+
window.mxMatrixClientPeg.get(),
121+
);
122+
const whistlerMxId = (await whistlerClientHandle.evaluate((cli) => {
123+
return cli.getUserId();
124+
}, whistlerClientHandle))!;
125+
126+
// Invite the second user
127+
await ewPage1.getByRole("button", { name: "Add room" }).click();
128+
await ewPage1.getByText("New room").click();
129+
await ewPage1.getByRole("textbox", { name: "Name" }).fill("Welcome Room");
130+
await ewPage1.getByRole("button", { name: "Create room" }).click();
131+
await expect(ewPage1.getByText("You created this room.")).toBeVisible();
132+
await expect(ewPage1.getByText("Encryption enabled")).toBeVisible();
133+
134+
await ewPage1
135+
.getByRole("button", { name: "Invite to this room", exact: true })
136+
.click();
137+
await expect(
138+
ewPage1.getByRole("heading", { name: "Invite to Welcome Room" }),
139+
).toBeVisible();
140+
141+
await ewPage1.getByRole("textbox").fill(whistlerMxId);
142+
await ewPage1.getByRole("textbox").click();
143+
await ewPage1.getByRole("button", { name: "Invite" }).click();
144+
145+
// Accept the invite
146+
await expect(
147+
ewPage2.getByRole("treeitem", { name: "Welcome Room" }),
148+
).toBeVisible();
149+
await ewPage2.getByRole("treeitem", { name: "Welcome Room" }).click();
150+
await ewPage2.getByRole("button", { name: "Accept" }).click();
151+
await expect(
152+
ewPage2.getByRole("main").getByRole("heading", { name: "Welcome Room" }),
153+
).toBeVisible();
154+
155+
// Renamed use to pUse, as a workaround for eslint error that was thinking this use was a react use.
156+
await pUse({
157+
brooks: {
158+
mxId: brooksMxId,
159+
page: ewPage1,
160+
clientHandle: brooksClientHandle,
161+
},
162+
whistler: {
163+
mxId: whistlerMxId,
164+
page: ewPage2,
165+
clientHandle: whistlerClientHandle,
166+
},
167+
});
168+
},
169+
});

playwright/global.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
Copyright 2025 New Vector Ltd.
3+
4+
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
import type * as Matrix from "matrix-js-sdk/src";
9+
10+
declare global {
11+
interface Window {
12+
mxMatrixClientPeg: {
13+
get(): Matrix.MatrixClient;
14+
};
15+
}
16+
}

0 commit comments

Comments
 (0)