diff --git a/e2e-tests/playwright/lib/src/index.ts b/e2e-tests/playwright/lib/src/index.ts index 5df4baf223d..acafc87bc85 100644 --- a/e2e-tests/playwright/lib/src/index.ts +++ b/e2e-tests/playwright/lib/src/index.ts @@ -31,6 +31,7 @@ export { ChannelsPostCreate, ChannelsPostEdit, ChannelsPost, + ChannelSettingsModal, DraftPost, FindChannelsModal, DeletePostModal, diff --git a/e2e-tests/playwright/lib/src/test_action.ts b/e2e-tests/playwright/lib/src/test_action.ts index d9ca7e4d9c7..ee7b84404b0 100644 --- a/e2e-tests/playwright/lib/src/test_action.ts +++ b/e2e-tests/playwright/lib/src/test_action.ts @@ -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')); +} diff --git a/e2e-tests/playwright/lib/src/test_fixture.ts b/e2e-tests/playwright/lib/src/test_fixture.ts index f104b5cc0b6..5143efcf4ef 100644 --- a/e2e-tests/playwright/lib/src/test_fixture.ts +++ b/e2e-tests/playwright/lib/src/test_fixture.ts @@ -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; diff --git a/e2e-tests/playwright/lib/src/types.ts b/e2e-tests/playwright/lib/src/types.ts index 04c01a10981..83c6dd8a900 100644 --- a/e2e-tests/playwright/lib/src/types.ts +++ b/e2e-tests/playwright/lib/src/types.ts @@ -5,6 +5,7 @@ import {Locator, Page, ViewportSize} from '@playwright/test'; export type TestArgs = { page: Page; + locator?: Locator; browserName: string; viewport?: ViewportSize | null; }; diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts new file mode 100644 index 00000000000..479a23665d9 --- /dev/null +++ b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/channel_settings_modal.ts @@ -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 { + await expect(this.infoTab).toBeVisible(); + await this.infoTab.click(); + + await this.infoSettings.toBeVisible(); + + return this.infoSettings; + } + + async openConfigurationTab(): Promise { + await expect(this.configurationTab).toBeVisible(); + await this.configurationTab.click(); + + await this.configurationSettings.toBeVisible(); + + return this.configurationSettings; + } +} diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/configuration_settings.ts b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/configuration_settings.ts similarity index 100% rename from e2e-tests/playwright/lib/src/ui/components/channels/settings/configuration_settings.ts rename to e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/configuration_settings.ts diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/info_settings.ts b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/info_settings.ts new file mode 100644 index 00000000000..97b957a4e6e --- /dev/null +++ b/e2e-tests/playwright/lib/src/ui/components/channels/channel_settings/info_settings.ts @@ -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(); + } +} diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/advanced_settings.ts b/e2e-tests/playwright/lib/src/ui/components/channels/settings/advanced_settings.ts new file mode 100644 index 00000000000..7446a50bb87 --- /dev/null +++ b/e2e-tests/playwright/lib/src/ui/components/channels/settings/advanced_settings.ts @@ -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(); + } +} diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/notification_settings.ts b/e2e-tests/playwright/lib/src/ui/components/channels/settings/notifications_settings.ts similarity index 96% rename from e2e-tests/playwright/lib/src/ui/components/channels/settings/notification_settings.ts rename to e2e-tests/playwright/lib/src/ui/components/channels/settings/notifications_settings.ts index f8d20f3d9c2..399501afbbd 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/settings/notification_settings.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/settings/notifications_settings.ts @@ -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(); diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts b/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts index 28ba99d2340..fb7f851fabe 100644 --- a/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts +++ b/e2e-tests/playwright/lib/src/ui/components/channels/settings/settings_modal.ts @@ -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 { - 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(); - } } diff --git a/e2e-tests/playwright/lib/src/ui/components/channels/settings/sidebar_settings.ts b/e2e-tests/playwright/lib/src/ui/components/channels/settings/sidebar_settings.ts new file mode 100644 index 00000000000..187be6c18ba --- /dev/null +++ b/e2e-tests/playwright/lib/src/ui/components/channels/settings/sidebar_settings.ts @@ -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(); + } +} diff --git a/e2e-tests/playwright/lib/src/ui/components/global_header.ts b/e2e-tests/playwright/lib/src/ui/components/global_header.ts index 481c03c25a8..4a30580e9d5 100644 --- a/e2e-tests/playwright/lib/src/ui/components/global_header.ts +++ b/e2e-tests/playwright/lib/src/ui/components/global_header.ts @@ -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'); } diff --git a/e2e-tests/playwright/lib/src/ui/components/index.ts b/e2e-tests/playwright/lib/src/ui/components/index.ts index 85943ffba51..8c6a2903894 100644 --- a/e2e-tests/playwright/lib/src/ui/components/index.ts +++ b/e2e-tests/playwright/lib/src/ui/components/index.ts @@ -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, diff --git a/e2e-tests/playwright/lib/src/ui/pages/channels.ts b/e2e-tests/playwright/lib/src/ui/pages/channels.ts index 9a282d526aa..c714bcddd91 100644 --- a/e2e-tests/playwright/lib/src/ui/pages/channels.ts +++ b/e2e-tests/playwright/lib/src/ui/pages/channels.ts @@ -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 { + async openChannelSettings(): Promise { 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 { + await this.globalHeader.openSettings(); + await this.settingsModal.toBeVisible(); return this.settingsModal; } diff --git a/e2e-tests/playwright/lib/src/visual/index.ts b/e2e-tests/playwright/lib/src/visual/index.ts index 59e4682091a..37fff9f6da2 100644 --- a/e2e-tests/playwright/lib/src/visual/index.ts +++ b/e2e-tests/playwright/lib/src/visual/index.ts @@ -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) { diff --git a/e2e-tests/playwright/package-lock.json b/e2e-tests/playwright/package-lock.json index 9e39edeb21a..859b168c5dd 100644 --- a/e2e-tests/playwright/package-lock.json +++ b/e2e-tests/playwright/package-lock.json @@ -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" diff --git a/e2e-tests/playwright/package.json b/e2e-tests/playwright/package.json index 9361c5c5ded..0024e945768 100644 --- a/e2e-tests/playwright/package.json +++ b/e2e-tests/playwright/package.json @@ -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", diff --git a/e2e-tests/playwright/specs/accessibility/channels/settings_dialog/settings.spec.ts b/e2e-tests/playwright/specs/accessibility/channels/settings_dialog/settings.spec.ts new file mode 100644 index 00000000000..3cf465d4242 --- /dev/null +++ b/e2e-tests/playwright/specs/accessibility/channels/settings_dialog/settings.spec.ts @@ -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); + }, +); diff --git a/e2e-tests/playwright/specs/functional/channels/channel_banner/channel_banner.spec.ts b/e2e-tests/playwright/specs/functional/channels/channel_banner/channel_banner.spec.ts index 1f45542b1e5..98fcad87231 100644 --- a/e2e-tests/playwright/specs/functional/channels/channel_banner/channel_banner.spec.ts +++ b/e2e-tests/playwright/specs/functional/channels/channel_banner/channel_banner.spec.ts @@ -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'); diff --git a/e2e-tests/playwright/specs/functional/channels/settings/notifications/highlight_without_notification.spec.ts b/e2e-tests/playwright/specs/functional/channels/settings/notifications/highlight_without_notification.spec.ts index 3287ef3a91a..c4814327924 100644 --- a/e2e-tests/playwright/specs/functional/channels/settings/notifications/highlight_without_notification.spec.ts +++ b/e2e-tests/playwright/specs/functional/channels/settings/notifications/highlight_without_notification.spec.ts @@ -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(); diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts new file mode 100644 index 00000000000..676e8002de2 --- /dev/null +++ b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts @@ -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}, + ); +}); diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-chrome-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-chrome-linux.png new file mode 100644 index 00000000000..390247ad3a4 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-firefox-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-firefox-linux.png new file mode 100644 index 00000000000..5861a627336 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-ipad-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-ipad-linux.png new file mode 100644 index 00000000000..3453bc044a6 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-advanced-tab-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-chrome-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-chrome-linux.png new file mode 100644 index 00000000000..ffd00d98253 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-chrome-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-chrome-linux.png new file mode 100644 index 00000000000..9029803fcb7 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-firefox-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-firefox-linux.png new file mode 100644 index 00000000000..ae520501aa4 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-ipad-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-ipad-linux.png new file mode 100644 index 00000000000..dcd65c11b4a Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-close-button-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-chrome-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-chrome-linux.png new file mode 100644 index 00000000000..d6e2694d465 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-firefox-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-firefox-linux.png new file mode 100644 index 00000000000..6d87265d4db Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-ipad-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-ipad-linux.png new file mode 100644 index 00000000000..f3e1b6d9a57 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-display-tab-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-firefox-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-firefox-linux.png new file mode 100644 index 00000000000..fdfc5d32fd2 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-ipad-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-ipad-linux.png new file mode 100644 index 00000000000..d7fbf088473 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-chrome-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-chrome-linux.png new file mode 100644 index 00000000000..4adc5cbc7bf Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-firefox-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-firefox-linux.png new file mode 100644 index 00000000000..46cb184d5d9 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-ipad-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-ipad-linux.png new file mode 100644 index 00000000000..ac5cd7d9897 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-notifications-tab-ipad-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-chrome-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-chrome-linux.png new file mode 100644 index 00000000000..da53bff3b1f Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-chrome-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-firefox-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-firefox-linux.png new file mode 100644 index 00000000000..43f0b642bf4 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-firefox-linux.png differ diff --git a/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-ipad-linux.png b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-ipad-linux.png new file mode 100644 index 00000000000..6e5b682a6a8 Binary files /dev/null and b/e2e-tests/playwright/specs/visual/channels/settings_dialog/settings_dialog.spec.ts-snapshots/settings-modal-visual-check-sidebar-tab-ipad-linux.png differ