snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Page} from '@playwright/test';
import type {UserProfile} from '@mattermost/types/users';
import type {ChannelsPage} from '@mattermost/playwright-lib';
import {expect, test} from '@mattermost/playwright-lib';
test('MM-63451 should be able to navigate the account settings menu with the keyboard after opening it with the mouse', 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);
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on the account menu button
await channelsPage.globalHeader.accountMenuButton.click();
await testMenuWithKeyboard(user, page, channelsPage);
});
test('MM-63451 should be able to navigate the account settings menu with the keyboard after opening it with the keyboard', 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);
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Focus the account menu button
await channelsPage.globalHeader.accountMenuButton.focus();
await expect(channelsPage.globalHeader.accountMenuButton).toBeFocused();
await page.keyboard.press('Space');
await testMenuWithKeyboard(user, page, channelsPage);
});
async function testMenuWithKeyboard(user: UserProfile, page: Page, channelsPage: ChannelsPage) {
// * Should start focused on the first menu item
await expect(page.getByRole('menuitem', {name: '@' + user.username})).toBeFocused();
// * Should be able to scroll down through the menu with the keyboard
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Set custom status'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Online'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Away'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Do not disturb Disables all notifications'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Offline'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Profile'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Log Out'})).toBeFocused();
// * Should be able to scroll back up through the menu with the keyboard
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('menuitem', {name: 'Profile'})).toBeFocused();
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('menuitem', {name: 'Offline'})).toBeFocused();
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('menuitem', {name: 'Do not disturb Disables all notifications'})).toBeFocused();
// * Should be able to move into the submenu by pressing the right arrow
await page.keyboard.press('ArrowRight');
await expect(page.getByRole('menuitem', {name: "Don't clear"})).toBeFocused();
// * Should be able to scroll through the submenu with the keyboard
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: '30 mins'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: '1 hour'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: '2 hours'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Tomorrow'})).toBeFocused();
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: 'Choose date and time'})).toBeFocused();
// * Should wrap around when you reach the end
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('menuitem', {name: "Don't clear"})).toBeFocused();
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('menuitem', {name: 'Choose date and time'})).toBeFocused();
// * Should be able to close the submenu by pressing the left arrow
await page.keyboard.press('ArrowLeft');
await expect(page.getByRole('menuitem', {name: 'Do not disturb Disables all notifications'})).toBeFocused();
// * Should be able to close the menu by pressing escape
await page.keyboard.press('Escape');
await expect(page.getByRole('menuitem')).toHaveCount(0);
// * Should be focused back on the menu button
await expect(channelsPage.globalHeader.accountMenuButton).toBeFocused();
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test('Settings sidebar should be keyboard accessible', async ({axe, pw}) => {
const {user} = await pw.initSetup();
// # Log in as a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Initialize Axe
const ab = axe.builder(page).disableRules([
'color-contrast',
// Known issue: These fail due to the way we've grouped plugin setting tabs together in the LHS
'aria-required-children',
'aria-required-parent',
]);
// # Visit default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Open settings modal
const settingsModal = await channelsPage.globalHeader.openSettings();
// * The settings modal should have no accessibility violations
const accessibilityScanResults = await ab.analyze();
expect(accessibilityScanResults.violations).toHaveLength(0);
// # Focus the sidebar
await settingsModal.container.focus();
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
// * The Notifications tab should start focused
await expect(page.getByRole('tab', {name: 'Notifications'})).toBeFocused();
await expect(page.getByText('Desktop and mobile notifications')).toBeVisible();
// * Pressing the down arrow should focus and show the Display tab
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('tab', {name: 'Display'})).toBeFocused();
await expect(page.getByText('Theme', {exact: true})).toBeVisible();
// * Pressing the down arrow should focus and show the Sidebar tab
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('tab', {name: 'Sidebar'})).toBeFocused();
await expect(page.getByText('Group unread channels separately')).toBeVisible();
// * Pressing the down arrow should focus and show the Advanced tab
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('tab', {name: 'Advanced'})).toBeFocused();
await expect(page.getByText('Enable Post Formatting')).toBeVisible();
// * Pressing the up arrow should go back through the tabs
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('tab', {name: 'Sidebar'})).toBeFocused();
await expect(page.getByText('Group unread channels separately')).toBeVisible();
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('tab', {name: 'Display'})).toBeFocused();
await expect(page.getByText('Theme', {exact: true})).toBeVisible();
await page.keyboard.press('ArrowUp');
await expect(page.getByRole('tab', {name: 'Notifications'})).toBeFocused();
await expect(page.getByText('Desktop and mobile notifications')).toBeVisible();
});

View File

@@ -0,0 +1,181 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify accessibility support in Add people to Channel Dialog screen
*/
test(
'MM-T1468 Accessibility Support in Add people to Channel Dialog screen',
{tag: ['@accessibility', '@add_people_channel']},
async ({pw}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, adminUser, adminClient} = await pw.initSetup();
// # Turn off email invitations, which replace the no-results message asserted below with an
// # invite-to-team prompt
await adminClient.patchConfig({ServiceSettings: {EnableEmailInvitations: false}});
// # Create a channel in the team
const channel = await adminClient.createChannel(
pw.random.channel({
teamId: team.id,
displayName: 'Test Channel',
name: 'test-channel',
}),
);
// # Create additional users and add to team
for (let i = 0; i < 5; i++) {
const newUser = await adminClient.createUser(await pw.random.user(), '', '');
await adminClient.addToTeam(team.id, newUser.id);
}
// # Log in as admin
const {page, channelsPage} = await pw.testBrowser.login(adminUser);
// # Visit the test channel
await channelsPage.goto(team.name, channel.name);
await channelsPage.toBeVisible();
// # Open channel menu and click Members
await channelsPage.centerView.header.openChannelMenu();
const membersMenuItem = page.locator('#channelMembers');
await membersMenuItem.click();
// # Click the Add people button
const membersRhs = page.locator('#rhsContainer');
await expect(membersRhs).toBeVisible();
const addMembers = membersRhs.locator('button.add-members');
await expect(addMembers).toBeEnabled();
await addMembers.click();
// * Verify the Add people dialog is visible
const dialog = page.getByRole('dialog').first();
await expect(dialog).toBeVisible();
// * Verify the heading with channel name
const modalName = `Add people to ${channel.display_name}`;
await expect(dialog.getByRole('heading', {name: modalName})).toBeVisible();
await pw.wait(pw.duration.one_sec);
// * Verify the search input has proper accessibility attributes
const searchInput = dialog.getByLabel('Search for people or groups');
await expect(searchInput).toBeVisible();
await expect(searchInput).toHaveAttribute('aria-autocomplete', 'list');
// # Search for a text and navigate with arrow keys
await pw.wait(pw.duration.half_sec);
await searchInput.fill('u');
await pw.wait(pw.duration.half_sec);
// # Navigate down through the list
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');
// * Verify the selected row has the correct class
const selectedRow = dialog.locator('#multiSelectList').locator('.more-modal__row--selected');
await expect(selectedRow).toBeVisible();
// * Verify image alt is displayed for user profile
const avatar = selectedRow.locator('img.Avatar');
await expect(avatar).toHaveAttribute('alt', 'user profile image');
// * Verify screen reader live region exists and has proper attributes
const srOnlyRegion = dialog.locator('.filtered-user-list div.sr-only:not([role="status"])');
await expect(srOnlyRegion).toHaveAttribute('aria-live', 'polite');
await expect(srOnlyRegion).toHaveAttribute('aria-atomic', 'true');
// # Search for an invalid text
await searchInput.fill('somethingwhichdoesnotexist');
// * Check if the no results message is displayed with proper accessibility
const noResultsMessage = dialog.getByText(/No matches found|No results found matching/i);
await expect(noResultsMessage).toBeVisible();
const noResultsWrapper = dialog.locator('.multi-select__wrapper');
await expect(noResultsWrapper).toHaveAttribute('aria-live', 'polite');
},
);
/**
* @objective Verify Add people to Channel dialog passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Add people to Channel dialog',
{tag: ['@accessibility', '@add_people_channel', '@snapshots']},
async ({pw, axe}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, adminUser, adminClient} = await pw.initSetup();
// # Create a channel in the team
const channel = await adminClient.createChannel(
pw.random.channel({
teamId: team.id,
displayName: 'Test Channel',
name: 'test-channel',
}),
);
// # Create additional users and add to team
for (let i = 0; i < 3; i++) {
const newUser = await adminClient.createUser(await pw.random.user(), '', '');
await adminClient.addToTeam(team.id, newUser.id);
}
// # Log in as admin
const {page, channelsPage} = await pw.testBrowser.login(adminUser);
// # Visit the test channel
await channelsPage.goto(team.name, channel.name);
await channelsPage.toBeVisible();
// # Open channel menu and click Members
await channelsPage.centerView.header.openChannelMenu();
const membersMenuItem = page.locator('#channelMembers');
await membersMenuItem.click();
// # Click the Add people button
const membersRhs = page.locator('#rhsContainer');
await expect(membersRhs).toBeVisible();
const addMembers = membersRhs.locator('button.add-members');
await expect(addMembers).toBeEnabled();
await addMembers.click();
// * Verify the Add people dialog is visible
const dialog = page.getByRole('dialog').first();
await expect(dialog).toBeVisible();
await pw.wait(pw.duration.one_sec);
// * Verify aria snapshot of Add people to Channel dialog
await expect(dialog).toMatchAriaSnapshot(`
- dialog "Add people to Test Channel":
- document:
- heading "Add people to Test Channel" [level=1]
- button "Close"
- log
- text: Search for people or groups
- combobox "Search for people or groups"
- button "Cancel"
- button "Add"
`);
// * Analyze the Add people dialog for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include('[role="dialog"]')
.analyze();
// * Should have no violations
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View File

@@ -0,0 +1,135 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify accessibility support in Browse Channels Dialog screen
*/
test(
'MM-T1467 Accessibility Support in Browse Channels Dialog screen',
{tag: ['@accessibility', '@browse_channels']},
async ({pw}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, user, adminClient} = await pw.initSetup();
// # Create two channels with purposes for testing aria-label
const channel1 = await adminClient.createChannel({
team_id: team.id,
name: 'accessibility-' + Date.now(),
display_name: 'Accessibility',
type: 'O',
purpose: 'some purpose',
});
const channel2 = await adminClient.createChannel({
team_id: team.id,
name: 'z-accessibility-' + Date.now(),
display_name: 'Z Accessibility',
type: 'O',
purpose: 'other purpose',
});
// # Log in as regular user
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Open the Browse Channels modal
const dialog = await channelsPage.openBrowseChannelsModal();
// * Verify the search input exists
const searchInput = dialog.searchInput;
await expect(searchInput).toBeVisible();
// # Wait for channel list to load
await dialog.toBeDoneLoading();
// # Hide already joined channels
const hideJoinedCheckbox = dialog.hideJoinedCheckbox;
await hideJoinedCheckbox.click();
// # Focus on Create Channel button and tab through elements
// Tab order: Close → Search → Channel type filter → Hide Archived → Hide Joined → first channel
const createChannelButton = dialog.createNewChannelButton;
await createChannelButton.focus();
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
// * Verify channel name is highlighted and has proper aria-label
await dialog.toHaveChannelAsNthResult(channel1.name, 0);
const channel1AriaLabel = `${channel1.display_name.toLowerCase()}, ${channel1.purpose.toLowerCase()}`;
const channel1Item = dialog.container.getByLabel(channel1AriaLabel);
await expect(channel1Item).toBeVisible();
await expect(channel1Item).toBeFocused();
// # Press Tab again to move to next channel
await page.keyboard.press('Tab');
// * Verify focus moved to next channel
await dialog.toHaveChannelAsNthResult(channel2.name, 1);
const channel2AriaLabel = `${channel2.display_name.toLowerCase()}, ${channel2.purpose.toLowerCase()}`;
const channel2Item = dialog.container.getByLabel(channel2AriaLabel);
await expect(channel2Item).toBeFocused();
},
);
/**
* @objective Verify Browse Channels dialog passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Browse Channels dialog',
{tag: ['@accessibility', '@browse_channels', '@snapshots']},
async ({pw, axe}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, user} = await pw.initSetup();
// # Log in as regular user
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Open the Browse Channels modal
const dialog = await channelsPage.openBrowseChannelsModal();
// * Verify aria snapshot of Browse Channels dialog
await expect(dialog.container).toMatchAriaSnapshot(`
- dialog "Browse Channels":
- document:
- heading "Browse Channels" [level=1]
- button "Create New Channel"
- button "Close"
- textbox "Search Channels"
- /text: \\d+ Results/
- /status: \\d+ Results/
- status: Channel type filter set to All
- button "Channel type filter"
- checkbox "Hide archived channels" [checked]: Hide Archived
- checkbox "Hide joined channels": Hide Joined
- search
`);
// * Analyze the Browse Channels dialog for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include('[role="dialog"]')
.analyze();
// * Should have no violations
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View File

@@ -0,0 +1,136 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify accessibility support in Direct Messages Dialog screen
*/
test(
'MM-T1466 Accessibility Support in Direct Messages Dialog screen',
{tag: ['@accessibility', '@direct_messages']},
async ({pw}) => {
// # Skip test if no license for Guest Accounts
await pw.skipIfNoLicense();
// # Initialize setup with admin and another user
const {team, adminUser, adminClient} = await pw.initSetup();
// # Create additional user and add to team
const user2 = await adminClient.createUser(await pw.random.user(), '', '');
await adminClient.addToTeam(team.id, user2.id);
// # Log in as admin
const {page, channelsPage} = await pw.testBrowser.login(adminUser);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Click on the Write a direct message button to open the Direct Messages dialog
const writeDirectMessageButton = page.getByRole('button', {name: 'Write a direct message'});
await writeDirectMessageButton.click();
// * Verify the Direct Messages dialog is visible
const dialog = page.getByRole('dialog', {name: 'Direct Messages'});
await expect(dialog).toBeVisible();
// * Verify the heading
await expect(dialog.getByRole('heading', {name: 'Direct Messages'})).toBeVisible();
// * Verify the search input has proper accessibility attributes
const searchInput = dialog.getByLabel('Search for people');
await expect(searchInput).toBeVisible();
await expect(searchInput).toHaveAttribute('aria-autocomplete', 'list');
// # Search for a text and navigate with arrow keys
await searchInput.fill('s');
await pw.wait(pw.duration.half_sec);
// # Navigate down through the list
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');
// * Verify the selected row has the correct class
const selectedRow = dialog.locator('#multiSelectList').locator('.more-modal__row--selected');
await expect(selectedRow).toBeVisible();
// * Verify image alt is displayed for user profile
const avatar = selectedRow.locator('img.Avatar');
await expect(avatar).toHaveAttribute('alt', 'user profile image');
// * Verify screen reader live region exists and has proper attributes
const srOnlyRegion = dialog.locator('.filtered-user-list div.sr-only:not([role="status"])');
await expect(srOnlyRegion).toHaveAttribute('aria-live', 'polite');
await expect(srOnlyRegion).toHaveAttribute('aria-atomic', 'true');
// # Search for an invalid text
const invalidSearchTerm = 'somethingwhichdoesnotexist';
await searchInput.clear();
await searchInput.fill(invalidSearchTerm);
await pw.wait(pw.duration.half_sec);
// * Check if the no results message is displayed with proper accessibility
const noResultsWrapper = dialog.locator('.multi-select__wrapper');
await expect(noResultsWrapper).toHaveAttribute('aria-live', 'polite');
await expect(noResultsWrapper).toContainText(`No results found matching ${invalidSearchTerm}`);
},
);
/**
* @objective Verify Direct Messages dialog passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Direct Messages dialog',
{tag: ['@accessibility', '@direct_messages', '@snapshots']},
async ({pw, axe}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, user} = await pw.initSetup();
// # Log in as admin
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Click on the Write a direct message button to open the Direct Messages dialog
const writeDirectMessageButton = page.getByRole('button', {name: 'Write a direct message'});
await writeDirectMessageButton.click();
// * Verify the Direct Messages dialog is visible
const dialog = page.getByRole('dialog', {name: 'Direct Messages'});
await expect(dialog).toBeVisible();
await pw.wait(pw.duration.one_sec);
// * Verify aria snapshot of Direct Messages dialog (key structural elements only)
await expect(dialog).toMatchAriaSnapshot(`
- dialog "Direct Messages":
- document:
- heading "Direct Messages" [level=1]
- button "Close"
- application:
- log
- text: Search for people
- combobox "Search for people"
- button "Go"
`);
// * Analyze the Direct Messages dialog for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include('[role="dialog"]')
// TODO: Address scrollable-region-focusable violation in the Direct Messages dialog
// The multiSelectList and sr-only status elements need to be keyboard accessible
.disableRules(['color-contrast', 'scrollable-region-focusable'])
.analyze();
// * Should have no violations
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View File

@@ -0,0 +1,171 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test.fixme('Base channel accessibility', 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);
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
await channelsPage.postMessage('hello');
// # Analyze the page
// Disable 'color-contrast' to be addressed by MM-53814
const accessibilityScanResults = await axe.builder(page, {disableColorContrast: true}).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('Post actions tab support', async ({pw, axe}) => {
// # Create and sign in a new user
const {user, adminClient} = await pw.initSetup();
const config = await adminClient.getConfig();
const license = await adminClient.getClientLicenseOld();
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
await channelsPage.postMessage('hello');
const post = await channelsPage.getLastPost();
await post.hover();
await post.postMenu.toBeVisible();
// # Open the dot menu
await post.postMenu.dotMenuButton.press('Enter');
// * Dot menu should be visible and have focused
await channelsPage.postDotMenu.toBeVisible();
await expect(channelsPage.postDotMenu.replyMenuItem).toBeFocused();
// # Analyze the page
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include('.MuiList-root.MuiList-padding')
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
// * Should move focus to Forward after arrow down
await channelsPage.postDotMenu.replyMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.forwardMenuItem).toBeFocused();
// * Should move focus to Follow message after arrow down
await channelsPage.postDotMenu.forwardMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.followMessageMenuItem).toBeFocused();
// * Should move focus to Mark as Unread after arrow down
await channelsPage.postDotMenu.followMessageMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.markAsUnreadMenuItem).toBeFocused();
// * Should move focus to Save after arrow down
await channelsPage.postDotMenu.markAsUnreadMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.saveMenuItem).toBeFocused();
// * Should move focus to Remind after arrow down
await channelsPage.postDotMenu.saveMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.remindMenuItem).toBeFocused();
// * Should move focus to Pin to Channel after arrow down
await channelsPage.postDotMenu.remindMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.pinToChannelMenuItem).toBeFocused();
if (config.FeatureFlags.MoveThreadsEnabled && license.IsLicensed === 'true') {
// * Should move focus to Move Thread after arrow down
await channelsPage.postDotMenu.pinToChannelMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.moveThreadMenuItem).toBeFocused();
// * Should move focus to Copy Text after arrow down
await channelsPage.postDotMenu.moveThreadMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.copyTextMenuItem).toBeFocused();
} else {
// * Should move focus to Copy Text after arrow down
await channelsPage.postDotMenu.pinToChannelMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.copyTextMenuItem).toBeFocused();
}
// * Should move focus to Copy Link after arrow down
await channelsPage.postDotMenu.copyTextMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.copyLinkMenuItem).toBeFocused();
// * Should move focus to Edit after arrow down
await channelsPage.postDotMenu.copyLinkMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.editMenuItem).toBeFocused();
// * Should move focus to Delete after arrow down
// "Quarantine for Review" is inserted between Edit and Delete when content flagging is on.
await channelsPage.postDotMenu.editMenuItem.press('ArrowDown');
if (config.ContentFlaggingSettings?.EnableContentFlagging) {
const quarantineMenuItem = page
.getByRole('menu', {name: 'Post extra options'})
.getByRole('menuitem')
.filter({hasText: 'Quarantine for Review'});
await expect(quarantineMenuItem).toBeFocused();
await page.keyboard.press('ArrowDown');
}
await expect(channelsPage.postDotMenu.deleteMenuItem).toBeFocused();
// * Then, should move focus back to Reply after arrow down
await channelsPage.postDotMenu.deleteMenuItem.press('ArrowDown');
await expect(channelsPage.postDotMenu.replyMenuItem).toBeFocused();
// * Should move focus to Delete after arrow uo
await channelsPage.postDotMenu.container.press('ArrowUp');
expect(await channelsPage.postDotMenu.deleteMenuItem).toBeFocused();
// # Set focus to Remind
await channelsPage.postDotMenu.remindMenuItem.focus();
await expect(channelsPage.postDotMenu.remindMenuItem).toBeFocused();
// * Reminder menu should still be hidden
await expect(channelsPage.postReminderMenu.container).toBeHidden();
// # Press arrow right
await channelsPage.postDotMenu.remindMenuItem.press('ArrowRight');
// * Reminder menu should be visible
expect(channelsPage.postReminderMenu.container).toBeVisible();
// * Should have focus on 30 mins after submenu opens
expect(await channelsPage.postReminderMenu.thirtyMinsMenuItem).toBeFocused();
// * Should move focus to 1 hour after arrow down
await channelsPage.postReminderMenu.thirtyMinsMenuItem.press('ArrowDown');
expect(await channelsPage.postReminderMenu.oneHourMenuItem).toBeFocused();
// * Should move focus to 2 hours after arrow down
await channelsPage.postReminderMenu.oneHourMenuItem.press('ArrowDown');
expect(await channelsPage.postReminderMenu.twoHoursMenuItem).toBeFocused();
// * Should move focus to Tomorrow after arrow down
await channelsPage.postReminderMenu.twoHoursMenuItem.press('ArrowDown');
expect(await channelsPage.postReminderMenu.tomorrowMenuItem).toBeFocused();
// * Should move focus to Custom after arrow down
await channelsPage.postReminderMenu.tomorrowMenuItem.press('ArrowDown');
expect(await channelsPage.postReminderMenu.customMenuItem).toBeFocused();
// * Then, should move focus back to 30 mins after arrow down
await channelsPage.postReminderMenu.customMenuItem.press('ArrowDown');
expect(await channelsPage.postReminderMenu.thirtyMinsMenuItem).toBeFocused();
// * Should hide Reminder menu and focus to Remind menu after arrow left
await channelsPage.postReminderMenu.container.press('ArrowLeft');
await expect(channelsPage.postReminderMenu.container).toBeHidden();
await expect(channelsPage.postDotMenu.remindMenuItem).toBeFocused();
// * Should hide Dot menu of Escape
await channelsPage.postDotMenu.container.press('Escape');
await expect(channelsPage.postDotMenu.container).toBeHidden();
});

View File

@@ -0,0 +1,119 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify accessibility support in Invite People Flow
*/
test(
'MM-T1515 Verify Accessibility Support in Invite People Flow',
{tag: ['@accessibility', '@invite_people']},
async ({pw}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, adminUser} = await pw.initSetup();
// # Log in as admin
const {page, channelsPage} = await pw.testBrowser.login(adminUser);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Open team menu and click Invite people
await channelsPage.sidebarLeft.teamMenuButton.click();
await channelsPage.teamMenu.toBeVisible();
const invitePeopleMenuItem = page.locator("#sidebarTeamMenu li:has-text('Invite people')");
await invitePeopleMenuItem.click();
// * Verify the Invite People modal has proper accessibility attributes
const inviteModal = page.getByTestId('invitationModal');
await expect(inviteModal).toBeVisible();
await expect(inviteModal).toHaveAttribute('aria-modal', 'true');
await expect(inviteModal).toHaveAttribute('aria-labelledby', 'invitation_modal_title');
await expect(inviteModal).toHaveAttribute('role', 'dialog');
// * Verify the modal title is visible and contains correct text
const modalTitle = page.locator('#invitation_modal_title');
await expect(modalTitle).toBeVisible();
await expect(modalTitle).toContainText('Invite people to');
// # Get the close button and verify accessibility
const closeButton = inviteModal.locator('button.icon-close');
await expect(closeButton).toHaveAttribute('aria-label', 'Close');
// # Focus on close button and verify tab navigation works
await closeButton.focus();
await page.keyboard.press('Shift+Tab');
await page.keyboard.press('Tab');
// * Verify focus returns to close button
await expect(closeButton).toBeFocused();
},
);
/**
* @objective Verify Invite People dialog passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Invite People dialog',
{tag: ['@accessibility', '@invite_people', '@snapshots']},
async ({pw, axe}) => {
// # Skip test if no license
await pw.skipIfNoLicense();
// # Initialize setup
const {team, user, adminClient} = await pw.initSetup();
// # Turn off email invitations, which change the invite input's placeholder from
// # "Add members" to "Enter a name or email address" in the snapshot below
await adminClient.patchConfig({ServiceSettings: {EnableEmailInvitations: false}});
// # Log in as user
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Open team menu and click Invite people
await channelsPage.sidebarLeft.teamMenuButton.click();
await channelsPage.teamMenu.toBeVisible();
const invitePeopleMenuItem = page.locator("#sidebarTeamMenu li:has-text('Invite people')");
await invitePeopleMenuItem.click();
// * Verify the Invite People modal is visible
const inviteModal = page.getByTestId('invitationModal');
await expect(inviteModal).toBeVisible();
await pw.wait(pw.duration.one_sec);
// * Verify aria snapshot of Invite People dialog (key structural elements only)
await expect(inviteModal).toMatchAriaSnapshot(`
- dialog:
- document:
- heading [level=1]
- button
- text: "To:"
- log
- text: Add members
- combobox "Invite People"
- listbox
- button
- button "Invite" [disabled]
`);
// * Analyze the Invite People dialog for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include('[data-testid="invitationModal"]')
.analyze();
// * Should have no violations
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View File

@@ -0,0 +1,437 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify keyboard navigation through interactive elements in Advanced settings
*/
test(
'navigate on keyboard tab between interactive elements',
{tag: ['@accessibility', '@settings', '@advanced_settings']},
async ({pw}) => {
// # Create and sign in a new user
const {user, adminClient} = await pw.initSetup();
const config = await adminClient.getConfig();
const wysiwygEnabled =
config.FeatureFlags?.WysiwygEditor === true || config.FeatureFlags?.WysiwygEditor === 'true';
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const globalHeader = channelsPage.globalHeader;
const settingsModal = channelsPage.settingsModal;
const advancedSettings = settingsModal.advancedSettings;
// # 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);
// # Click on Advanced tab to open it
await settingsModal.advancedTab.click();
// * Advanced Settings panel should be visible
await expect(advancedSettings.container).toBeVisible();
// # Press Tab to move focus to Send Messages on CTRL+ENTER button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(advancedSettings.ctrlEnterEditButton);
if (wysiwygEnabled) {
// # Press Tab to move focus to Rich text editing button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(advancedSettings.wysiwygEditorEditButton);
}
// # Press Tab to move focus to Enable Post Formatting button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(advancedSettings.postFormattingEditButton);
// # Press Tab to move focus to Enable Join/Leave Messages button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(advancedSettings.joinLeaveEditButton);
// # Press Tab to move focus to Scroll position button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(advancedSettings.scrollPositionEditButton);
// # Press Tab to move focus to Allow message drafts to sync button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(advancedSettings.syncDraftsEditButton);
},
);
/**
* @objective Verify Advanced settings panel passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Advanced settings panel',
{tag: ['@accessibility', '@settings', '@advanced_settings', '@snapshots']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user, adminClient} = await pw.initSetup();
const config = await adminClient.getConfig();
const wysiwygEnabled =
config.FeatureFlags?.WysiwygEditor === true || config.FeatureFlags?.WysiwygEditor === 'true';
// # 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
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.advancedTab.click();
// * Advanced Settings panel should be visible
await expect(settingsModal.advancedSettings.container).toBeVisible();
const wysiwygSnapshot = wysiwygEnabled
? `
- heading "Rich text editing (Beta)" [level=4]
- button "Rich text editing (Beta) Edit"
- text: /(Markdown editing [(]default[)]|Rich text editing.*)/`
: '';
// * Verify aria snapshot of Advanced settings tab
await expect(settingsModal.advancedSettings.container).toMatchAriaSnapshot(`
- tabpanel "advanced":
- heading "Advanced Settings" [level=3]
- heading /Send Messages on (CTRL|⌘)\\+ENTER/ [level=4]
- button /Send Messages on (CTRL|⌘)\\+ENTER Edit/
- text: /(On for all messages|On only for code blocks starting with \`\`\`|Off)/${wysiwygSnapshot}
- heading "Enable Post Formatting" [level=4]
- button "Enable Post Formatting Edit"
- text: /(On|Off)/
- heading "Enable Join/Leave Messages" [level=4]
- button "Enable Join/Leave Messages Edit"
- text: /(On|Off)/
- heading "Scroll position when viewing an unread channel" [level=4]
- button "Scroll position when viewing an unread channel Edit"
- text: /(Start me where I left off|Start me at the newest message)/
- heading "Allow message drafts to sync with the server" [level=4]
- button "Allow message drafts to sync with the server Edit"
- text: /(On|Off)/
`);
// * Analyze the Advanced settings panel for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include(settingsModal.advancedSettings.id)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify Send Messages on CTRL+ENTER section passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Send Messages on CTRL+ENTER section',
{tag: ['@accessibility', '@settings', '@advanced_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.advancedTab.click();
const advancedSettings = settingsModal.advancedSettings;
// # Click Edit on Send Messages section
await advancedSettings.ctrlEnterEditButton.click();
// * Verify aria snapshot of Send Messages section when expanded
await advancedSettings.expandedSection.waitFor();
await expect(advancedSettings.expandedSection).toMatchAriaSnapshot(`
- heading /Send Messages on (CTRL|⌘)\\+ENTER/ [level=4]
- group /Send Messages on (CTRL|⌘)\\+ENTER/:
- text: /Send Messages on (CTRL|⌘)\\+ENTER/
- radio "On for all messages"
- text: On for all messages
- radio /(On only for code blocks starting with \`\`\`|On only for code blocks starting with \\\`\\\`\\\`)/ [checked]
- text: /(On only for code blocks starting with \`\`\`|On only for code blocks starting with \\\`\\\`\\\`)/
- radio "Off"
- text: /Off When enabled, (CTRL|⌘) \\+ ENTER will send the message and ENTER inserts a new line\\./
- separator
- alert
- button "Save"
- button "Cancel"
`);
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe.builder(page).include(advancedSettings.expandedSectionId).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify Enable Post Formatting section passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Enable Post Formatting section',
{tag: ['@accessibility', '@settings', '@advanced_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.advancedTab.click();
const advancedSettings = settingsModal.advancedSettings;
// # Click Edit on Enable Post Formatting section
await advancedSettings.postFormattingEditButton.click();
// * Verify aria snapshot of Enable Post Formatting section when expanded
await advancedSettings.expandedSection.waitFor();
await expect(advancedSettings.expandedSection).toMatchAriaSnapshot(`
- heading "Enable Post Formatting" [level=4]
- group "Enable Post Formatting":
- radio /(On|Off)/ [checked]
- text: /(On|Off)/
- separator
- alert
- button "Save"
- button "Cancel"
`);
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe.builder(page).include(advancedSettings.expandedSectionId).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify Enable Join/Leave Messages section passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Enable Join/Leave Messages section',
{tag: ['@accessibility', '@settings', '@advanced_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.advancedTab.click();
const advancedSettings = settingsModal.advancedSettings;
// # Click Edit on Enable Join/Leave Messages section
await advancedSettings.joinLeaveEditButton.click();
// * Verify aria snapshot of Enable Join/Leave Messages section when expanded
await advancedSettings.expandedSection.waitFor();
await expect(advancedSettings.expandedSection).toMatchAriaSnapshot(`
- heading "Enable Join/Leave Messages" [level=4]
- group "Enable Join/Leave Messages":
- text: Enable Join/Leave Messages
- radio "On" [checked]
- text: "On"
- radio "Off"
- text: /Off When "On", System Messages saying a user has joined or left a channel will be visible\\./
- separator
- alert
- button "Save"
- button "Cancel"
`);
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe.builder(page).include(advancedSettings.expandedSectionId).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify Scroll position section passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Scroll position section',
{tag: ['@accessibility', '@settings', '@advanced_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.advancedTab.click();
const advancedSettings = settingsModal.advancedSettings;
// # Click Edit on Scroll position section
await advancedSettings.scrollPositionEditButton.click();
// * Verify aria snapshot of Scroll position section when expanded
await advancedSettings.expandedSection.waitFor();
await expect(advancedSettings.expandedSection).toMatchAriaSnapshot(`
- heading "Scroll position when viewing an unread channel" [level=4]
- group "Scroll position when viewing an unread channel":
- text: Scroll position when viewing an unread channel
- radio "Start me where I left off" [checked]
- text: Start me where I left off
- radio "Start me at the newest message"
- text: /Start me at the newest message Choose your scroll position when you view an unread channel\\./
- separator
- alert
- button "Save"
- button "Cancel"
`);
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe.builder(page).include(advancedSettings.expandedSectionId).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify message drafts sync section passes accessibility scan and matches aria-snapshot
*/
test(
'accessibility scan and aria-snapshot of Allow message drafts to sync section',
{tag: ['@accessibility', '@settings', '@advanced_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Advanced tab
await settingsModal.advancedTab.click();
const advancedSettings = settingsModal.advancedSettings;
// # Click Edit on Allow message drafts to sync section
await advancedSettings.syncDraftsEditButton.click();
// * Verify aria snapshot of Allow message drafts to sync section when expanded
await advancedSettings.expandedSection.waitFor();
await expect(advancedSettings.expandedSection).toMatchAriaSnapshot(`
- heading "Allow message drafts to sync with the server" [level=4]
- group "Allow message drafts to sync with the server":
- text: Allow message drafts to sync with the server
- radio "On" [checked]
- text: "On"
- radio "Off"
- text: /Off When enabled, message drafts are synced with the server/
- separator
- alert
- button "Save"
- button "Cancel"
`);
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe.builder(page).include(advancedSettings.expandedSectionId).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,385 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test(
'navigate on keyboard tab between interactive elements',
{tag: ['@accessibility', '@settings', '@notification_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;
const notificationsSettings = settingsModal.notificationsSettings;
// # 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 Tab to move focus to Notifications tab
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(settingsModal.notificationsTab);
// # Press Tab to move focus to Learn more link
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.learnMoreText);
// # Press Tab to move focus to Desktop and mobile notifications button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.desktopAndMobileEditButton);
// # Press Tab to move focus to Desktop notification sounds button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.desktopNotificationSoundEditButton);
// # Press Tab to move focus to Email notifications button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.emailEditButton);
// # Press Tab to move focus to Auto-follow threads on channel-wide mentions button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.channelMentionAutoFollowEditButton);
// # Press Tab to move focus to Keywords that trigger notifications button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.keywordsTriggerNotificationsEditButton);
// # Press Tab to move focus to Keywords that get highlighted (without notifications) button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.keywordsGetHighlightedEditButton);
// # Press Tab to move focus to Send a test notification button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.testNotificationButton);
// # Press Tab to move focus to Troubleshooting docs button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(notificationsSettings.troubleshootingDocsButton);
},
);
test(
'accessibility scan and aria-snapshot of Notifications settings panel',
{tag: ['@accessibility', '@settings', '@notification_settings', '@snapshots']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user, adminClient} = await pw.initSetup();
const clientConfig = await adminClient.getClientConfig();
// # 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
await expect(settingsModal.container).toBeVisible();
// * Verify aria snapshot of Notifications settings tab
await expect(settingsModal.notificationsSettings.container).toMatchAriaSnapshot(`
- tabpanel "notifications":
- heading "Notifications" [level=3]
- link "Learn more about notifications":
- /url: https://mattermost.com/pl/about-notifications?utm_source=mattermost&utm_medium=in-product&utm_content=user_settings_notifications&uid=${user.id}&sid=${clientConfig.DiagnosticId}&edition=enterprise&server_version=${clientConfig.Version}
- img
- heading "Desktop and mobile notifications Permission required" [level=4]:
- img
- button "Desktop and mobile notifications Permission required Edit"
- text: Mentions, direct messages, and group messages
- heading "Desktop notification sounds" [level=4]
- button "Desktop notification sounds Edit"
- text: "\\"Bing\\" for messages"
- heading "Email notifications" [level=4]
- button "Email notifications Edit"
- heading "Auto-follow threads on channel-wide mentions" [level=4]
- button "Auto-follow threads on channel-wide mentions Edit"
- text: "On"
- heading "Keywords that trigger notifications" [level=4]
- button "Keywords that trigger notifications Edit"
- text: "\\"@${user.username}\\", \\"@channel\\", \\"@all\\", \\"@here\\""
- heading "Keywords that get highlighted (without notifications)" [level=4]
- button "Keywords that get highlighted (without notifications) Edit"
- heading "Troubleshooting notifications" [level=4]
- paragraph: Not receiving notifications? Start by sending a test notification to all your devices to check if they’re working as expected. If issues persist, explore ways to solve them with troubleshooting steps.
- button "Send a test notification"
- button "Troubleshooting docs 󰏌"
`);
// * Analyze the Notifications settings panel for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include(settingsModal.notificationsSettings.id)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
test(
'accessibility scan and aria-snapshot of Desktop and mobile notifications section',
{tag: ['@accessibility', '@settings', '@notification_settings', '@snapshots']},
async ({pw, axe}) => {
// # Create and sign in a new user
const {user, adminClient} = await pw.initSetup();
// # Send push notifications, without which the mobile settings in this section are replaced
// # by a "not enabled by your system administrator" message
await adminClient.patchConfig({EmailSettings: {SendPushNotifications: true}});
// # 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
await expect(settingsModal.container).toBeVisible();
const notificationsSettings = settingsModal.notificationsSettings;
// # Click Edit on Desktop and mobile notifications section
await notificationsSettings.desktopAndMobileEditButton.click();
// * Verify aria snapshot of Desktop and mobile notifications section when expanded
await notificationsSettings.expandedSection.waitFor();
await expect(notificationsSettings.expandedSection).toMatchAriaSnapshot({
name: 'desktop_and_mobile_section.yml',
});
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include(notificationsSettings.expandedSectionId)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
test(
'accessibility scan and aria-snapshot of Desktop notification sounds section',
{tag: ['@accessibility', '@settings', '@notification_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
const notificationsSettings = settingsModal.notificationsSettings;
// # Click Edit on Desktop notification sounds section
await notificationsSettings.desktopNotificationSoundEditButton.click();
// * Verify aria snapshot of Desktop notification sounds section when expanded
await notificationsSettings.expandedSection.waitFor();
await expect(notificationsSettings.expandedSection).toMatchAriaSnapshot({
name: 'desktop_notification_sounds_section.yml',
});
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include(notificationsSettings.expandedSectionId)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
test(
'accessibility scan and aria-snapshot of Email notifications section',
{tag: ['@accessibility', '@settings', '@notification_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
const notificationsSettings = settingsModal.notificationsSettings;
// # Click Edit on Email notifications section
await notificationsSettings.emailEditButton.click();
// * Verify aria snapshot of Email notifications section when expanded
await notificationsSettings.expandedSection.waitFor();
await expect(notificationsSettings.expandedSection).toMatchAriaSnapshot({
name: 'email_notifications_section.yml',
});
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include(notificationsSettings.expandedSectionId)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
test(
'accessibility scan and aria-snapshot of Keywords that trigger notifications section',
{tag: ['@accessibility', '@settings', '@notification_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
const notificationsSettings = settingsModal.notificationsSettings;
// # Click Edit on Keywords that trigger notifications section
await notificationsSettings.keywordsTriggerNotificationsEditButton.click();
// * Verify aria snapshot of Keywords that trigger notifications section when expanded
await notificationsSettings.expandedSection.waitFor();
await expect(notificationsSettings.expandedSection).toMatchAriaSnapshot(`
- heading "Keywords that trigger notifications" [level=4]
- group "Keywords that trigger notifications":
- checkbox "Your case-sensitive first name \\"${user.first_name}\\""
- text: Your case-sensitive first name "${user.first_name}"
- checkbox "Your non case-sensitive username \\"${user.username}\\""
- text: Your non case-sensitive username "${user.username}"
- checkbox "Channel-wide mentions \\"@channel\\", \\"@all\\", \\"@here\\"" [checked]
- text: Channel-wide mentions "@channel", "@all", "@here"
- checkbox "Other non case-sensitive words, press Tab or use commas to separate keywords:"
- text: "Other non case-sensitive words, press Tab or use commas to separate keywords:"
- log
- combobox "Keywords that trigger notifications"
- text: Notifications are triggered when someone sends a message that includes your username ("@${user.username}") or any of the options selected above.
- separator
- alert
- button "Save"
- button "Cancel"
`);
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include(notificationsSettings.expandedSectionId)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
test(
'accessibility scan and aria-snapshot of Keywords that get highlighted (without notifications) section',
{tag: ['@accessibility', '@settings', '@notification_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
const notificationsSettings = settingsModal.notificationsSettings;
// # Click Edit on Keywords that get highlighted (without notifications) section
await notificationsSettings.keywordsGetHighlightedEditButton.click();
// * Verify aria snapshot of Keywords that get highlighted (without notifications) section when expanded
await notificationsSettings.expandedSection.waitFor();
await expect(notificationsSettings.expandedSection).toMatchAriaSnapshot({
name: 'keywords_that_get_highlighted_section.yml',
});
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include(notificationsSettings.expandedSectionId)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View File

@@ -0,0 +1,22 @@
- heading "Desktop and mobile notifications" [level=4]
- text: 󰀪
- heading "Browser notification permission was denied" [level=4]
- paragraph: You're missing important message and call notifications from Mattermost. To start receiving notifications, please enable notifications for Mattermost in your browser settings.
- button "How to enable notifications"
- group "Send notifications for:":
- radio "All new messages"
- radio "Mentions, direct messages, and group messages" [checked]
- text: Mentions, direct messages, and group messages
- radio "Nothing"
- checkbox "Notify me about replies to threads I'm following" [checked]
- text: Notify me about replies to threads I'm following
- separator
- checkbox "Use different settings for my mobile devices"
- text: "Use different settings for my mobile devices Trigger mobile notifications when I am:"
- log
- text: Online, away, or offline
- combobox "Trigger mobile notifications when I am:"
- separator
- alert
- button "Save"
- button "Cancel"

View File

@@ -0,0 +1,10 @@
- heading "Desktop notification sounds" [level=4]
- checkbox "Message notification sound" [checked]
- text: Message notification sound
- log
- text: Bing
- combobox "Message notification sound"
- separator
- alert
- button "Save"
- button "Cancel"

View File

@@ -0,0 +1,16 @@
- heading "Email notifications" [level=4]
- group:
- group:
- radio "On" [checked]
- text: "On"
- radio "Off"
- text: "Off"
- text: When enabled, email notifications are sent for mentions and direct messages when you are offline or away for more than 5 minutes.
- separator
- group:
- checkbox "Notify me about replies to threads I’m following" [checked]
- text: Notify me about replies to threads I’m following
- separator
- alert
- button "Save"
- button "Cancel"

View File

@@ -0,0 +1,9 @@
- heading "Keywords that get highlighted (without notifications)" [level=4]
- text: "Enter non case-sensitive keywords, press Tab or use commas to separate them: option , selected.Select is focused ,type to refine list, press Down to open the menu, press left to focus selected values"
- log
- combobox
- text: These keywords will be shown to you with a highlight when anyone sends a message that includes them.
- separator
- alert
- button "Save"
- button "Cancel"

View File

@@ -0,0 +1,307 @@
// 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 through the tabs plugins add after Advanced, Calls among them
const builtInTabs = 4;
const lastTab = settingsModal.tabs.last();
for (let i = 0; i < (await settingsModal.tabs.count()) - builtInTabs; i++) {
await page.keyboard.press('ArrowDown');
}
await pw.toBeFocusedWithFocusVisible(lastTab);
// # 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 wraps to the last tab
await page.keyboard.press('ArrowUp');
await pw.toBeFocusedWithFocusVisible(lastTab);
});
/**
* @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

@@ -0,0 +1,210 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify keyboard navigation works correctly through all interactive elements in the Sidebar settings panel
*/
test(
'navigate on keyboard tab between interactive elements',
{tag: ['@accessibility', '@settings', '@sidebar_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;
const sidebarSettings = settingsModal.sidebarSettings;
// # 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 Tab to move focus to Notifications tab
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(settingsModal.notificationsTab);
// # Press ArrowDown to navigate to Display tab
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(settingsModal.displayTab);
// # Press ArrowDown to navigate to Sidebar tab
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(settingsModal.sidebarTab);
// # Press Tab to move focus to Group unread channels separately button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(sidebarSettings.groupUnreadEditButton);
// # Press Tab to move focus to Number of direct messages to show button
await page.keyboard.press('Tab');
await pw.toBeFocusedWithFocusVisible(sidebarSettings.limitVisibleDMsEditButton);
},
);
/**
* @objective Verify the Sidebar settings panel passes accessibility scan and matches aria snapshot
*/
test(
'accessibility scan and aria-snapshot of Sidebar settings panel',
{tag: ['@accessibility', '@settings', '@sidebar_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Sidebar tab
await settingsModal.openSidebarTab();
// * Verify aria snapshot of Sidebar settings tab
await expect(settingsModal.sidebarSettings.container).toMatchAriaSnapshot(`
- tabpanel "sidebar":
- heading "Sidebar Settings" [level=3]
- heading "Group unread channels separately" [level=4]
- button "Group unread channels separately Edit": Edit
- text: "Off"
- heading "Number of direct messages to show" [level=4]
- button "Number of direct messages to show Edit": Edit
- text: "40"
`);
// * Analyze the Sidebar settings panel for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include(settingsModal.sidebarSettings.id)
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify the Group unread channels separately section passes accessibility scan when expanded
*/
test(
'accessibility scan and aria-snapshot of Group unread channels separately section',
{tag: ['@accessibility', '@settings', '@sidebar_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Sidebar tab
await settingsModal.openSidebarTab();
const sidebarSettings = settingsModal.sidebarSettings;
// # Click Edit on Group unread channels separately section
await sidebarSettings.groupUnreadEditButton.click();
// * Verify aria snapshot of Group unread channels separately section when expanded
await sidebarSettings.expandedSection.waitFor();
await expect(sidebarSettings.expandedSection).toMatchAriaSnapshot({
name: 'group_unread_channels_section.yml',
});
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe.builder(page).include(sidebarSettings.expandedSectionId).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify the Number of direct messages to show section passes accessibility scan when expanded
*/
test(
'accessibility scan and aria-snapshot of Number of direct messages to show section',
{tag: ['@accessibility', '@settings', '@sidebar_settings', '@snapshots']},
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 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
await expect(settingsModal.container).toBeVisible();
// # Open Sidebar tab
await settingsModal.openSidebarTab();
const sidebarSettings = settingsModal.sidebarSettings;
// # Click Edit on Number of direct messages to show section
await sidebarSettings.limitVisibleDMsEditButton.click();
// * Verify aria snapshot of Number of direct messages to show section when expanded
await sidebarSettings.expandedSection.waitFor();
await expect(sidebarSettings.expandedSection).toMatchAriaSnapshot({
name: 'number_of_direct_messages_section.yml',
});
// # Analyze the expanded section for accessibility issues
const accessibilityScanResults = await axe
.builder(page)
.include(sidebarSettings.expandedSectionId)
.exclude('#react-select-2-input') // Exclude react-select input that has a known accessibility issue
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View File

@@ -0,0 +1,9 @@
- heading "Group unread channels separately" [level=4]
- group "Group unread channels separately":
- radio "On"
- radio "Off" [checked]
- text: Off When enabled, all unread channels and direct messages will be grouped together in the sidebar.
- separator
- alert
- button "Save"
- button "Cancel"

View File

@@ -0,0 +1,10 @@
- heading "Number of direct messages to show" [level=4]
- group "Number of direct messages to show":
- text: /Number of direct messages to show option \d+, selected\.Select is focused , press Down to open the menu,/
- log
- combobox
- text: You can also change these settings in the direct messages sidebar menu.
- separator
- alert
- button "Save"
- button "Cancel"

View File

@@ -0,0 +1,326 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify arrow key navigation through all menu items in the team menu
*/
test(
'navigate using arrow keys between menu items',
{tag: ['@accessibility', '@team_menu', '@keyboard_navigation']},
async ({pw}) => {
// # Initialize setup and make the user a team admin to see all menu items
const {user, team, adminClient} = await pw.initSetup();
await adminClient.updateTeamMemberSchemeRoles(team.id, user.id, true, true);
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus on team menu button and press Enter to open the menu
await sidebarLeft.teamMenuButton.focus();
await page.keyboard.press('Enter');
await pw.logFocusedElement(page);
// * Team menu should be visible and focus should be on first menu item - "Invite people"
await teamMenu.toBeVisible();
await pw.toBeFocusedWithFocusVisible(teamMenu.invitePeople);
// # Press ArrowDown to move focus to "Team settings" menu item
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(teamMenu.teamSettings);
// # Press ArrowDown to move focus to "Manage members" menu item
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(teamMenu.manageMembers);
// # Press ArrowDown to move focus to "Leave team" menu item
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(teamMenu.leaveTeam);
// # Press ArrowDown to move focus to "Create a team" menu item
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(teamMenu.createTeam);
// # Press ArrowDown to move focus to "Learn about teams" menu item
await page.keyboard.press('ArrowDown');
await pw.toBeFocusedWithFocusVisible(teamMenu.learnAboutTeams);
},
);
/**
* @objective Verify Tab key navigation escapes menu and moves to next focusable element
*/
test(
'navigate using Tab key to escape menu',
{tag: ['@accessibility', '@team_menu', '@keyboard_navigation']},
async ({pw}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
const {page, channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Set focus on team menu button and press Enter to open the menu
await sidebarLeft.teamMenuButton.focus();
await page.keyboard.press('Enter');
// * Team menu should be visible and focus should be on first menu item
await teamMenu.toBeVisible();
await pw.toBeFocusedWithFocusVisible(teamMenu.invitePeople);
// # Press Tab to exit menu and move to next focusable element outside menu
await page.keyboard.press('Tab');
// * Team menu should be closed and focus should move to next focusable element
await expect(teamMenu.container).not.toBeVisible();
// * Focus should be on the next focusable element - "Browse or create channels" button
await expect(sidebarLeft.teamMenuButton).not.toBeFocused();
await expect(sidebarLeft.browseOrCreateChannelButton).toBeFocused();
},
);
/**
* @objective Verify menu items can be activated using Enter and Space keys
*/
test(
'activate menu items using Enter and Space keys',
{tag: ['@accessibility', '@team_menu', '@keyboard_navigation']},
async ({pw}) => {
// # Initialize setup and make the user a team admin to see all menu items
const {user, team, adminClient} = await pw.initSetup();
await adminClient.updateTeamMemberSchemeRoles(team.id, user.id, true, true);
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on team menu button to open the menu
await sidebarLeft.teamMenuButton.focus();
await page.keyboard.press('Enter');
// * Team menu should be visible
await teamMenu.toBeVisible();
// # Set focus on "Team settings" menu item and press Enter
await teamMenu.teamSettings.focus();
await page.keyboard.press('Enter');
// * Team settings modal should open (wait for navigation or modal)
await channelsPage.teamSettingsModal.toBeVisible();
// # Close the modal using Escape key
await page.keyboard.press('Escape');
await expect(channelsPage.teamSettingsModal.container).not.toBeVisible();
// # Reopen team menu
await sidebarLeft.teamMenuButton.focus();
await page.keyboard.press('Enter');
await teamMenu.toBeVisible();
// # Set focus on "Learn about teams" menu item and press Space
await teamMenu.invitePeople.focus();
await page.keyboard.press(' ');
// * Should navigate to learn about teams documentation or open modal
const invitePeopleModal = await channelsPage.getInvitePeopleModal(team.display_name);
await invitePeopleModal.toBeVisible();
},
);
/**
* @objective Verify Escape key closes the team menu
*/
test('close menu using Escape key', {tag: ['@accessibility', '@team_menu', '@keyboard_navigation']}, async ({pw}) => {
// # Create and sign in a new user
const {user} = await pw.initSetup();
const {page, channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on team menu button to open the menu
await sidebarLeft.teamMenuButton.click();
// * Team menu should be visible
await teamMenu.toBeVisible();
// # Press Escape key
await page.keyboard.press('Escape');
// * Team menu should be closed
await expect(teamMenu.container).not.toBeVisible();
// * Focus should return to the team menu button
await pw.toBeFocusedWithFocusVisible(sidebarLeft.teamMenuButton);
});
/**
* @objective Verify team menu accessibility compliance and aria-snapshot structure
*/
test(
'accessibility scan and aria-snapshot of team menu',
{tag: ['@accessibility', '@team_menu', '@snapshots']},
async ({pw, axe}) => {
// # Initialize setup and make the user a team admin to see all menu items
const {user, team, adminClient} = await pw.initSetup();
await adminClient.updateTeamMemberSchemeRoles(team.id, user.id, true, true);
// # Log in a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on team menu button to open the menu
await sidebarLeft.teamMenuButton.click();
// * Team menu should be visible
await teamMenu.toBeVisible();
// * Verify aria snapshot of team menu structure
await expect(teamMenu.container).toMatchAriaSnapshot();
// # Analyze the team menu for accessibility issues
const accessibilityScanResults = await axe
.builder(page, {disableColorContrast: true})
.include(teamMenu.getContainerId())
.analyze();
// * Should have no violations
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);
/**
* @objective Verify menu items have proper ARIA attributes for screen readers
*/
test(
'verify ARIA attributes and screen reader support',
{tag: ['@accessibility', '@team_menu', '@screen_reader']},
async ({pw}) => {
// # Initialize setup and make the user a team admin to see all menu items
const {user, team, adminClient} = await pw.initSetup();
await adminClient.updateTeamMemberSchemeRoles(team.id, user.id, true, true);
// # Log in a user in new browser context
const {channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on team menu button to open the menu
await sidebarLeft.teamMenuButton.click();
// * Team menu should be visible
await teamMenu.toBeVisible();
// * Verify menu container has correct role
await expect(teamMenu.container).toHaveAttribute('role', 'menu');
// * Verify menu items have correct roles and attributes
await expect(teamMenu.invitePeople).toHaveAttribute('role', 'menuitem');
await expect(teamMenu.invitePeople).toHaveAttribute('aria-haspopup', 'dialog');
await expect(teamMenu.teamSettings).toHaveAttribute('role', 'menuitem');
await expect(teamMenu.teamSettings).toHaveAttribute('aria-haspopup', 'dialog');
await expect(teamMenu.manageMembers).toHaveAttribute('role', 'menuitem');
await expect(teamMenu.manageMembers).toHaveAttribute('aria-haspopup', 'dialog');
await expect(teamMenu.leaveTeam).toHaveAttribute('role', 'menuitem');
await expect(teamMenu.leaveTeam).toHaveAttribute('aria-haspopup', 'dialog');
await expect(teamMenu.createTeam).toHaveAttribute('role', 'menuitem');
await expect(teamMenu.learnAboutTeams).toHaveAttribute('role', 'menuitem');
// * Verify menu items are focusable
await expect(teamMenu.invitePeople).toHaveAttribute('tabindex', '0');
// * Verify menu items have accessible names
await expect(teamMenu.invitePeople).toHaveAccessibleName('Invite people Add or invite people to the team');
await expect(teamMenu.teamSettings).toHaveAccessibleName('Team settings');
await expect(teamMenu.manageMembers).toHaveAccessibleName('Manage members');
await expect(teamMenu.leaveTeam).toHaveAccessibleName('Leave team');
await expect(teamMenu.createTeam).toHaveAccessibleName('Create a team');
await expect(teamMenu.learnAboutTeams).toHaveAccessibleName('Learn about teams');
},
);
/**
* @objective Verify menu items with icons have proper visual indicators and alt text
*/
test(
'verify visual indicators and icon accessibility',
{tag: ['@accessibility', '@team_menu', '@visual_indicators']},
async ({pw}) => {
// # Initialize setup and make the user a team admin to see all menu items
const {user, team, adminClient} = await pw.initSetup();
await adminClient.updateTeamMemberSchemeRoles(team.id, user.id, true, true);
// # Log in a user in new browser context
const {channelsPage} = await pw.testBrowser.login(user);
const sidebarLeft = channelsPage.sidebarLeft;
const teamMenu = channelsPage.teamMenu;
// # Visit a default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Click on team menu button to open the menu
await sidebarLeft.teamMenuButton.click();
// * Team menu should be visible
await teamMenu.toBeVisible();
// * Verify icons have aria-hidden attribute (decorative)
const menuItems = [
teamMenu.invitePeople,
teamMenu.teamSettings,
teamMenu.manageMembers,
teamMenu.leaveTeam,
teamMenu.createTeam,
teamMenu.learnAboutTeams,
];
for (const menuItem of menuItems) {
// * Each menu item should contain an SVG icon with aria-hidden
const icon = menuItem.locator('svg').first();
await expect(icon).toHaveAttribute('aria-hidden', 'true');
// * Menu item should have visible focus indicator on focus
await menuItem.focus();
// * Verify the menu item is focused and has focus-visible styles
await pw.toBeFocusedWithFocusVisible(menuItem);
}
},
);

View File

@@ -0,0 +1,9 @@
- menu:
- menuitem "Invite people Add or invite people to the team"
- menuitem "Team settings"
- menuitem "Manage members"
- menuitem "Leave team"
- separator
- menuitem "Create a team"
- separator
- menuitem "Learn about teams"

View File

@@ -0,0 +1,118 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test('Theme settings should be keyboard accessible', async ({axe, pw}) => {
const {user} = await pw.initSetup();
// # Log in as a user in new browser context
const {page, channelsPage} = await pw.testBrowser.login(user);
// # Initialize Axe
const ab = axe.builder(page).disableRules([
'color-contrast',
// Known issue: These fail due to the way we've grouped plugin setting tabs together in the LHS
'aria-required-children',
'aria-required-parent',
]);
// # Visit default channel page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Open settings modal
const settingsModal = await channelsPage.globalHeader.openSettings();
// * The settings modal should have no accessibility violations
let accessibilityScanResults = await ab.analyze();
expect(accessibilityScanResults.violations).toHaveLength(0);
// # Open display tab
await settingsModal.container.focus();
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('ArrowDown');
// * The display tab should be open
const {displaySettings} = settingsModal;
await displaySettings.toBeVisible();
// * The display tab should have no accessibility violations
accessibilityScanResults = await ab.analyze();
expect(accessibilityScanResults.violations).toHaveLength(0);
// # Open the theme section
await page.keyboard.press('Tab');
await page.keyboard.press('Space');
// * The theme section should be open
await displaySettings.verifySectionIsExpanded('theme');
// * The Premade Themes option should be focused
await expect(page.getByLabel('Premade Themes')).toBeFocused();
// * The theme section for premade themes should have no accessibility violations
accessibilityScanResults = await ab.analyze();
expect(accessibilityScanResults.violations).toHaveLength(0);
// * Should be able to tab through the options
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Denim'})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Sapphire'})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Quartz'})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Indigo'})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Onyx'})).toBeFocused();
await page.keyboard.press('Tab');
// Note: There's no "Apply to all your teams" option because this user is only on one team
await expect(page.getByRole('link', {name: 'See other themes'})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Save'})).toBeFocused();
// # Go back to the premade/custom option
await page.getByLabel('Premade Themes').focus();
// # Switch to the custom theme section
await page.keyboard.press('ArrowDown');
// * The Custom Theme option should be focused
await expect(page.getByLabel('Custom Theme')).toBeFocused();
// * Check the ARIA of the Sidebar Styles section
await page.keyboard.press('Tab');
const sidebarStyles = page.getByRole('button', {name: 'Sidebar Styles'});
await expect(sidebarStyles).toBeFocused();
await expect(sidebarStyles).toHaveAttribute('aria-expanded', 'false');
// * Check that we can tab over the collapsed section to the next section and then back again
await page.keyboard.press('Tab');
await expect(page.getByRole('button', {name: 'Center Channel Styles'})).toBeFocused();
await page.keyboard.press('Shift+Tab');
await expect(sidebarStyles).toBeFocused();
// * Check that we can expand the section
await page.keyboard.press('Enter');
await expect(sidebarStyles).toHaveAttribute('aria-expanded', 'true');
// # Wait for the expanding animation to be open
await expect(page.getByLabel('Sidebar Styles')).toHaveCSS('overflow-y', 'visible');
// * Check that we can tab through color pickers
await page.keyboard.press('Tab');
await expect(page.getByLabel('Sidebar BG', {exact: true})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByLabel('Sidebar Text', {exact: true})).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByLabel('Sidebar Header BG')).toBeFocused();
// * The theme section for custom themes should have no accessibility violations
accessibilityScanResults = await ab.analyze();
expect(accessibilityScanResults.violations).toHaveLength(0);
});

View File

@@ -0,0 +1,84 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test.beforeEach(async ({pw}) => {
const {adminClient} = await pw.getAdminClient();
await adminClient.patchConfig({
TeamSettings: {EnableOpenServer: true},
SamlSettings: {Enable: false},
LdapSettings: {Enable: false},
});
});
test('/login accessibility quick check', async ({pw, axe}) => {
// Set up the page not to redirect to the landing page
await pw.hasSeenLandingPage();
// # Go to login page
await pw.loginPage.goto();
await pw.loginPage.toBeVisible();
// # Analyze the page
const accessibilityScanResults = await axe.builder(pw.loginPage.page).analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('/login accessibility tab support', async ({pw}) => {
// Set up the page not to redirect to the landing page
await pw.hasSeenLandingPage();
// # Go to login page
await pw.loginPage.goto();
await pw.loginPage.toBeVisible();
// * Should have focused at login input on page load
expect(await pw.loginPage.loginInput).toBeFocused();
// * Should move focus to password input after tab
await pw.loginPage.loginInput.press('Tab');
expect(await pw.loginPage.passwordInput).toBeFocused();
// * Should move focus to password toggle button after tab
await pw.loginPage.passwordInput.press('Tab');
expect(await pw.loginPage.passwordToggleButton).toBeFocused();
// * Should move focus to forgot password link after tab
await pw.loginPage.passwordToggleButton.press('Tab');
expect(await pw.loginPage.forgotPasswordLink).toBeFocused();
// * Should move focus to forgot password link after tab
await pw.loginPage.forgotPasswordLink.press('Tab');
expect(await pw.loginPage.signInButton).toBeFocused();
// * Should move focus to about link after tab
await pw.loginPage.signInButton.press('Tab');
expect(await pw.loginPage.footer.aboutLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await pw.loginPage.footer.aboutLink.press('Tab');
expect(await pw.loginPage.footer.privacyPolicyLink).toBeFocused();
// * Should move focus to terms link after tab
await pw.loginPage.footer.privacyPolicyLink.press('Tab');
expect(await pw.loginPage.footer.termsLink).toBeFocused();
// * Should move focus to help link after tab
await pw.loginPage.footer.termsLink.press('Tab');
expect(await pw.loginPage.footer.helpLink).toBeFocused();
// # Move focus to login input
await pw.loginPage.loginInput.focus();
expect(await pw.loginPage.loginInput).toBeFocused();
// * Should move focus to login body after shift+tab
await pw.loginPage.loginInput.press('Shift+Tab');
expect(await pw.loginPage.createAccountLink).toBeFocused();
// * Should move focus to login body after tab
await pw.loginPage.createAccountLink.press('Shift+Tab');
expect(await pw.loginPage.header.logo).toBeFocused();
});

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test('/reset_password accessibility quick check', async ({pw, axe}) => {
// Set up the page not to redirect to the landing page
await pw.hasSeenLandingPage();
// # Go to reset password page
await pw.resetPasswordPage.goto();
await pw.resetPasswordPage.toBeVisible();
// # Analyze the page
const accessibilityScanResults = await axe
.builder(pw.resetPasswordPage.page, {disableColorContrast: true})
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('/reset_password accessibility tab support', async ({pw}) => {
// Set up the page not to redirect to the landing page
await pw.hasSeenLandingPage();
// # Go to reset password page
await pw.resetPasswordPage.goto();
await pw.resetPasswordPage.toBeVisible();
// * Should have focused at email input on page load
expect(await pw.resetPasswordPage.emailInput).toBeFocused();
// * Should move focus to reset button after tab
await pw.resetPasswordPage.emailInput.press('Tab');
expect(await pw.resetPasswordPage.resetButton).toBeFocused();
// * Should move focus to about link after tab
await pw.resetPasswordPage.resetButton.press('Tab');
expect(await pw.resetPasswordPage.footer.aboutLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await pw.resetPasswordPage.footer.aboutLink.press('Tab');
expect(await pw.resetPasswordPage.footer.privacyPolicyLink).toBeFocused();
// * Should move focus to terms link after tab
await pw.resetPasswordPage.footer.privacyPolicyLink.press('Tab');
expect(await pw.resetPasswordPage.footer.termsLink).toBeFocused();
// * Should move focus to help link after tab
await pw.resetPasswordPage.footer.termsLink.press('Tab');
expect(await pw.resetPasswordPage.footer.helpLink).toBeFocused();
// # Move focus to email input
await pw.resetPasswordPage.emailInput.focus();
expect(await pw.resetPasswordPage.emailInput).toBeFocused();
// * Should move focus to back button after shift+tab
await pw.resetPasswordPage.emailInput.press('Shift+Tab');
expect(await pw.resetPasswordPage.header.backButton).toBeFocused();
});

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
test('/signup_user_complete accessibility quick check', async ({pw, axe}) => {
// Set up the page not to redirect to the landing page
await pw.hasSeenLandingPage();
// # Go to reset password page
await pw.signupPage.goto();
await pw.signupPage.toBeVisible();
// # Analyze the page
const accessibilityScanResults = await axe
.builder(pw.signupPage.page, {disableColorContrast: true, disableLinkInTextBlock: true})
.analyze();
// * Should have no violation
expect(accessibilityScanResults.violations).toHaveLength(0);
});
test('/signup_user_complete accessibility tab support', async ({pw}, testInfo) => {
// Set up the page not to redirect to the landing page
await pw.hasSeenLandingPage();
// # Go to reset password page
await pw.signupPage.goto();
await pw.signupPage.toBeVisible();
// * Should have focused at email input on page load
expect(await pw.signupPage.emailInput).toBeFocused();
// * Should move focus to username input after tab
await pw.signupPage.emailInput.press('Tab');
expect(await pw.signupPage.usernameInput).toBeFocused();
// * Should move focus to password input after tab
await pw.signupPage.usernameInput.press('Tab');
expect(await pw.signupPage.passwordInput).toBeFocused();
// * Should move focus to password toggle button after tab
await pw.signupPage.passwordInput.press('Tab');
expect(await pw.signupPage.passwordToggleButton).toBeFocused();
// * Should move focus to terms and privacy checkbox after tab
await pw.signupPage.passwordToggleButton.press('Tab');
expect(await pw.signupPage.termsAndPrivacyCheckBox).toBeFocused();
// * Should move focus to acceptable use policy link after tab
await pw.signupPage.termsAndPrivacyCheckBox.press('Tab');
expect(await pw.signupPage.termsAndPrivacyAcceptableUsePolicyLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await pw.signupPage.termsAndPrivacyAcceptableUsePolicyLink.press('Tab');
expect(await pw.signupPage.termsAndPrivacyPrivacyPolicyLink).toBeFocused();
// * Should move focus to about link after tab (skips disabled create account button)
await pw.signupPage.termsAndPrivacyPrivacyPolicyLink.press('Tab');
expect(await pw.signupPage.footer.aboutLink).toBeFocused();
// * Should move focus to privacy policy link after tab
await pw.signupPage.footer.aboutLink.press('Tab');
expect(await pw.signupPage.footer.privacyPolicyLink).toBeFocused();
// * Should move focus to terms link after tab
await pw.signupPage.footer.privacyPolicyLink.press('Tab');
expect(await pw.signupPage.footer.termsLink).toBeFocused();
// * Should move focus to help link after tab
await pw.signupPage.footer.termsLink.press('Tab');
expect(await pw.signupPage.footer.helpLink).toBeFocused();
// # Move focus to email input
await pw.signupPage.emailInput.focus();
expect(await pw.signupPage.emailInput).toBeFocused();
if (testInfo.project.name === 'ipad') {
// * Should move focus to header back button after shift+tab
await pw.signupPage.bodyCard.press('Shift+Tab');
expect(await pw.signupPage.header.backButton).toBeFocused();
// * Should move focus to log in link after shift+tab
await pw.signupPage.header.backButton.press('Shift+Tab');
expect(await pw.signupPage.loginLink).toBeFocused();
// * Should move focus to header logo after shift+tab
await pw.signupPage.loginLink.press('Shift+Tab');
expect(await pw.signupPage.header.logo).toBeFocused();
} else {
// * Should move focus to log in link after shift+tab
await pw.signupPage.bodyCard.press('Shift+Tab');
expect(await pw.signupPage.loginLink).toBeFocused();
// * Should move focus to header back button after shift+tab
await pw.signupPage.loginLink.press('Shift+Tab');
expect(await pw.signupPage.header.backButton).toBeFocused();
// * Should move focus to header logo after shift+tab
await pw.signupPage.header.backButton.press('Shift+Tab');
expect(await pw.signupPage.header.logo).toBeFocused();
}
});