MM-63699: E2E/Accessibility: Settings modal (#33869)

* a11y tests on notificatins settings

* refactor settings and channel settings modal, add focus check on settings modal

* more a11y and visual tests
This commit is contained in:
sabril 2025-09-19 22:51:03 +08:00 committed by GitHub
parent fa7eeb1c0e
commit b579b4e63a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 612 additions and 68 deletions

View file

@ -31,6 +31,7 @@ export {
ChannelsPostCreate,
ChannelsPostEdit,
ChannelsPost,
ChannelSettingsModal,
DraftPost,
FindChannelsModal,
DeletePostModal,

View file

@ -1,16 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, Page} from '@playwright/test';
import {Locator, Page, expect} from '@playwright/test';
export {waitUntil} from 'async-wait-until';
const visibilityHidden = 'visibility: hidden !important;';
const hideTeamHeader = `#sidebarTeamMenuButton {${visibilityHidden}} `;
const hidePostHeaderTime = `.post__time {${visibilityHidden}} `;
const hidePostProfileIcon = `.profile-icon {${visibilityHidden}} `;
const hideKeywordsAndMentionsDesc = `#keywordsAndMentionsDesc {${visibilityHidden}} `;
export async function hideDynamicChannelsContent(page: Page) {
await page.addStyleTag({content: hideTeamHeader + hidePostHeaderTime + hidePostProfileIcon});
await page.addStyleTag({
content: hideTeamHeader + hidePostHeaderTime + hidePostProfileIcon + hideKeywordsAndMentionsDesc,
});
}
export async function waitForAnimationEnd(locator: Locator) {
@ -18,3 +21,8 @@ export async function waitForAnimationEnd(locator: Locator) {
Promise.all(element.getAnimations({subtree: true}).map((animation) => animation.finished)),
);
}
export async function toBeFocusedWithFocusVisible(locator: Locator) {
await expect(locator).toBeFocused();
return locator.evaluate((element) => element.matches(':focus-visible'));
}

View file

@ -27,7 +27,7 @@ import {
initSetup,
isOutsideRemoteUserHour,
} from './server';
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
import {toBeFocusedWithFocusVisible, hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
import {pages} from './ui/pages';
import {matchSnapshot} from './visual';
import {stubNotification, waitForNotification} from './mock_browser_api';
@ -81,6 +81,7 @@ export class PlaywrightExtended {
readonly initSetup;
// ./test_action
readonly toBeFocusedWithFocusVisible;
readonly hideDynamicChannelsContent;
readonly waitForAnimationEnd;
readonly waitUntil;
@ -134,7 +135,9 @@ export class PlaywrightExtended {
this.initSetup = initSetup;
this.getAdminClient = getAdminClient;
this.isOutsideRemoteUserHour = isOutsideRemoteUserHour;
// ./test_action
this.toBeFocusedWithFocusVisible = toBeFocusedWithFocusVisible;
this.hideDynamicChannelsContent = hideDynamicChannelsContent;
this.waitForAnimationEnd = waitForAnimationEnd;
this.waitUntil = waitUntil;

View file

@ -5,6 +5,7 @@ import {Locator, Page, ViewportSize} from '@playwright/test';
export type TestArgs = {
page: Page;
locator?: Locator;
browserName: string;
viewport?: ViewportSize | null;
};

View file

@ -0,0 +1,65 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
import InfoSettings from './info_settings';
import ConfigurationSettings from './configuration_settings';
export default class ChannelSettingsModal {
readonly container: Locator;
readonly closeButton;
readonly infoTab;
readonly configurationTab;
readonly infoSettings;
readonly configurationSettings;
constructor(container: Locator) {
this.container = container;
this.closeButton = container.getByRole('button', {name: 'Close'});
this.infoTab = container.getByRole('tab', {name: 'info'});
this.configurationTab = container.getByRole('tab', {name: 'configuration'});
this.infoSettings = new InfoSettings(container.locator('.ChannelSettingsModal__infoTab'));
this.configurationSettings = new ConfigurationSettings(
container.locator('.ChannelSettingsModal__configurationTab'),
);
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
getContainerId() {
return this.container.getAttribute('id');
}
async close() {
await this.closeButton.click();
await expect(this.container).not.toBeVisible();
}
async openInfoTab(): Promise<InfoSettings> {
await expect(this.infoTab).toBeVisible();
await this.infoTab.click();
await this.infoSettings.toBeVisible();
return this.infoSettings;
}
async openConfigurationTab(): Promise<ConfigurationSettings> {
await expect(this.configurationTab).toBeVisible();
await this.configurationTab.click();
await this.configurationSettings.toBeVisible();
return this.configurationSettings;
}
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
export default class InfoSettings {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
export default class AdvancedSettings {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}

View file

@ -18,6 +18,10 @@ export default class NotificationsSettings {
await expect(this.container).toBeVisible();
}
async getContainerId() {
return 'notificationsSettings';
}
async expandSection(section: NotificationSettingsSection) {
if (section === 'keysWithHighlight') {
await this.container.getByText('Keywords That Get Highlighted (without notifications)').click();

View file

@ -3,45 +3,57 @@
import {Locator, expect} from '@playwright/test';
import AdvancedSettings from './advanced_settings';
import DisplaySettings from './display_settings';
import NotificationsSettings from './notification_settings';
import ConfigurationSettings from '@/ui/components/channels/settings/configuration_settings';
import NotificationsSettings from './notifications_settings';
import SidebarSettings from './sidebar_settings';
export default class SettingsModal {
readonly container: Locator;
readonly notificationsSettingsTab;
readonly configurationSettingsTab;
readonly content;
readonly closeButton;
readonly notificationsTab;
readonly displayTab;
readonly sidebarTab;
readonly advancedTab;
readonly notificationsSettings;
readonly configurationSettings;
readonly displaySettingsTab;
readonly displaySettings;
readonly sidebarSettings;
readonly advancedSettings;
constructor(container: Locator) {
this.container = container;
this.notificationsSettingsTab = container.locator('#notificationsButton');
this.configurationSettingsTab = container.locator('#configurationButton');
this.content = container.locator('.modal-content');
this.closeButton = container.getByRole('button', {name: 'Close'});
this.notificationsSettings = new NotificationsSettings(container.locator('#notificationsSettings'));
this.configurationSettings = new ConfigurationSettings(
container.locator('.ChannelSettingsModal__configurationTab'),
this.notificationsTab = container.getByRole('tab', {name: 'notifications'});
this.displayTab = container.getByRole('tab', {name: 'display'});
this.sidebarTab = container.getByRole('tab', {name: 'sidebar'});
this.advancedTab = container.getByRole('tab', {name: 'advanced'});
this.notificationsSettings = new NotificationsSettings(
container.getByRole('tabpanel', {name: 'notifications'}),
);
this.displaySettingsTab = container.locator('#displayButton');
this.displaySettings = new DisplaySettings(container.locator('#displaySettings'));
this.displaySettings = new DisplaySettings(container.getByRole('tabpanel', {name: 'display'}));
this.sidebarSettings = new SidebarSettings(container.getByRole('tabpanel', {name: 'sidebar'}));
this.advancedSettings = new AdvancedSettings(container.getByRole('tabpanel', {name: 'advanced'}));
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
getContainerId() {
return this.container.getAttribute('id');
}
async openNotificationsTab() {
await expect(this.notificationsSettingsTab).toBeVisible();
await this.notificationsSettingsTab.click();
await expect(this.notificationsTab).toBeVisible();
await this.notificationsTab.click();
await this.notificationsSettings.toBeVisible();
@ -49,32 +61,35 @@ export default class SettingsModal {
}
async openDisplayTab() {
await expect(this.displaySettingsTab).toBeVisible();
await this.displaySettingsTab.click();
await expect(this.displayTab).toBeVisible();
await this.displayTab.click();
await this.displaySettings.toBeVisible();
return this.displaySettings;
}
async closeModal() {
async openSidebarTab() {
await expect(this.sidebarTab).toBeVisible();
await this.sidebarTab.click();
await this.sidebarSettings.toBeVisible();
return this.sidebarSettings;
}
async openAdvancedTab() {
await expect(this.advancedTab).toBeVisible();
await this.advancedTab.click();
await this.advancedSettings.toBeVisible();
return this.advancedSettings;
}
async close() {
await this.container.getByLabel('Close').click();
await expect(this.container).not.toBeVisible();
}
async openConfigurationTab(): Promise<ConfigurationSettings> {
await expect(this.configurationSettingsTab).toBeVisible();
await this.configurationSettingsTab.click();
await this.configurationSettings.toBeVisible();
return this.configurationSettings;
}
async close() {
const closeButton = this.container.locator('button.close');
await expect(closeButton).toBeVisible();
await closeButton.click();
}
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Locator, expect} from '@playwright/test';
export default class SidebarSettings {
readonly container: Locator;
constructor(container: Locator) {
this.container = container;
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
}

View file

@ -12,6 +12,7 @@ export default class GlobalHeader {
readonly accountMenuButton;
readonly productSwitchMenu;
readonly recentMentionsButton;
readonly savedMessagesButton;
readonly settingsButton;
readonly searchBox;
@ -22,6 +23,7 @@ export default class GlobalHeader {
this.accountMenuButton = container.getByRole('button', {name: "'s account menu"});
this.productSwitchMenu = container.getByRole('button', {name: 'Product switch menu'});
this.recentMentionsButton = container.getByRole('button', {name: 'Recent mentions'});
this.savedMessagesButton = container.getByRole('button', {name: 'Saved messages'});
this.settingsButton = container.getByRole('button', {name: 'Settings'});
this.searchBox = container.locator('#searchFormContainer');
}

View file

@ -8,6 +8,7 @@ import ChannelsPost from './channels/post';
import ChannelsCenterView from './channels/center_view';
import ChannelsSidebarLeft from './channels/sidebar_left';
import ChannelsSidebarRight from './channels/sidebar_right';
import ChannelSettingsModal from './channels/channel_settings/channel_settings_modal';
import DeletePostModal from './channels/delete_post_modal';
import FindChannelsModal from './channels/find_channels_modal';
import SettingsModal from './channels/settings/settings_modal';
@ -56,6 +57,7 @@ const components = {
ChannelsPostCreate,
ChannelsPostEdit,
ChannelsPost,
ChannelSettingsModal,
DraftPost,
FindChannelsModal,
DeletePostModal,
@ -103,6 +105,7 @@ export {
ChannelsPostCreate,
ChannelsPostEdit,
ChannelsPost,
ChannelSettingsModal,
DraftPost,
FindChannelsModal,
DeletePostModal,

View file

@ -4,8 +4,7 @@
import {expect, Page} from '@playwright/test';
import {waitUntil} from 'async-wait-until';
import {ChannelsPost, components} from '@/ui/components';
import SettingsModal from '@/ui/components/channels/settings/settings_modal';
import {ChannelsPost, ChannelSettingsModal, SettingsModal, components} from '@/ui/components';
import {duration} from '@/util';
export default class ChannelsPage {
readonly channels = 'Channels';
@ -23,10 +22,12 @@ export default class ChannelsPage {
readonly userProfilePopover;
readonly messagePriority;
readonly findChannelsModal;
readonly channelSettingsModal;
readonly deletePostModal;
readonly settingsModal;
readonly findChannelsModal;
readonly profileModal;
readonly settingsModal;
readonly postContainer;
readonly postDotMenu;
readonly postReminderMenu;
@ -49,10 +50,11 @@ export default class ChannelsPage {
this.userAccountMenuButton = page.getByRole('button', {name: "User's account menu"});
// Modals
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
this.channelSettingsModal = new ChannelSettingsModal(page.getByRole('dialog', {name: 'Channel Settings'}));
this.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal'));
this.settingsModal = new components.SettingsModal(page.getByRole('dialog', {name: 'Settings'}));
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
this.profileModal = new components.ProfileModal(page.getByRole('dialog', {name: 'Profile'}));
this.settingsModal = new components.SettingsModal(page.getByRole('dialog', {name: 'Settings'}));
// Menus
this.postDotMenu = new components.PostDotMenu(page.getByRole('menu', {name: 'Post extra options'}));
@ -129,11 +131,17 @@ export default class ChannelsPage {
return {rootPost, sidebarRight, lastPost};
}
async openChannelSettings(): Promise<SettingsModal> {
async openChannelSettings(): Promise<ChannelSettingsModal> {
await this.centerView.header.openChannelMenu();
await this.page.locator('#channelSettings[role="menuitem"]').click();
await this.settingsModal.toBeVisible();
await this.channelSettingsModal.toBeVisible();
return this.channelSettingsModal;
}
async openSettings(): Promise<SettingsModal> {
await this.globalHeader.openSettings();
await this.settingsModal.toBeVisible();
return this.settingsModal;
}

View file

@ -30,9 +30,15 @@ export async function matchSnapshot(testInfo: TestInfo, testArgs: TestArgs, opti
}
if (testConfig.snapshotEnabled) {
// Visual test with built-in snapshot
const filename = testInfo.title.trim().replace(illegalRe, '').replace(/\s/g, '-').trim().toLowerCase();
await expect(testArgs.page).toHaveScreenshot(`${filename}.png`, {fullPage: true, ...options});
// Visual test with built-in snapshot
// If locator is provided, take screenshot of the locator instead of the full page
if (testArgs.locator) {
await expect(testArgs.locator).toHaveScreenshot(`${filename}.png`, {...options});
} else {
await expect(testArgs.page).toHaveScreenshot(`${filename}.png`, {fullPage: true, ...options});
}
}
if (testConfig.percyEnabled) {

View file

@ -33,7 +33,7 @@
},
"../../webapp/platform/client": {
"name": "@mattermost/client",
"version": "10.11.0",
"version": "10.12.0",
"license": "MIT",
"devDependencies": {
"@types/jest": "28.1.8",
@ -41,7 +41,7 @@
"typescript": "^5.0.0"
},
"peerDependencies": {
"@mattermost/types": "10.11.0",
"@mattermost/types": "10.12.0",
"typescript": "^4.3.0 || ^5.0.0"
},
"peerDependenciesMeta": {
@ -52,7 +52,7 @@
},
"../../webapp/platform/types": {
"name": "@mattermost/types",
"version": "10.11.0",
"version": "10.12.0",
"license": "MIT",
"devDependencies": {
"typescript": "^5.0.0"

View file

@ -15,6 +15,7 @@
"check": "npm run lint && npm run prettier && npm run tsc && npm run lint:test-docs",
"test": "npm run build && cross-env PW_SNAPSHOT_ENABLE=true playwright test",
"test:ci": "npm run build && cross-env PW_SNAPSHOT_ENABLE=true playwright test --grep-invert @visual --project=chrome",
"test:visual": "npm run build && cross-env PW_SNAPSHOT_ENABLE=true playwright test --grep @visual",
"test:update-snapshots": "npm run build && cross-env PW_SNAPSHOT_ENABLE=true playwright test specs/visual --grep @visual --update-snapshots",
"test:slomo": "npm run build && cross-env PW_SNAPSHOT_ENABLE=true PW_SLOWMO=1000 playwright test",
"percy:docker": "npm run build && cross-env PW_PERCY_ENABLE=true PERCY_BROWSER_EXECUTABLE='/ms-playwright/chromium-1169/chrome-linux/chrome' percy exec -- playwright test specs/visual --grep @visual --project=chrome --project=ipad",

View file

@ -0,0 +1,301 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify that focus is properly managed when opening and closing settings modal using keyboard interactions
*/
test(
'manages focus when opening and closing settings modal with keyboard',
{tag: ['@accessibility', '@settings']},
async ({pw}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const globalHeader = channelsPage.globalHeader;
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings modal should be visible and focus should be on the modal
await expect(settingsModal.container).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.container);
// # Press Tab and verify focus is on Close button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(settingsModal.closeButton);
// # Press Enter and verify Settings modal is closed and focus is back on Settings button
await page.keyboard.press('Enter');
await expect(settingsModal.container).not.toBeVisible();
await pw.toBeFocusedWithFocusVisible(globalHeader.settingsButton);
// # Open Settings modal again
await page.keyboard.press('Enter');
await expect(settingsModal.container).toBeVisible();
// # Press Escape and verify Settings modal is closed and focus is back on Settings button
await page.keyboard.press('Escape');
await expect(settingsModal.container).not.toBeVisible();
await pw.toBeFocusedWithFocusVisible(globalHeader.settingsButton);
},
);
/**
* @objective Verify that users can navigate between settings tabs using arrow keys
*/
test('navigates between settings tabs using arrow keys', {tag: ['@accessibility', '@settings']}, async ({pw}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await channelsPage.globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings modal should be visible and focus should be on the modal
await expect(settingsModal.container).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.container);
// # Press Tab twice and verify focus is on Notifications tab and Notifications Settings panel is visible
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await expect(settingsModal.notificationsTab).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.notificationsTab);
await expect(settingsModal.notificationsSettings.container).toBeVisible();
// # Press arrow down key and verify focus is on Display tab and Display Settings panel is visible
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(settingsModal.displayTab);
await expect(settingsModal.displaySettings.container).toBeVisible();
// # Press arrow down key and verify focus is on Sidebar tab and Sidebar Settings panel is visible
await page.keyboard.press('ArrowDown');
await expect(settingsModal.sidebarTab).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.sidebarTab);
await expect(settingsModal.sidebarSettings.container).toBeVisible();
// # Press Tab and verify focus is on Advanced tab and Advanced Settings panel is visible
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(settingsModal.advancedTab);
await expect(settingsModal.advancedSettings.container).toBeVisible();
// # Press arrow down key and verify focus is back on Notifications tab and Notifications Settings panel is visible
await page.keyboard.press('ArrowDown');
await expect(settingsModal.notificationsTab).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.notificationsTab);
await expect(settingsModal.notificationsSettings.container).toBeVisible();
// # Press arrow up key and verify focus is on Advanced tab and Advanced Settings panel is visible
await page.keyboard.press('ArrowUp');
await expect(settingsModal.advancedTab).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.advancedTab);
await expect(settingsModal.advancedSettings.container).toBeVisible();
});
/**
* @objective Verify that notifications settings panel meets WCAG accessibility standards
*/
test(
'passes accessibility scan on notifications settings panel',
{tag: ['@accessibility', '@settings']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await channelsPage.globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings modal on notifications panel should be visible
await expect(settingsModal.container).toBeVisible();
await expect(settingsModal.notificationsSettings.container).toBeVisible();
// * Analyze the Settings modal with notifications panel
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.disableRules([
'color-contrast',
// Known issue: These fail due to the way setting tabs are grouped.
'aria-required-children',
'aria-required-parent',
])
.include(settingsModal.getContainerId())
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify that display settings panel meets WCAG accessibility standards
*/
test(
'passes accessibility scan on display settings panel',
{tag: ['@accessibility', '@settings']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await channelsPage.globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings dialog should be visible
await expect(settingsModal.container).toBeVisible();
// # Open Display tab
await settingsModal.openDisplayTab();
// * Display Settings panel should be visible
await expect(settingsModal.displaySettings.container).toBeVisible();
// * Analyze the Settings modal with display panel
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.disableRules([
'color-contrast',
// Known issue: These fail due to the way setting tabs are grouped.
'aria-required-children',
'aria-required-parent',
])
.include(settingsModal.getContainerId())
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify that sidebar settings panel meets WCAG accessibility standards
*/
test(
'passes accessibility scan on sidebar settings panel',
{tag: ['@accessibility', '@settings']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await channelsPage.globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings dialog should be visible
await expect(settingsModal.container).toBeVisible();
// # Open Sidebar tab
await settingsModal.openSidebarTab();
// * Sidebar Settings panel should be visible
await expect(settingsModal.sidebarSettings.container).toBeVisible();
// * Analyze the Settings modal with sidebar panel
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.disableRules([
'color-contrast',
// Known issue: These fail due to the way setting tabs are grouped.
'aria-required-children',
'aria-required-parent',
])
.include(settingsModal.getContainerId())
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify that advanced settings panel meets WCAG accessibility standards
*/
test(
'passes accessibility scan on advanced settings panel',
{tag: ['@accessibility', '@settings']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await channelsPage.globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings dialog should be visible
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.openAdvancedTab();
// * Advanced Settings panel should be visible
await expect(settingsModal.advancedSettings.container).toBeVisible();
// * Analyze the Settings modal with advanced panel
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.disableRules([
'color-contrast',
// Known issue: These fail due to the way setting tabs are grouped.
'aria-required-children',
'aria-required-parent',
])
.include(settingsModal.getContainerId())
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View file

@ -15,36 +15,36 @@ test('Should show channel banner when configured', async ({pw}) => {
await channelsPage.newChannel(getRandomId(), 'O');
let settingsModal = await channelsPage.openChannelSettings();
let configurationTab = await settingsModal.openConfigurationTab();
let channelSettingsModal = await channelsPage.openChannelSettings();
let configurationTab = await channelSettingsModal.openConfigurationTab();
await configurationTab.enableChannelBanner();
await configurationTab.setChannelBannerText('Example channel banner text');
await configurationTab.setChannelBannerTextColor('#77DD88');
await configurationTab.save();
await settingsModal.closeModal();
await channelSettingsModal.close();
await channelsPage.centerView.assertChannelBanner('Example channel banner text', '#77DD88');
// Now we'll disable the channel banner
settingsModal = await channelsPage.openChannelSettings();
configurationTab = await settingsModal.openConfigurationTab();
channelSettingsModal = await channelsPage.openChannelSettings();
configurationTab = await channelSettingsModal.openConfigurationTab();
await configurationTab.disableChannelBanner();
await configurationTab.save();
await settingsModal.closeModal();
await channelSettingsModal.close();
await channelsPage.centerView.assertChannelBannerNotVisible();
// re-enabling channel banner should already have
// the previously configured text and color
settingsModal = await channelsPage.openChannelSettings();
configurationTab = await settingsModal.openConfigurationTab();
channelSettingsModal = await channelsPage.openChannelSettings();
configurationTab = await channelSettingsModal.openConfigurationTab();
await configurationTab.enableChannelBanner();
await configurationTab.save();
await settingsModal.closeModal();
await channelSettingsModal.close();
await channelsPage.centerView.assertChannelBanner('Example channel banner text', '#77DD88');
});
@ -60,15 +60,15 @@ test('Should render markdown', async ({pw}) => {
await channelsPage.newChannel(getRandomId(), 'O');
const settingsModal = await channelsPage.openChannelSettings();
const configurationTab = await settingsModal.openConfigurationTab();
const channelSettingsModal = await channelsPage.openChannelSettings();
const configurationTab = await channelSettingsModal.openConfigurationTab();
await configurationTab.enableChannelBanner();
await configurationTab.setChannelBannerText('**bold** *italic* ~~strikethrough~~');
await configurationTab.setChannelBannerTextColor('#77DD88');
await configurationTab.save();
await settingsModal.closeModal();
await channelSettingsModal.close();
await channelsPage.centerView.assertChannelBannerHasBoldText('bold');
await channelsPage.centerView.assertChannelBannerHasItalicText('italic');

View file

@ -93,7 +93,7 @@ test('MM-T5465-2 Should highlight the keywords when a message is sent with the k
await notificationsSettings.save();
// # Close the settings modal
await settingsModal.closeModal();
await settingsModal.close();
// # Post a message without the keyword
const messageWithoutKeyword = 'This message does not contain the keyword';
@ -147,7 +147,7 @@ test('MM-T5465-3 Should highlight the keywords when a message is sent with the k
await notificationsSettings.save();
// # Close the settings modal
await settingsModal.closeModal();
await settingsModal.close();
// # Post a message without the keyword
const messageWithoutKeyword = 'This message does not contain the keyword';
@ -203,7 +203,7 @@ test('MM-T5465-4 Highlighted keywords should not appear in the Recent Mentions',
await notificationsSettings.save();
// # Close the settings modal
await settingsModal.closeModal();
await settingsModal.close();
// # Open the recent mentions
await channelsPage.globalHeader.openRecentMentions();
@ -263,7 +263,7 @@ test('MM-T5465-5 Should highlight keywords in message sent from another user', a
await notificationsSettings.save();
// # Close the settings modal
await settingsModal.closeModal();
await settingsModal.close();
// * Verify that the keywords are highlighted in the last message received
const lastPostWithHighlight = await channelsPage.getLastPost();

View file

@ -0,0 +1,78 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Capture snapshots of settings modal and key focused elements on keyboard navigation
*/
test('settings modal visual check', {tag: ['@visual', '@settings']}, async ({pw, browserName, viewport}, testInfo) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const globalHeader = channelsPage.globalHeader;
const settingsModal = channelsPage.settingsModal;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus to Settings button and press Enter
await globalHeader.settingsButton.focus();
await page.keyboard.press('Enter');
// * Settings modal should be visible and focus should be on the modal
await expect(settingsModal.container).toBeVisible();
await pw.toBeFocusedWithFocusVisible(settingsModal.container);
const testArgs = {page, browserName, viewport};
await pw.hideDynamicChannelsContent(page);
await pw.matchSnapshot({...testInfo, title: `${testInfo.title}`}, {...testArgs, locator: settingsModal.content});
// # Press Tab to move focus to Close button
await page.keyboard.press('Tab');
// * Verify focus is on Close button
await pw.toBeFocusedWithFocusVisible(settingsModal.closeButton);
await pw.matchSnapshot(
{...testInfo, title: `${testInfo.title} close button`},
{...testArgs, locator: settingsModal.content},
);
// # Press Tab to move focus to Notifications tab
await page.keyboard.press('Tab');
// * Verify focus is on Notifications tab and Notifications Settings panel is visible
await pw.toBeFocusedWithFocusVisible(settingsModal.notificationsTab);
await pw.matchSnapshot(
{...testInfo, title: `${testInfo.title} notifications tab`},
{...testArgs, locator: settingsModal.content},
);
// # Press arrow down key to move focus to Display tab
await page.keyboard.press('ArrowDown');
// * Verify focus is on Display tab and Display Settings panel is visible
await pw.toBeFocusedWithFocusVisible(settingsModal.displayTab);
await pw.matchSnapshot(
{...testInfo, title: `${testInfo.title} display tab`},
{...testArgs, locator: settingsModal.content},
);
// # Press arrow down key to move focus to Sidebar tab
await page.keyboard.press('ArrowDown');
// * Verify focus is on Sidebar tab and Sidebar Settings panel is visible
await pw.toBeFocusedWithFocusVisible(settingsModal.sidebarTab);
await pw.matchSnapshot(
{...testInfo, title: `${testInfo.title} sidebar tab`},
{...testArgs, locator: settingsModal.content},
);
// # Press arrow down key to move focus to Advanced tab
await page.keyboard.press('ArrowDown');
// * Verify focus is on Advanced tab and Advanced Settings panel is visible
await pw.toBeFocusedWithFocusVisible(settingsModal.advancedTab);
await pw.matchSnapshot(
{...testInfo, title: `${testInfo.title} advanced tab`},
{...testArgs, locator: settingsModal.content},
);
});