Skip to content

Commit 65c32e4

Browse files
committed
Provice toasts utility functions, soon to replace the toasts fixture.
Just because it seems less magic and still convenient.
1 parent 35b9b12 commit 65c32e4

6 files changed

Lines changed: 145 additions & 39 deletions

File tree

apps/web/playwright/e2e/crypto/device-verification.spec.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
77
*/
88

99
import jsQR from "jsqr";
10+
import { assertNoToasts, getToast, rejectToast } from "@element-hq/element-web-playwright-common/src/utils/toasts";
1011

1112
import type { JSHandle, Locator, Page } from "@playwright/test";
1213
import type { VerificationRequest } from "matrix-js-sdk/src/crypto-api";
@@ -81,11 +82,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
8182
);
8283

8384
// Regression test for https://github.com/element-hq/element-web/issues/29110
84-
test("No toast after verification, even if the secrets take a while to arrive", async ({
85-
page,
86-
credentials,
87-
toasts,
88-
}) => {
85+
test("No toast after verification, even if the secrets take a while to arrive", async ({ page, credentials }) => {
8986
// Before we log in, the bot creates an encrypted room, so that we can test the toast behaviour that only happens
9087
// when we are in an encrypted room.
9188
await aliceBotClient.createRoom({
@@ -124,8 +121,8 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
124121
await infoDialog.getByRole("button", { name: "Got it" }).click();
125122

126123
// There should be no toast (other than the notifications one)
127-
await toasts.rejectToast("Notifications");
128-
await toasts.assertNoToasts();
124+
await rejectToast(page, "Notifications");
125+
await assertNoToasts(page);
129126

130127
// There may still be a `/sendToDevice/m.secret.request` in flight, which will later throw an error and cause
131128
// a *subsequent* test to fail. Tell playwright to ignore any errors resulting from in-flight routes.
@@ -272,7 +269,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
272269
await checkDeviceIsConnectedKeyBackup(app, expectedBackupVersion, true);
273270
}
274271

275-
test("Handle incoming verification request with SAS", async ({ page, credentials, homeserver, toasts, app }) => {
272+
test("Handle incoming verification request with SAS", async ({ page, credentials, homeserver, app }) => {
276273
/* Log in but don't verify the device */
277274
await logIntoElement(page, credentials);
278275
const authPage = page.locator(".mx_AuthPage");
@@ -302,7 +299,7 @@ test.describe("Device verification", { tag: "@no-webkit" }, () => {
302299
);
303300

304301
/* Check the toast for the incoming request */
305-
const toast = await toasts.getToast("Verification requested");
302+
const toast = await getToast(page, "Verification requested");
306303
// it should contain the device ID of the requesting device
307304
await expect(toast.getByText(`${aliceBotClient.credentials.deviceId} from `)).toBeVisible();
308305
// Accept

apps/web/playwright/e2e/crypto/toasts.spec.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import { type GeneratedSecretStorageKey } from "matrix-js-sdk/src/crypto-api";
9+
import { assertNoToasts, getToast, rejectToast } from "@element-hq/element-web-playwright-common/src/utils/toasts";
910

1011
import { test, expect } from "../../element-web-test";
1112
import { createBot, deleteCachedSecrets, disableKeyBackup, logIntoElement, logIntoElementAndVerify } from "./utils";
@@ -72,7 +73,7 @@ test.describe("Key storage out of sync toast", () => {
7273
test.describe("'Turn on key storage' toast", () => {
7374
let botClient: Bot | undefined;
7475

75-
test.beforeEach(async ({ page, homeserver, credentials, toasts }) => {
76+
test.beforeEach(async ({ page, homeserver, credentials }) => {
7677
// Set up all crypto stuff. Key storage defaults to on.
7778

7879
const res = await createBot(page, homeserver, credentials);
@@ -90,13 +91,13 @@ test.describe("'Turn on key storage' toast", () => {
9091
await page.getByRole("textbox", { name: "Name" }).fill("Test room");
9192
await page.getByRole("button", { name: "Create room" }).click();
9293

93-
await toasts.rejectToast("Notifications");
94+
await rejectToast(page, "Notifications");
9495
});
9596

96-
test("should not show toast if key storage is on", async ({ page, toasts }) => {
97+
test("should not show toast if key storage is on", async ({ page }) => {
9798
// Given the default situation after signing in
9899
// Then no toast is shown (because key storage is on)
99-
await toasts.assertNoToasts();
100+
await assertNoToasts(page);
100101

101102
// When we reload
102103
await page.reload();
@@ -105,15 +106,15 @@ test.describe("'Turn on key storage' toast", () => {
105106
await new Promise((resolve) => setTimeout(resolve, 2000));
106107

107108
// Then still no toast is shown
108-
await toasts.assertNoToasts();
109+
await assertNoToasts(page);
109110
});
110111

111-
test("should not show toast if key storage is off because we turned it off", async ({ app, page, toasts }) => {
112+
test("should not show toast if key storage is off because we turned it off", async ({ app, page }) => {
112113
// Given the backup is disabled because we disabled it
113114
await disableKeyBackup(app);
114115

115116
// Then no toast is shown
116-
await toasts.assertNoToasts();
117+
await assertNoToasts(page);
117118

118119
// When we reload
119120
await page.reload();
@@ -122,10 +123,10 @@ test.describe("'Turn on key storage' toast", () => {
122123
await new Promise((resolve) => setTimeout(resolve, 2000));
123124

124125
// Then still no toast is shown
125-
await toasts.assertNoToasts();
126+
await assertNoToasts(page);
126127
});
127128

128-
test("should show toast if key storage is off but account data is missing", async ({ app, page, toasts }) => {
129+
test("should show toast if key storage is off but account data is missing", async ({ app, page }) => {
129130
// Given the backup is disabled but we didn't set account data saying that is expected
130131
await disableKeyBackup(app);
131132
await botClient.setAccountData("m.org.matrix.custom.backup_disabled", { disabled: false });
@@ -137,7 +138,7 @@ test.describe("'Turn on key storage' toast", () => {
137138
await page.reload();
138139

139140
// Then the toast is displayed
140-
let toast = await toasts.getToast("Turn on key storage");
141+
let toast = await getToast(page, "Turn on key storage");
141142

142143
// And when we click "Continue"
143144
await toast.getByRole("button", { name: "Continue" }).click();
@@ -149,7 +150,7 @@ test.describe("'Turn on key storage' toast", () => {
149150
await page.getByRole("button", { name: "Close dialog" }).click();
150151

151152
// Then we see the toast again
152-
toast = await toasts.getToast("Turn on key storage");
153+
toast = await getToast(page, "Turn on key storage");
153154

154155
// And when we click "Dismiss"
155156
await toast.getByRole("button", { name: "Dismiss" }).click();
@@ -163,7 +164,7 @@ test.describe("'Turn on key storage' toast", () => {
163164
await page.getByTestId("dialog-background").click({ force: true, position: { x: 10, y: 10 } });
164165

165166
// Then we see the toast again
166-
toast = await toasts.getToast("Turn on key storage");
167+
toast = await getToast(page, "Turn on key storage");
167168

168169
// And when we click Dismiss and then "Go to Settings"
169170
await toast.getByRole("button", { name: "Dismiss" }).click();
@@ -174,12 +175,12 @@ test.describe("'Turn on key storage' toast", () => {
174175

175176
// And when we close that, see the toast, click Dismiss, and Yes, Dismiss
176177
await page.getByRole("button", { name: "Close dialog" }).click();
177-
toast = await toasts.getToast("Turn on key storage");
178+
toast = await getToast(page, "Turn on key storage");
178179
await toast.getByRole("button", { name: "Dismiss" }).click();
179180
await page.getByRole("button", { name: "Yes, dismiss" }).click();
180181

181182
// Then the toast is gone
182-
await toasts.assertNoToasts();
183+
await assertNoToasts(page);
183184
});
184185
});
185186

apps/web/playwright/e2e/room/room-status-bar.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
44
Please see LICENSE files in the repository root for full details.
55
*/
66

7+
import { getToast } from "@element-hq/element-web-playwright-common/src/utils/toasts";
8+
79
import { test, expect } from "../../element-web-test";
810

911
test.describe("Room Status Bar", () => {
@@ -38,7 +40,7 @@ test.describe("Room Status Bar", () => {
3840
await expect(banner).toBeVisible({ timeout: 15000 });
3941
await expect(banner).toMatchScreenshot("connectivity_lost.png");
4042
});
41-
test("should NOT an error when a resource limit is hit", async ({ page, user, app, room, axe, toasts }) => {
43+
test("should NOT an error when a resource limit is hit", async ({ page, user, app, room, axe }) => {
4244
await app.viewRoomById(room.roomId);
4345
await page.route("**/_matrix/client/*/sync*", async (route, req) => {
4446
await route.fulfill({
@@ -54,7 +56,7 @@ test.describe("Room Status Bar", () => {
5456
});
5557
await app.client.sendMessage(room.roomId, "forcing sync to run");
5658
// Wait for the MAU warning toast to appear so we know this status bar would have appeared.
57-
await toasts.getToast("Warning", 15000);
59+
await getToast(page, "Warning", 15000);
5860
await expect(page.getByRole("region", { name: "Room status bar" })).not.toBeVisible();
5961
});
6062
test(

apps/web/playwright/e2e/toasts/analytics-toast.spec.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
66
Please see LICENSE files in the repository root for full details.
77
*/
88

9+
import { acceptToast, assertNoToasts, rejectToast } from "@element-hq/element-web-playwright-common/src/utils/toasts";
10+
911
import { test } from "../../element-web-test";
1012

1113
test.describe("Analytics Toast", () => {
1214
test.use({
1315
displayName: "Tod",
1416
});
1517

16-
test("should not show an analytics toast if config has nothing about posthog", async ({ user, toasts }) => {
17-
await toasts.rejectToast("Notifications");
18-
await toasts.assertNoToasts();
18+
test("should not show an analytics toast if config has nothing about posthog", async ({ user, page }) => {
19+
await rejectToast(page, "Notifications");
20+
await assertNoToasts(page);
1921
});
2022

2123
test.describe("with posthog enabled", () => {
@@ -28,18 +30,18 @@ test.describe("Analytics Toast", () => {
2830
},
2931
});
3032

31-
test.beforeEach(async ({ user, toasts }) => {
32-
await toasts.rejectToast("Notifications");
33+
test.beforeEach(async ({ user, page }) => {
34+
await rejectToast(page, "Notifications");
3335
});
3436

35-
test("should show an analytics toast which can be accepted", async ({ user, toasts }) => {
36-
await toasts.acceptToast("Help improve Element");
37-
await toasts.assertNoToasts();
37+
test("should show an analytics toast which can be accepted", async ({ user, page }) => {
38+
await acceptToast(page, "Help improve Element");
39+
await assertNoToasts(page);
3840
});
3941

40-
test("should show an analytics toast which can be rejected", async ({ user, toasts }) => {
41-
await toasts.rejectToast("Help improve Element");
42-
await toasts.assertNoToasts();
42+
test("should show an analytics toast which can be rejected", async ({ user, page }) => {
43+
await rejectToast(page, "Help improve Element");
44+
await assertNoToasts(page);
4345
});
4446
});
4547
});

apps/web/playwright/e2e/voip/pstn.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
55
Please see LICENSE files in the repository root for full details.
66
*/
77

8+
import {} from "@element-hq/element-web-playwright-common/src/utils/toasts";
9+
810
import { test, expect } from "../../element-web-test";
911

1012
test.describe("PSTN", () => {
@@ -20,9 +22,9 @@ test.describe("PSTN", () => {
2022
});
2123
});
2224

23-
test("should render dialpad as expected", { tag: "@screenshot" }, async ({ page, user, toasts }) => {
24-
await toasts.rejectToast("Notifications");
25-
await toasts.assertNoToasts();
25+
test("should render dialpad as expected", { tag: "@screenshot" }, async ({ page, user }) => {
26+
await rejectToast(page, "Notifications");
27+
await assertNoToasts(page);
2628

2729
await expect(page.getByTestId("room-list-search")).toMatchScreenshot("dialpad-trigger.png");
2830
await page.getByLabel("Open dial pad").click();
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2026 Element Creations Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
import { expect, type Locator, type Page } from "@playwright/test";
9+
10+
/**
11+
* Assert that no toasts exist
12+
*
13+
* @public
14+
* @param page - Playwright page we are working with
15+
*/
16+
export async function assertNoToasts(page: Page): Promise<void> {
17+
await expect(page.locator(".mx_Toast_toast")).not.toBeVisible();
18+
}
19+
20+
/**
21+
* Assert that a toast with the given title exists, and return it
22+
*
23+
* @public
24+
* @param page - Playwright page we are working with
25+
* @param title - Expected title of the toast
26+
* @param timeout - Time to retry the assertion for in milliseconds.
27+
* Defaults to `timeout` in `TestConfig.expect`.
28+
* @returns the Locator for the matching toast
29+
*/
30+
export async function getToast(page: Page, title: string, timeout?: number): Promise<Locator> {
31+
const toast = getToastIfExists(page, title);
32+
await expect(toast).toBeVisible({ timeout });
33+
return toast;
34+
}
35+
36+
/**
37+
* Find a toast with the given title, if it exists.
38+
*
39+
* @public
40+
* @param page - Playwright page we are working with
41+
* @param title - Title of the toast.
42+
* @returns the Locator for the matching toast, or an empty locator if it
43+
* doesn't exist.
44+
*/
45+
export function getToastIfExists(page: Page, title: string): Locator {
46+
return page.locator(".mx_Toast_toast", { hasText: title }).first();
47+
}
48+
49+
/**
50+
* Accept a toast with the given title. Only works for the first toast in
51+
* the stack.
52+
*
53+
* @public
54+
* @param page - Playwright page we are working with
55+
* @param title - Expected title of the toast
56+
*/
57+
export async function acceptToast(page: Page, title: string): Promise<void> {
58+
const toast = await getToast(page, title);
59+
await toast.locator('.mx_Toast_buttons button[data-kind="primary"]').click();
60+
}
61+
/**
62+
* Accept a toast with the given title, if it exists. Only works for the
63+
* first toast in the stack.
64+
*
65+
* @public
66+
* @param page - Playwright page we are working with
67+
* @param title - Title of the toast
68+
*/
69+
export async function acceptToastIfExists(page: Page, title: string): Promise<void> {
70+
const toast = getToastIfExists(page, title).locator('.mx_Toast_buttons button[data-kind="primary"]');
71+
if ((await toast.count()) > 0) {
72+
await toast.click();
73+
}
74+
}
75+
76+
/**
77+
* Reject a toast with the given title. Only works for the first toast in
78+
* the stack.
79+
*
80+
* @public
81+
* @param page - Playwright page we are working with
82+
* @param title - Expected title of the toast
83+
*/
84+
export async function rejectToast(page: Page, title: string): Promise<void> {
85+
const toast = await getToast(page, title);
86+
await toast.locator('.mx_Toast_buttons button[data-kind="secondary"]').click();
87+
}
88+
89+
/**
90+
* Reject a toast with the given title, if it exists. Only works for the
91+
* first toast in the stack.
92+
*
93+
* @public
94+
* @param page - Playwright page we are working with
95+
* @param title - Title of the toast
96+
*/
97+
export async function rejectToastIfExists(page: Page, title: string): Promise<void> {
98+
const toast = getToastIfExists(page, title).locator('.mx_Toast_buttons button[data-kind="secondary"]');
99+
if ((await toast.count()) > 0) {
100+
await toast.click();
101+
}
102+
}

0 commit comments

Comments
 (0)