Playwright/E2E: Add method to stub notification in Playwright and automate MM-T483 (#29203)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
05abb39019
Коммит
80289a0d8c
90
e2e-tests/playwright/support/mock_browser_api.ts
Обычный файл
90
e2e-tests/playwright/support/mock_browser_api.ts
Обычный файл
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Page} from '@playwright/test';
|
||||
|
||||
type NotificationData = {title: string} & NotificationOptions;
|
||||
|
||||
// Extend the Window interface to add custom properties
|
||||
declare global {
|
||||
interface Window {
|
||||
_originalNotification: typeof Notification;
|
||||
_notifications: NotificationData[];
|
||||
getNotifications: () => NotificationData[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `stubNotification` intercepts the Notification API to capture notifications.
|
||||
*
|
||||
* Note:
|
||||
* - Works across browsers and devices, except in headless mode, where stubbing the Notification API is supported only in Firefox and WebKit.
|
||||
* - An `Error: page.evaluate: window.getNotifications is not a function` may occur if the `stubNotification` function is called before the page has fully loaded.
|
||||
*
|
||||
* @param page Page object
|
||||
* @param permission Permission setting for notifications, with possible values: "default" | "granted" | "denied". Note: A notification sound may still occur even when set to "denied", as the browser might attempt to trigger system notifications.
|
||||
*/
|
||||
export async function stubNotification(page: Page, permission: NotificationPermission) {
|
||||
await page.evaluate((notificationPermission: NotificationPermission) => {
|
||||
// Override the Notification.requestPermission method
|
||||
window.Notification.requestPermission = () => Promise.resolve(permission);
|
||||
|
||||
// Copy the original Notification
|
||||
if (!window._originalNotification) {
|
||||
window._originalNotification = window.Notification;
|
||||
}
|
||||
|
||||
// Initialize a list where to capture the notifications
|
||||
window._notifications = [];
|
||||
|
||||
// Override the Notification constructor
|
||||
class CustomNotification extends window._originalNotification {
|
||||
constructor(title: string, options?: NotificationOptions) {
|
||||
super(title, options);
|
||||
const notification = {title, ...options};
|
||||
window._notifications.push(notification);
|
||||
}
|
||||
}
|
||||
|
||||
// Set static properties and permission status
|
||||
Object.defineProperties(CustomNotification, {
|
||||
permission: {
|
||||
get: () => notificationPermission,
|
||||
},
|
||||
requestPermission: {
|
||||
value: () => Promise.resolve(notificationPermission),
|
||||
},
|
||||
});
|
||||
|
||||
// Replace the global Notification with the custom one
|
||||
window.Notification = CustomNotification as unknown as typeof Notification;
|
||||
|
||||
// Method to get all notifications
|
||||
window.getNotifications = () => window._notifications;
|
||||
}, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* `waitForNotification` waits for a specified number of notifications to be received on the page within a given timeout.
|
||||
* @param page Page object
|
||||
* @param expectedCount Number of notifications to wait for before returning. (default: 1)
|
||||
* @param timeout Wait time in milliseconds. (default: 5000ms)
|
||||
* @returns An array of notifications received
|
||||
*/
|
||||
export async function waitForNotification(
|
||||
page: Page,
|
||||
expectedCount = 1,
|
||||
timeout: number = 5000,
|
||||
): Promise<NotificationData[]> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
const notifications = await page.evaluate(() => window.getNotifications());
|
||||
if (notifications.length >= expectedCount) {
|
||||
return notifications;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Notification not received within the timeout period of ${timeout}ms`);
|
||||
return [];
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {initSetup, getAdminClient} from './server';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
||||
import {pages} from './ui/pages';
|
||||
import {matchSnapshot} from './visual';
|
||||
import {stubNotification, waitForNotification} from './mock_browser_api';
|
||||
|
||||
export {expect} from '@playwright/test';
|
||||
|
||||
@@ -41,7 +42,7 @@ export const test = base.extend<ExtendedFixtures>({
|
||||
|
||||
class PlaywrightExtended {
|
||||
// ./browser_context
|
||||
readonly testBrowser: TestBrowser;
|
||||
readonly testBrowser;
|
||||
|
||||
// ./flag
|
||||
readonly shouldHaveCallsEnabled;
|
||||
@@ -65,6 +66,10 @@ class PlaywrightExtended {
|
||||
// ./visual
|
||||
readonly matchSnapshot;
|
||||
|
||||
// ./mock_browser_api
|
||||
readonly stubNotification;
|
||||
readonly waitForNotification;
|
||||
|
||||
constructor(browser: Browser) {
|
||||
// ./browser_context
|
||||
this.testBrowser = new TestBrowser(browser);
|
||||
@@ -90,6 +95,10 @@ class PlaywrightExtended {
|
||||
|
||||
// ./visual
|
||||
this.matchSnapshot = matchSnapshot;
|
||||
|
||||
// ./mock_browser_api
|
||||
this.stubNotification = stubNotification;
|
||||
this.waitForNotification = waitForNotification;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,14 @@ export default class ChannelsPost {
|
||||
await this.removePostButton.waitFor();
|
||||
await this.removePostButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* `toContainText` verifies if the post contains the specified text.
|
||||
* @param text Text to be verified in the post
|
||||
*/
|
||||
async toContainText(text: string) {
|
||||
await expect(this.container).toContainText(text);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPost};
|
||||
|
||||
@@ -69,12 +69,20 @@ export default class ChannelsPage {
|
||||
if (teamName) {
|
||||
channelsUrl += `${teamName}`;
|
||||
if (channelName) {
|
||||
const prefix = channelName.startsWith('@') ? '/messages' : '';
|
||||
const prefix = channelName.startsWith('@') ? '/messages' : '/channels';
|
||||
channelsUrl += `${prefix}/${channelName}`;
|
||||
}
|
||||
}
|
||||
await this.page.goto(channelsUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* `postMessage` posts a message in the current channel
|
||||
* @param message Message to post
|
||||
*/
|
||||
async postMessage(message: string) {
|
||||
await this.centerView.postCreate.postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
export {ChannelsPage};
|
||||
|
||||
Ссылка в новой задаче
Block a user