(test): fix flaky and migrate to playwright (#35156)

This commit is contained in:
sabril 2026-02-04 12:21:17 +08:00 committed by GitHub
parent 51e6431275
commit e499decea0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 630 additions and 291 deletions

View file

@ -1,222 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @enterprise @accessibility
import {Channel} from '@mattermost/types/channels';
import {Team} from '@mattermost/types/teams';
import {UserProfile} from '@mattermost/types/users';
import * as TIMEOUTS from '../../../../fixtures/timeouts';
describe('Verify Accessibility Support in Modals & Dialogs', () => {
let testTeam: Team;
let testChannel: Channel;
let testUser: UserProfile;
let selectedRowText: string;
before(() => {
// * Check if server has license for Guest Accounts
cy.apiRequireLicenseForFeature('GuestAccounts');
cy.apiInitSetup({userPrefix: 'user000a'}).then(({team, channel, user}) => {
testTeam = team;
testChannel = channel;
testUser = user;
cy.apiCreateUser({prefix: 'user000b'}).then(({user: newUser}) => {
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
cy.apiAddUserToChannel(testChannel.id, newUser.id);
});
});
});
});
beforeEach(() => {
// # Login as sysadmin and visit the town-square
cy.apiAdminLogin();
cy.visit(`/${testTeam.name}/channels/town-square`);
});
it('MM-T1466 Accessibility Support in Direct Messages Dialog screen', () => {
// * Verify the aria-label in create direct message button
cy.uiAddDirectMessage().click();
// * Verify the accessibility support in Direct Messages Dialog
cy.findAllByRole('dialog', {name: 'Direct Messages'}).eq(0).within(() => {
cy.findByRole('heading', {name: 'Direct Messages'});
// * Verify the accessibility support in search input
cy.findByLabelText('Search for people').
should('have.attr', 'aria-autocomplete', 'list');
// # Search for a text and then check up and down arrow
cy.findByLabelText('Search for people').
typeWithForce('s').
wait(TIMEOUTS.HALF_SEC).
typeWithForce('{downarrow}{downarrow}{downarrow}{uparrow}');
cy.get('#multiSelectList').children().eq(2).should('have.class', 'more-modal__row--selected').within(() => {
cy.get('.more-modal__name').invoke('text').then((user) => {
selectedRowText = user.split(' - ')[0].replace('@', '');
});
// * Verify image alt is displayed
cy.get('img.Avatar').should('have.attr', 'alt', 'user profile image');
});
// * Verify if the reader is able to read out the selected row
cy.get('.filtered-user-list div.sr-only:not([role="status"])').
should('have.attr', 'aria-live', 'polite').
and('have.attr', 'aria-atomic', 'true').
invoke('text').then((text) => {
expect(text).equal(selectedRowText);
});
// # Search for an invalid text
const additionalSearchTerm = 'somethingwhichdoesnotexist';
cy.findByLabelText('Search for people').clear().
typeWithForce(additionalSearchTerm).
wait(TIMEOUTS.HALF_SEC);
// * Check if reader can read no results
cy.get('.multi-select__wrapper').should('have.attr', 'aria-live', 'polite').and('have.text', `No results found matching ${additionalSearchTerm}`);
});
});
it('MM-T1467 Accessibility Support in Browse Channels Dialog screen', () => {
function getChannelAriaLabel(channel) {
return channel.display_name.toLowerCase() + ', ' + channel.purpose.toLowerCase();
}
// # Create atleast 2 channels
let otherChannel;
cy.apiCreateChannel(testTeam.id, 'z_accessibility', 'Z Accessibility', 'O', 'other purpose').then(({channel}) => {
otherChannel = channel;
});
cy.apiCreateChannel(testTeam.id, 'accessibility', 'Accessibility', 'O', 'some purpose').then(({channel}) => {
cy.apiLogin(testUser).then(() => {
cy.reload();
// * Verify the aria-label in more public channels button
cy.uiBrowseOrCreateChannel('Browse channels');
// * Verify the accessibility support in More Channels Dialog
cy.findByRole('dialog', {name: 'Browse Channels'}).within(() => {
cy.findByRole('heading', {name: 'Browse Channels'});
// * Verify the accessibility support in search input
cy.findByPlaceholderText('Search channels');
cy.get('#moreChannelsList').should('be.visible').then((el) => {
return el[0].children.length === 2;
});
// # Hide already joined channels
cy.findByText('Hide Joined').click();
// # Focus on the Create Channel button and TAB five time
cy.get('#createNewChannelButton').focus().tab().tab().tab().tab().tab();
// * Verify channel name is highlighted and reader reads the channel name and channel description
cy.get('#moreChannelsList').within(() => {
const selectedChannel = getChannelAriaLabel(channel);
cy.findByLabelText(selectedChannel).should('be.visible').should('be.focused');
});
// * Press Tab again and verify if focus changes to next row
cy.focused().tab();
cy.findByLabelText(getChannelAriaLabel(otherChannel)).should('be.focused');
});
});
});
});
it('MM-T1468 Accessibility Support in Add people to Channel Dialog screen', () => {
// # Add atleast 5 users
for (let i = 0; i < 5; i++) {
cy.apiCreateUser().then(({user}) => {
cy.apiAddUserToTeam(testTeam.id, user.id);
});
}
// # Visit the test channel, and wait for the page to fully load
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
// # Open Add Members Dialog
cy.uiOpenChannelMenu('Members');
cy.uiGetButton('Add').click();
// * Verify the accessibility support in Add people Dialog
cy.findAllByRole('dialog').eq(0).within(() => {
const modalName = `Add people to ${testChannel.display_name}`;
cy.findByRole('heading', {name: modalName});
cy.wait(TIMEOUTS.ONE_SEC);
// * Verify the accessibility support in search input
cy.findByLabelText('Search for people or groups').
should('have.attr', 'aria-autocomplete', 'list');
// # Search for a text and then check up and down arrow
cy.findByLabelText('Search for people or groups').
wait(TIMEOUTS.HALF_SEC).
typeWithForce('u').
wait(TIMEOUTS.HALF_SEC).
typeWithForce('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}');
cy.get('#multiSelectList').
children().eq(2).
should('have.class', 'more-modal__row--selected').
within(() => {
cy.get('.more-modal__name').invoke('text').then((user) => {
selectedRowText = user.split(' - ')[0].replace('@', '');
});
// * Verify image alt is displayed
cy.get('img.Avatar').should('have.attr', 'alt', 'user profile image');
});
// * Verify if the reader is able to read out the selected row
cy.get('.filtered-user-list div.sr-only:not([role="status"])').
should('have.attr', 'aria-live', 'polite').
and('have.attr', 'aria-atomic', 'true').
invoke('text').then((text) => {
// Check that the readout starts with the selected user since it may be followed by
// "Already in Channel" depending on which user was selected
expect(text).to.match(new RegExp(`^${selectedRowText}\\b`));
});
// # Search for an invalid text and check if reader can read no results
cy.findByLabelText('Search for people or groups').
typeWithForce('somethingwhichdoesnotexist').
wait(TIMEOUTS.HALF_SEC);
// * Check if reader can read no results
cy.get('.custom-no-options-message').
should('be.visible').
and('contain', 'No matches found - Invite them to the team');
});
});
it('MM-T1515 Verify Accessibility Support in Invite People Flow', () => {
// # Open Invite People
cy.uiGetLHSHeader().click();
cy.get("#sidebarTeamMenu li:contains('Invite people')").should('be.visible').click();
// * Verify accessibility support in Invite People Dialog
cy.findByTestId('invitationModal').should('have.attr', 'aria-modal', 'true').and('have.attr', 'aria-labelledby', 'invitation_modal_title').and('have.attr', 'role', 'dialog');
cy.get('#invitation_modal_title').should('be.visible').and('contain.text', 'Invite people to');
// # Press tab
cy.get('button.icon-close').focus().tab({shift: true}).tab();
// * Verify tab focuses on close button
cy.get('button.icon-close').should('have.attr', 'aria-label', 'Close').and('be.focused');
});
});

View file

@ -1,69 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @files_and_attachments
import * as TIMEOUTS from '../../../fixtures/timeouts';
describe('Edit Message with Attachment', () => {
before(() => {
// # Enable Link Previews
cy.apiUpdateConfig({
ServiceSettings: {
EnableLinkPreviews: true,
},
});
// # Create new team and new user and visit off-topic channel
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
cy.visit(offTopicUrl);
});
});
it('MM-T2268 - Edit Message with Attachment', () => {
// # Upload file
cy.get('#fileUploadInput').attachFile('mattermost-icon.png');
// # Wait for file to upload
cy.wait(TIMEOUTS.TWO_SEC);
// # Post message
cy.postMessage('Test');
cy.getLastPost().within(() => {
// * Posted message should be correct
cy.get('.post-message__text').should('contain.text', 'Test');
// * Attachment should exist
cy.get('.file-view--single').should('exist');
// * Edited indicator should not exist
cy.get('.post-edited__indicator').should('not.exist');
});
// # Open the edit dialog
cy.uiGetPostTextBox().type('{uparrow}');
// # Add some more text and save
cy.get('#edit_textbox').type(' with some edit');
cy.get('#edit_textbox').type('{enter}').wait(TIMEOUTS.HALF_SEC);
cy.getLastPost().within(() => {
// * New text should show
cy.get('.post-message__text').should('contain.text', 'Test with some edit');
// * Attachment should still exist
cy.get('.file-view--single').should('exist');
// * Edited indicator should exist
cy.get('.post-edited__indicator').should('exist');
});
});
});

View file

@ -0,0 +1,173 @@
// 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();
// # 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 addButton = page.getByRole('button', {name: 'Add people'});
await addButton.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');
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');
const noResultsMessage = dialog.locator('.no-channel-message .primary-message');
await expect(noResultsMessage).toBeVisible();
await expect(noResultsMessage).toContainText('No results found matching');
},
);
/**
* @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 addButton = page.getByRole('button', {name: 'Add people'});
await addButton.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,147 @@
// 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();
// # Click on Browse or Create Channel button and then Browse Channels
await channelsPage.sidebarLeft.browseOrCreateChannelButton.click();
const browseChannelsMenuItem = page.locator('#browseChannelsMenuItem');
await browseChannelsMenuItem.click();
// * Verify the Browse Channels dialog is visible
const dialog = page.getByRole('dialog', {name: 'Browse Channels'});
await expect(dialog).toBeVisible();
// * Verify the heading
await expect(dialog.getByRole('heading', {name: 'Browse Channels'})).toBeVisible();
// * Verify the search input exists
const searchInput = dialog.getByPlaceholder('Search channels');
await expect(searchInput).toBeVisible();
// # Wait for channel list to load
const channelList = dialog.locator('#moreChannelsList');
await expect(channelList).toBeVisible();
// # Hide already joined channels
const hideJoinedCheckbox = dialog.getByText('Hide Joined');
await hideJoinedCheckbox.click();
// # Focus on Create Channel button and tab through elements
const createChannelButton = dialog.locator('#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');
// * Verify channel name is highlighted and has proper aria-label
const channel1AriaLabel = `${channel1.display_name.toLowerCase()}, ${channel1.purpose.toLowerCase()}`;
const channel1Item = dialog.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
const channel2AriaLabel = `${channel2.display_name.toLowerCase()}, ${channel2.purpose.toLowerCase()}`;
const channel2Item = dialog.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();
// # Click on Browse or Create Channel button and then Browse Channels
await channelsPage.sidebarLeft.browseOrCreateChannelButton.click();
const browseChannelsMenuItem = page.locator('#browseChannelsMenuItem');
await browseChannelsMenuItem.click();
// * Verify the Browse Channels dialog is visible
const dialog = page.getByRole('dialog', {name: 'Browse Channels'});
await expect(dialog).toBeVisible();
await pw.wait(pw.duration.one_sec);
// * Verify aria snapshot of Browse Channels dialog
await expect(dialog).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 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, {disableColorContrast: true})
.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(['scrollable-region-focusable'])
.analyze();
// * Should have no violations
expect(accessibilityScanResults.violations).toHaveLength(0);
},
);

View file

@ -0,0 +1,115 @@
// 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} = await pw.initSetup();
// # 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,59 @@
// 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 users can edit a message that has an attachment,
* the attachment is preserved after edit, and the edited indicator appears.
*/
test('MM-T2268 Edit Message with Attachment', async ({pw}) => {
// # Initialize user and login
const {user} = await pw.initSetup();
const {channelsPage, page} = await pw.testBrowser.login(user);
// # Navigate to channels page
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Post a message with an attachment
await channelsPage.postMessage('Test', ['mattermost.png']);
// # Get the last post and verify content
const post = await channelsPage.getLastPost();
await post.toBeVisible();
// * Verify the posted message is correct
await post.toContainText('Test');
// * Verify attachment exists (image thumbnail)
const attachment = post.container.getByLabel(/file thumbnail/i);
await expect(attachment).toBeVisible();
// * Verify edited indicator does not exist initially
const postId = await post.getId();
await expect(channelsPage.centerView.editedPostIcon(postId)).not.toBeVisible();
// # Focus on the post textbox and press Up arrow to open edit dialog
await channelsPage.centerView.postCreate.input.focus();
await page.keyboard.press('ArrowUp');
// # Verify edit mode is active and add more text
await channelsPage.centerView.postEdit.toBeVisible();
await channelsPage.centerView.postEdit.input.fill('Test with some edit');
await channelsPage.centerView.postEdit.sendMessage();
// # Get the updated post
const updatedPost = await channelsPage.getLastPost();
await updatedPost.toBeVisible();
// * Verify the new text shows
await updatedPost.toContainText('Test with some edit');
// * Verify attachment still exists (image thumbnail)
const updatedAttachment = updatedPost.container.getByLabel(/file thumbnail/i);
await expect(updatedAttachment).toBeVisible();
// * Verify edited indicator now exists
await expect(channelsPage.centerView.editedPostIcon(postId)).toBeVisible();
});