From ecf7cdbdea7b9591daf8bf5189009a33771253fc Mon Sep 17 00:00:00 2001 From: Saturnino Abril Date: Thu, 10 Aug 2023 08:53:13 -0400 Subject: [PATCH] fix(accessibility): tab support at login, reset and signup pages, buttons at ATE and app bar (#24214) --- .../signin_authentication/signup_spec.js | 26 +++--- e2e-tests/playwright/support/test_fixture.ts | 27 +++++- .../support/ui/components/footer.ts | 30 ++++++ .../playwright/support/ui/components/index.ts | 4 + .../support/ui/components/main_header.ts | 24 +++++ .../playwright/support/ui/pages/index.ts | 2 + .../playwright/support/ui/pages/login.ts | 12 ++- .../support/ui/pages/reset_password.ts | 51 ++++++++++ .../playwright/support/ui/pages/signup.ts | 29 +++++- .../channels/intro_channel.spec.ts | 2 +- .../tests/accessibility/common/login.spec.ts | 62 ++++++++++++- .../common/reset_password.spec.ts | 55 +++++++++++ .../common/signup_user_complete.spec.ts | 93 +++++++++++++++++++ .../formatting_bar/formatting_icon.tsx | 3 +- .../channels/src/components/alert_banner.tsx | 6 +- .../__snapshots__/app_bar.test.tsx.snap | 4 + .../app_bar/app_bar_plugin_component.tsx | 12 ++- .../src/components/common/back_button.tsx | 6 +- .../header_footer_template.test.tsx.snap | 40 ++------ .../header_footer_template.tsx | 22 +---- .../channels/src/components/login/login.tsx | 2 - .../post_priority_picker_overlay.tsx | 1 + .../channels/src/components/signup/signup.tsx | 1 + .../components/widgets/inputs/check/index.tsx | 10 +- .../inputs/password_input/password_input.tsx | 2 +- webapp/channels/src/i18n/en.json | 2 + 26 files changed, 441 insertions(+), 87 deletions(-) create mode 100644 e2e-tests/playwright/support/ui/components/footer.ts create mode 100644 e2e-tests/playwright/support/ui/components/main_header.ts create mode 100644 e2e-tests/playwright/support/ui/pages/reset_password.ts create mode 100644 e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts create mode 100644 e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts diff --git a/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js b/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js index e9469fb487..1c4fad4344 100644 --- a/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/signin_authentication/signup_spec.js @@ -45,10 +45,12 @@ describe('Signup Email page', () => { it('should match elements, back button', () => { // * Check elements in the header with back button - cy.get('#back_button').should('be.visible'); - cy.get('#back_button').should('contain', 'Back'); - cy.get('#back_button_icon').should('be.visible'); - cy.get('#back_button_icon').should('have.attr', 'title', 'Back Icon'); + cy.findByTestId('back_button'). + should('be.visible'). + and('have.text', 'Back'); + cy.get('#back_button_icon'). + should('be.visible'). + and('have.attr', 'title', 'Back Icon'); }); it('should match elements, body', () => { @@ -94,20 +96,20 @@ describe('Signup Email page', () => { // * Check elements in the footer cy.get('.hfroute-footer').scrollIntoView().should('be.visible').within(() => { // * Check if about footer link is present - cy.findByText('About').should('exist'). - and('have.attr', 'href', config.SupportSettings.AboutLink || ABOUT_LINK); + cy.findByText('About').should('be.visible'). + should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.AboutLink || ABOUT_LINK}/*`)); // * Check if privacy footer link is present - cy.findByText('Privacy Policy').should('exist'). - and('have.attr', 'href', config.SupportSettings.PrivacyPolicyLink || PRIVACY_POLICY_LINK); + cy.findByText('Privacy Policy').should('be.visible'). + should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.PrivacyPolicyLink || PRIVACY_POLICY_LINK}/*`)); // * Check if terms footer link is present - cy.findByText('Terms').should('exist'). - and('have.attr', 'href', config.SupportSettings.TermsOfServiceLink || TERMS_OF_SERVICE_LINK); + cy.findByText('Terms').should('be.visible'). + should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.TermsOfServiceLink || TERMS_OF_SERVICE_LINK}/*`)); // * Check if help footer link is present - cy.findByText('Help').should('exist'). - and('have.attr', 'href', config.SupportSettings.HelpLink || HELP_LINK); + cy.findByText('Help').should('be.visible'). + should('have.attr', 'href').and('match', new RegExp(`${config.SupportSettings.HelpLink || HELP_LINK}/*`)); const todaysDate = new Date(); const currentYear = todaysDate.getFullYear(); diff --git a/e2e-tests/playwright/support/test_fixture.ts b/e2e-tests/playwright/support/test_fixture.ts index 7d963933d8..f43ba0c825 100644 --- a/e2e-tests/playwright/support/test_fixture.ts +++ b/e2e-tests/playwright/support/test_fixture.ts @@ -18,6 +18,11 @@ type ExtendedFixtures = { pages: typeof pages; }; +type AxeBuilderOptions = { + disableColorContrast?: boolean; + disableLinkInTextBlock?: boolean; +}; + export const test = base.extend({ // eslint-disable-next-line no-empty-pattern axe: async ({}, use) => { @@ -94,15 +99,29 @@ class PlaywrightExtended { } class AxeBuilderExtended { - readonly builder: (page: Page, disableRules?: string[]) => AxeBuilder; + readonly builder: (page: Page, options?: AxeBuilderOptions) => AxeBuilder; // See https://github.com/dequelabs/axe-core/blob/master/doc/API.md#axe-core-tags readonly tags: string[] = ['wcag2a', 'wcag2aa']; constructor() { - // See https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md#wcag-20-level-a--aa-rules - this.builder = (page: Page, disableRules?: string[]) => { - return new AxeBuilder({page}).withTags(this.tags).disableRules(disableRules || []); + this.builder = (page: Page, options: AxeBuilderOptions = {}) => { + // See https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md#wcag-20-level-a--aa-rules + const disabledRules: string[] = []; + + if (options.disableColorContrast) { + // Disabled in pages due to impact to overall theme of Mattermost. + // Option: make use of custom theme to improve color contrast. + disabledRules.push('color-contrast'); + } + + if (options.disableLinkInTextBlock) { + // Disabled in pages due to impact to overall theme of Mattermost. + // Option: make use of custom theme to improve color contrast. + disabledRules.push('link-in-text-block'); + } + + return new AxeBuilder({page}).withTags(this.tags).disableRules(disabledRules); }; } diff --git a/e2e-tests/playwright/support/ui/components/footer.ts b/e2e-tests/playwright/support/ui/components/footer.ts new file mode 100644 index 0000000000..d531e9a585 --- /dev/null +++ b/e2e-tests/playwright/support/ui/components/footer.ts @@ -0,0 +1,30 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, Locator} from '@playwright/test'; + +export default class Footer { + readonly container: Locator; + + readonly copyright; + readonly aboutLink; + readonly privacyPolicyLink; + readonly termsLink; + readonly helpLink; + + constructor(container: Locator) { + this.container = container; + + this.copyright = container.locator('.footer-copyright'); + this.aboutLink = container.locator('text=About'); + this.privacyPolicyLink = container.locator('text=Privacy Policy'); + this.termsLink = container.locator('text=Terms'); + this.helpLink = container.locator('text=Help'); + } + + async toBeVisible() { + await expect(this.copyright).toBeVisible(); + } +} + +export {Footer}; diff --git a/e2e-tests/playwright/support/ui/components/index.ts b/e2e-tests/playwright/support/ui/components/index.ts index ebb3e3b879..806c21aaec 100644 --- a/e2e-tests/playwright/support/ui/components/index.ts +++ b/e2e-tests/playwright/support/ui/components/index.ts @@ -10,7 +10,9 @@ import {ChannelsPost} from './channels/post'; import {ChannelsSidebarLeft} from './channels/sidebar_left'; import {ChannelsSidebarRight} from './channels/sidebar_right'; import {FindChannelsModal} from './channels/find_channels_modal'; +import {Footer} from './footer'; import {GlobalHeader} from './global_header'; +import {MainHeader} from './main_header'; import {PostDotMenu} from './channels/post_dot_menu'; import {DeletePostModal} from './channels/delete_post_modal'; import {PostMenu} from './channels/post_menu'; @@ -26,7 +28,9 @@ const components = { ChannelsSidebarLeft, ChannelsSidebarRight, FindChannelsModal, + Footer, GlobalHeader, + MainHeader, PostDotMenu, DeletePostModal, PostMenu, diff --git a/e2e-tests/playwright/support/ui/components/main_header.ts b/e2e-tests/playwright/support/ui/components/main_header.ts new file mode 100644 index 0000000000..b3e7c99f25 --- /dev/null +++ b/e2e-tests/playwright/support/ui/components/main_header.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, Locator} from '@playwright/test'; + +export default class MainHeader { + readonly container: Locator; + + readonly logo; + readonly backButton; + + constructor(container: Locator) { + this.container = container; + + this.logo = container.locator('.header-logo-link'); + this.backButton = container.getByTestId('back_button'); + } + + async toBeVisible() { + await expect(this.container).toBeVisible(); + } +} + +export {MainHeader}; diff --git a/e2e-tests/playwright/support/ui/pages/index.ts b/e2e-tests/playwright/support/ui/pages/index.ts index 9c872ad03e..f84d4a442d 100644 --- a/e2e-tests/playwright/support/ui/pages/index.ts +++ b/e2e-tests/playwright/support/ui/pages/index.ts @@ -6,6 +6,7 @@ import {BoardsViewPage} from './boards_view'; import {ChannelsPage} from './channels'; import {LandingLoginPage} from './landing_login'; import {LoginPage} from './login'; +import {ResetPasswordPage} from './reset_password'; import {SignupPage} from './signup'; const pages = { @@ -14,6 +15,7 @@ const pages = { ChannelsPage, LandingLoginPage, LoginPage, + ResetPasswordPage, SignupPage, }; diff --git a/e2e-tests/playwright/support/ui/pages/login.ts b/e2e-tests/playwright/support/ui/pages/login.ts index 69db9f59f3..8c8957c280 100644 --- a/e2e-tests/playwright/support/ui/pages/login.ts +++ b/e2e-tests/playwright/support/ui/pages/login.ts @@ -6,6 +6,8 @@ import {expect, Page} from '@playwright/test'; import {AdminConfig} from '@mattermost/types/config'; import {UserProfile} from '@mattermost/types/users'; +import {components} from '@e2e-support/ui/components'; + export default class LoginPage { readonly adminConfig: AdminConfig; @@ -17,6 +19,7 @@ export default class LoginPage { readonly loginInput; readonly loginPlaceholder; readonly passwordInput; + readonly passwordToggleButton; readonly signInButton; readonly createAccountLink; readonly forgotPasswordLink; @@ -24,6 +27,9 @@ export default class LoginPage { readonly fieldWithError; readonly formContainer; + readonly header; + readonly footer; + constructor(page: Page, adminConfig: AdminConfig) { this.page = page; this.adminConfig = adminConfig; @@ -34,16 +40,20 @@ export default class LoginPage { this.title = page.locator('h1:has-text("Log in to your account")'); this.subtitle = page.locator('text=Collaborate with your team in real-time'); - this.bodyCard = page.locator('.login-body-card'); + this.bodyCard = page.locator('.login-body-card-content'); this.loginInput = page.locator('#input_loginId'); this.loginPlaceholder = page.locator(`[placeholder="${loginInputPlaceholder}"]`); this.passwordInput = page.locator('#input_password-input'); + this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'}); this.signInButton = page.locator('button:has-text("Log in")'); this.createAccountLink = page.locator("text=Don't have an account?"); this.forgotPasswordLink = page.locator('text=Forgot your password?'); this.userErrorLabel = page.locator('text=Please enter your email or username'); this.fieldWithError = page.locator('.with-error'); this.formContainer = page.locator('.signup-team__container'); + + this.header = new components.MainHeader(page.locator('.hfroute-header')); + this.footer = new components.Footer(page.locator('.hfroute-footer')); } async toBeVisible() { diff --git a/e2e-tests/playwright/support/ui/pages/reset_password.ts b/e2e-tests/playwright/support/ui/pages/reset_password.ts new file mode 100644 index 0000000000..b8dd056fb5 --- /dev/null +++ b/e2e-tests/playwright/support/ui/pages/reset_password.ts @@ -0,0 +1,51 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, Page} from '@playwright/test'; + +import {components} from '@e2e-support/ui/components'; + +export default class ResetPasswordPage { + readonly page: Page; + + readonly title; + readonly subtitle; + readonly emailInput; + readonly resetButton; + readonly formContainer; + + readonly header; + readonly footer; + + constructor(page: Page) { + this.page = page; + + this.title = page.locator('h1:has-text("Password Reset")'); + this.subtitle = page.locator('text=To reset your password, enter the email address you used to sign up'); + this.emailInput = page.locator(`[placeholder="Email"]`); + this.resetButton = page.locator('#passwordResetButton'); + this.formContainer = page.locator('.signup-team__container'); + + this.header = new components.MainHeader(page.locator('.signup-header')); + this.footer = new components.Footer(page.locator('#footer_section')); + } + + async toBeVisible() { + await this.page.waitForLoadState('networkidle'); + await expect(this.title).toBeVisible(); + await expect(this.subtitle).toBeVisible(); + await expect(this.emailInput).toBeVisible(); + await expect(this.resetButton).toBeVisible(); + } + + async goto() { + await this.page.goto('/reset_password'); + } + + async reset(email: string) { + await this.emailInput.fill(email); + await Promise.all([this.page.waitForNavigation(), this.resetButton.click()]); + } +} + +export {ResetPasswordPage}; diff --git a/e2e-tests/playwright/support/ui/pages/signup.ts b/e2e-tests/playwright/support/ui/pages/signup.ts index 1fa8362e07..c3ec6a8f18 100644 --- a/e2e-tests/playwright/support/ui/pages/signup.ts +++ b/e2e-tests/playwright/support/ui/pages/signup.ts @@ -4,6 +4,7 @@ import {expect, Page} from '@playwright/test'; import {duration, wait} from '@e2e-support/util'; +import {components} from '@e2e-support/ui/components'; export default class SignupPage { readonly page: Page; @@ -14,28 +15,50 @@ export default class SignupPage { readonly emailInput; readonly usernameInput; readonly passwordInput; + readonly passwordToggleButton; + readonly newsLetterCheckBox; + readonly newsLetterPrivacyPolicyLink; + readonly newsLetterUnsubscribeLink; + readonly agreementTermsOfUseLink; + readonly agreementPrivacyPolicyLink; readonly createAccountButton; readonly loginLink; readonly emailError; readonly usernameError; readonly passwordError; + readonly header; + readonly footer; + constructor(page: Page) { this.page = page; this.title = page.locator('h1:has-text("Let’s get started")'); this.subtitle = page.locator('text=Create your Mattermost account to start collaborating with your team'); - this.bodyCard = page.locator('.signup-body-card'); + this.bodyCard = page.locator('.signup-body-card-content'); + this.loginLink = page.locator('text=Log in'); this.emailInput = page.locator('#input_email'); this.usernameInput = page.locator('#input_name'); this.passwordInput = page.locator('#input_password-input'); + this.passwordToggleButton = page.getByRole('button', {name: 'Show or hide password'}); this.createAccountButton = page.locator('button:has-text("Create Account")'); - this.loginLink = page.locator('text=Click here to sign in.'); this.emailError = page.locator('text=Please enter a valid email address'); this.usernameError = page.locator( 'text=Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.' ); this.passwordError = page.locator('text=Must be 5-64 characters long.'); + + const newsletterBlock = page.locator('.check-input'); + this.newsLetterCheckBox = newsletterBlock.getByRole('checkbox', {name: 'newsletter checkbox'}); + this.newsLetterPrivacyPolicyLink = newsletterBlock.locator('text=Privacy Policy'); + this.newsLetterUnsubscribeLink = newsletterBlock.locator('text=unsubscribe'); + + const agreementBlock = page.locator('.signup-body-card-agreement'); + this.agreementTermsOfUseLink = agreementBlock.locator('text=Terms of Use'); + this.agreementPrivacyPolicyLink = agreementBlock.locator('text=Privacy Policy'); + + this.header = new components.MainHeader(page.locator('.hfroute-header')); + this.footer = new components.Footer(page.locator('.hfroute-footer')); } async toBeVisible() { @@ -49,7 +72,7 @@ export default class SignupPage { } async goto() { - await this.page.goto('/signup_email'); + await this.page.goto('/signup_user_complete'); } async create(user: {email: string; username: string; password: string}, waitForRedirect = true) { diff --git a/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts b/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts index 0dfac7cdaf..a8ce81c196 100644 --- a/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts +++ b/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts @@ -19,7 +19,7 @@ test('Intro to channel', async ({pw, pages, axe}) => { // # Analyze the page // Disable 'color-contrast' to be addressed by MM-53814 - const accessibilityScanResults = await axe.builder(page, ['color-contrast']).analyze(); + const accessibilityScanResults = await axe.builder(page, {disableColorContrast: true}).analyze(); // * Should have no violation expect(accessibilityScanResults.violations).toHaveLength(0); diff --git a/e2e-tests/playwright/tests/accessibility/common/login.spec.ts b/e2e-tests/playwright/tests/accessibility/common/login.spec.ts index 2618783ed1..ad8e36fc37 100644 --- a/e2e-tests/playwright/tests/accessibility/common/login.spec.ts +++ b/e2e-tests/playwright/tests/accessibility/common/login.spec.ts @@ -3,7 +3,7 @@ import {expect, test} from '@e2e-support/test_fixture'; -test('/login', async ({pw, pages, page, axe}) => { +test('/login accessibility quick check', async ({pw, pages, page, axe}) => { // # Go to login page const {adminClient} = await pw.getAdminClient(); const adminConfig = await adminClient.getConfig(); @@ -17,3 +17,63 @@ test('/login', async ({pw, pages, page, axe}) => { // * Should have no violation expect(accessibilityScanResults.violations).toHaveLength(0); }); + +test('/login accessibility tab support', async ({pw, pages, page}) => { + // # Go to login page + const {adminClient} = await pw.getAdminClient(); + const adminConfig = await adminClient.getConfig(); + const loginPage = new pages.LoginPage(page, adminConfig); + await loginPage.goto(); + await loginPage.toBeVisible(); + + // * Should have focused at login input on page load + expect(await loginPage.loginInput).toBeFocused(); + + // * Should move focus to password input after tab + await loginPage.loginInput.press('Tab'); + expect(await loginPage.passwordInput).toBeFocused(); + + // * Should move focus to password toggle button after tab + await loginPage.passwordInput.press('Tab'); + expect(await loginPage.passwordToggleButton).toBeFocused(); + + // * Should move focus to forgot password link after tab + await loginPage.passwordToggleButton.press('Tab'); + expect(await loginPage.forgotPasswordLink).toBeFocused(); + + // * Should move focus to forgot password link after tab + await loginPage.forgotPasswordLink.press('Tab'); + expect(await loginPage.signInButton).toBeFocused(); + + // * Should move focus to about link after tab + await loginPage.signInButton.press('Tab'); + expect(await loginPage.footer.aboutLink).toBeFocused(); + + // * Should move focus to privacy policy link after tab + await loginPage.footer.aboutLink.press('Tab'); + expect(await loginPage.footer.privacyPolicyLink).toBeFocused(); + + // * Should move focus to terms link after tab + await loginPage.footer.privacyPolicyLink.press('Tab'); + expect(await loginPage.footer.termsLink).toBeFocused(); + + // * Should move focus to help link after tab + await loginPage.footer.termsLink.press('Tab'); + expect(await loginPage.footer.helpLink).toBeFocused(); + + // * Should move focus to header logo after tab + await loginPage.footer.helpLink.press('Tab'); + expect(await loginPage.header.logo).toBeFocused(); + + // * Should move focus to create account link after tab + await loginPage.header.logo.press('Tab'); + expect(await loginPage.createAccountLink).toBeFocused(); + + // * Should move focus to create account link after tab + await loginPage.createAccountLink.press('Tab'); + expect(await loginPage.bodyCard).toBeFocused(); + + // * Then, should move focus to login body after tab + await loginPage.bodyCard.press('Tab'); + expect(await loginPage.loginInput).toBeFocused(); +}); diff --git a/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts b/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts new file mode 100644 index 0000000000..32176afe16 --- /dev/null +++ b/e2e-tests/playwright/tests/accessibility/common/reset_password.spec.ts @@ -0,0 +1,55 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, test} from '@e2e-support/test_fixture'; + +test('/reset_password accessibility quick check', async ({pages, page, axe}) => { + // # Go to reset password page + const resetPasswordPage = new pages.ResetPasswordPage(page); + await resetPasswordPage.goto(); + await resetPasswordPage.toBeVisible(); + + // # Analyze the page + const accessibilityScanResults = await axe.builder(resetPasswordPage.page, {disableColorContrast: true}).analyze(); + + // * Should have no violation + expect(accessibilityScanResults.violations).toHaveLength(0); +}); + +test('/reset_password accessibility tab support', async ({pages, page}) => { + // # Go to reset password page + const resetPasswordPage = new pages.ResetPasswordPage(page); + await resetPasswordPage.goto(); + await resetPasswordPage.toBeVisible(); + + // * Should have focused at email input on page load + expect(await resetPasswordPage.emailInput).toBeFocused(); + + // * Should move focus to reset button after tab + await resetPasswordPage.emailInput.press('Tab'); + expect(await resetPasswordPage.resetButton).toBeFocused(); + + // * Should move focus to about link after tab + await resetPasswordPage.resetButton.press('Tab'); + expect(await resetPasswordPage.footer.aboutLink).toBeFocused(); + + // * Should move focus to privacy policy link after tab + await resetPasswordPage.footer.aboutLink.press('Tab'); + expect(await resetPasswordPage.footer.privacyPolicyLink).toBeFocused(); + + // * Should move focus to terms link after tab + await resetPasswordPage.footer.privacyPolicyLink.press('Tab'); + expect(await resetPasswordPage.footer.termsLink).toBeFocused(); + + // * Should move focus to help link after tab + await resetPasswordPage.footer.termsLink.press('Tab'); + expect(await resetPasswordPage.footer.helpLink).toBeFocused(); + + // * Should move focus to header logo after tab + await resetPasswordPage.footer.helpLink.press('Tab'); + expect(await resetPasswordPage.header.backButton).toBeFocused(); + + // * Then, should move focus to email input after tab + await resetPasswordPage.header.backButton.press('Tab'); + expect(await resetPasswordPage.emailInput).toBeFocused(); +}); diff --git a/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts b/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts new file mode 100644 index 0000000000..cf3431e766 --- /dev/null +++ b/e2e-tests/playwright/tests/accessibility/common/signup_user_complete.spec.ts @@ -0,0 +1,93 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, test} from '@e2e-support/test_fixture'; + +test('/signup_user_complete accessibility quick check', async ({pages, page, axe}) => { + // # Go to reset password page + const signupPage = new pages.SignupPage(page); + await signupPage.goto(); + await signupPage.toBeVisible(); + + // # Analyze the page + const accessibilityScanResults = await axe + .builder(signupPage.page, {disableColorContrast: true, disableLinkInTextBlock: true}) + .analyze(); + + // * Should have no violation + expect(accessibilityScanResults.violations).toHaveLength(0); +}); + +test('/signup_user_complete accessibility tab support', async ({pages, page}) => { + // # Go to reset password page + const signupPage = new pages.SignupPage(page); + await signupPage.goto(); + await signupPage.toBeVisible(); + + // * Should have focused at email input on page load + expect(await signupPage.emailInput).toBeFocused(); + + // * Should move focus to username input after tab + await signupPage.emailInput.press('Tab'); + expect(await signupPage.usernameInput).toBeFocused(); + + // * Should move focus to password input after tab + await signupPage.usernameInput.press('Tab'); + expect(await signupPage.passwordInput).toBeFocused(); + + // * Should move focus to password toggle button after tab + await signupPage.passwordInput.press('Tab'); + expect(await signupPage.passwordToggleButton).toBeFocused(); + + // * Should move focus to newsletter checkbox after tab + await signupPage.passwordToggleButton.press('Tab'); + expect(await signupPage.newsLetterCheckBox).toBeFocused(); + + // * Should move focus to newsletter privacy policy link after tab + await signupPage.newsLetterCheckBox.press('Tab'); + expect(await signupPage.newsLetterPrivacyPolicyLink).toBeFocused(); + + // * Should move focus to newsletter unsubscribe link after tab + await signupPage.newsLetterPrivacyPolicyLink.press('Tab'); + expect(await signupPage.newsLetterUnsubscribeLink).toBeFocused(); + + // * Should move focus to agreement terms of use link after tab + await signupPage.newsLetterUnsubscribeLink.press('Tab'); + expect(await signupPage.agreementTermsOfUseLink).toBeFocused(); + + // * Should move focus to agreement privacy policy link after tab + await signupPage.agreementTermsOfUseLink.press('Tab'); + expect(await signupPage.agreementPrivacyPolicyLink).toBeFocused(); + + // * Should move focus to privacy policy link after tab + await signupPage.footer.aboutLink.press('Tab'); + expect(await signupPage.footer.privacyPolicyLink).toBeFocused(); + + // * Should move focus to terms link after tab + await signupPage.footer.privacyPolicyLink.press('Tab'); + expect(await signupPage.footer.termsLink).toBeFocused(); + + // * Should move focus to help link after tab + await signupPage.footer.termsLink.press('Tab'); + expect(await signupPage.footer.helpLink).toBeFocused(); + + // * Should move focus to header logo after tab + await signupPage.footer.helpLink.press('Tab'); + expect(await signupPage.header.logo).toBeFocused(); + + // * Should move focus to header back button after tab + await signupPage.header.logo.press('Tab'); + expect(await signupPage.header.backButton).toBeFocused(); + + // * Should move focus to log in link after tab + await signupPage.header.backButton.press('Tab'); + expect(await signupPage.loginLink).toBeFocused(); + + // * Should move focus to sign up body after tab + await signupPage.loginLink.press('Tab'); + expect(await signupPage.bodyCard).toBeFocused(); + + // * Then, should move focus to email input after tab + await signupPage.bodyCard.press('Tab'); + expect(await signupPage.emailInput).toBeFocused(); +}); diff --git a/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_icon.tsx b/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_icon.tsx index 5d053680ec..d74168bf2a 100644 --- a/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_icon.tsx +++ b/webapp/channels/src/components/advanced_text_editor/formatting_bar/formatting_icon.tsx @@ -71,6 +71,7 @@ export const IconContainer = styled.button` `; interface FormattingIconProps { + id?: string; mode: MarkdownMode; onClick?: () => void; className?: string; @@ -129,7 +130,7 @@ const FormattingIcon = (props: FormattingIconProps): JSX.Element => { const bodyAction = ( { const {formatMessage} = useIntl(); + const closeText = formatMessage({id: 'alert_banner.tooltipCloseBtn', defaultMessage: 'Close'}); const [tooltipId] = useState(`alert_banner_close_btn_tooltip_${Math.random()}`); const bannerIcon = useCallback(() => { @@ -110,12 +111,11 @@ const AlertBanner = ({ delayShow={Constants.OVERLAY_TIME_DELAY} placement='left' overlay={closeBtnTooltip || ( - - {formatMessage({id: 'alert_banner.tooltipCloseBtn', defaultMessage: 'Close'})} - + {closeText} )} >