From cff2c4cd2502c17ed10322684a364fc912dd9681 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 14 May 2026 15:29:13 +0100 Subject: [PATCH] Replace toasts fixture in playwright-common with utility functions. (#33152) * Provide toasts utility functions, soon to replace the toasts fixture. * Review suggestions --- packages/playwright-common/src/index.ts | 1 + .../playwright-common/src/utils/toasts.ts | 148 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 packages/playwright-common/src/utils/toasts.ts diff --git a/packages/playwright-common/src/index.ts b/packages/playwright-common/src/index.ts index afc92244e4..60904b5e0f 100644 --- a/packages/playwright-common/src/index.ts +++ b/packages/playwright-common/src/index.ts @@ -13,6 +13,7 @@ import { routeConfigJson } from "./utils/config_json.js"; export * from "./utils/config_json.js"; export * from "./utils/context.js"; +export * from "./utils/toasts.js"; export { populateLocalStorageWithCredentials } from "./fixtures/user.js"; diff --git a/packages/playwright-common/src/utils/toasts.ts b/packages/playwright-common/src/utils/toasts.ts new file mode 100644 index 0000000000..06fed74146 --- /dev/null +++ b/packages/playwright-common/src/utils/toasts.ts @@ -0,0 +1,148 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { expect, type Locator, type Page } from "@playwright/test"; + +/** + * Assert that no toasts exist. + * + * @public + * @param page - Playwright page we are working with. + */ +export async function assertNoToasts(page: Page): Promise { + await expect(page.locator(".mx_Toast_toast")).not.toBeVisible(); +} + +/** + * Find the toast with the supplied title. Fail if it does not exist. + * + * @public + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + * @param timeout - Time in ms before we give up and decide the toast does + * not exist. + * @returns the Locator for the matching toast. + */ +export async function getToast(page: Page, title: string, timeout?: number): Promise { + const toast = page.locator(".mx_Toast_toast", { hasText: title }).first(); + await expect(toast).toBeVisible({ timeout }); + return toast; +} + +/** + * Find the toast with the supplied title, or return null if not found. + * + * @public + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + * @param timeout - Time in ms before we give up and decide the toast does + * not exist. + * @returns the Locator for the matching toast, or null if it does not exist. + */ +export async function getToastIfExists(page: Page, title: string, timeout = 2000): Promise { + const toast = page.locator(".mx_Toast_toast", { hasText: title }).first(); + try { + await toast.waitFor({ state: "visible", timeout }); + return toast; + } catch { + return null; + } +} + +/** + * Accept the toast with the supplied title, or fail if it does not exist. + * + * Only works if this toast is at the top of the stack of toasts. + * + * @public + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + */ +export async function acceptToast(page: Page, title: string): Promise { + return await clickToastButton(page, title, "primary"); +} + +/** + * Accept the toast with the supplied title, if it exists, or return after 2 + * seconds if it is not found. + * + * Only works if this toast is at the top of the stack of toasts. + * + * @public + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + */ +export async function acceptToastIfExists(page: Page, title: string): Promise { + return await clickToastButton(page, title, "primary", 2000, false); +} + +/** + * Reject the toast with the supplied title, or fail if it does not exist. + * + * Only works if this toast is at the top of the stack of toasts. + * + * @public + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + */ +export async function rejectToast(page: Page, title: string): Promise { + return await clickToastButton(page, title, "secondary"); +} + +/** + * Reject the toast with the supplied title, if it exists, or return after 2 + * seconds if it is not found. + * + * Only works if this toast is at the top of the stack of toasts. + * + * @public + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + */ +export async function rejectToastIfExists(page: Page, title: string): Promise { + return await clickToastButton(page, title, "secondary", 2000, false); +} + +/** + * Find the toast with the supplied title and click a button on it. + * + * Only works if this toast is at the top of the stack of toasts. + * + * If `required` is false, you should supply a relatively short `timeout` + * (e.g. 2000, meaning 2 seconds) to prevent your test taking too long. + * + * @param page - Playwright page we are working with. + * @param title - Expected title of the toast. + * @param button - Which button to click on the toast. Allowed values are + * "primary", which will accept the toast, or "secondary", + * which will reject it. + * @param timeout - Time in ms before we give up and decide the toast does + * not exist. If `required` is true, defaults to `timeout` + * in `TestConfig.expect`. Otherwise, defaults to 2000 (2 + * seconds). + * @param required - If true, fail the test (throw an exception) if the + * toast is not visible. Otherwise, just return after + * `timeout` if the toast is not visible. + */ +async function clickToastButton( + page: Page, + title: string, + button: "primary" | "secondary", + timeout?: number, + required = true, +): Promise { + let toast: Locator | null; + if (required) { + toast = await getToast(page, title, timeout); + } else { + toast = await getToastIfExists(page, title, timeout); + } + + if (toast) { + await toast.locator(`.mx_Toast_buttons button[data-kind="${button}"]`).click(); + } +}