diff --git a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts index 2bf3f89b92..2b5e6165bb 100644 --- a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts +++ b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts @@ -45,13 +45,11 @@ describe('runner/cypress sessions.ui.spec', { validateSessionsInstrumentPanel(['blank_session']) cy.get('.command-name-session') - .first() .within(() => { cy.get('i.command-message-indicator-successful') .siblings() .should('contain', '(new) blank_session') - cy.get('.command-name-session').contains('blank_session') validateCreateNewSessionGroup() }) @@ -70,14 +68,11 @@ describe('runner/cypress sessions.ui.spec', { validateSessionsInstrumentPanel(['blank_session']) cy.get('.command-name-session') - .first() .within(() => { cy.get('i.command-message-indicator-successful') .siblings() .should('contain', '(new) blank_session') - cy.get('.command-name-session').contains('blank_session') - validateCreateNewSessionGroup() cy.contains('Validate Session: valid') @@ -106,14 +101,11 @@ describe('runner/cypress sessions.ui.spec', { validateSessionsInstrumentPanel(['blank_session']) cy.get('.command-name-session') - .first() .within(() => { cy.get('i.command-message-indicator-successful') .siblings() .should('contain', '(new) blank_session') - cy.get('.command-name-session').contains('blank_session') - validateCreateNewSessionGroup() cy.contains('Validate Session: invalid') @@ -151,13 +143,10 @@ describe('runner/cypress sessions.ui.spec', { validateSessionsInstrumentPanel(['user1']) cy.get('.command-name-session') - .first() .within(() => { cy.get('i.command-message-indicator-pending') .siblings().should('contain', '(saved) user1') - cy.get('.command-name-session').contains('user1') - cy.contains('Restore Saved Session') .closest('.command') .contains('Clear Page') @@ -208,13 +197,10 @@ describe('runner/cypress sessions.ui.spec', { validateSessionsInstrumentPanel(['user1']) cy.get('.command-name-session') - .first() .within(() => { cy.get('i.command-message-indicator-bad') .siblings().should('contain', '(recreated) user1') - cy.get('.command-name-session').contains('user1') - cy.contains('Restore Saved Session') .closest('.command') .contains('Clear Page') @@ -272,13 +258,10 @@ describe('runner/cypress sessions.ui.spec', { validateSessionsInstrumentPanel(['user1']) cy.get('.command-name-session') - .first() .within(() => { cy.get('i.command-message-indicator-bad') .siblings().should('contain', '(recreated) user1') - cy.get('.command-name-session').contains('user1') - cy.contains('Restore Saved Session') .closest('.command') .contains('Clear Page') diff --git a/packages/app/cypress/e2e/settings.cy.ts b/packages/app/cypress/e2e/settings.cy.ts index 0cb36cdc73..ecc1b0c854 100644 --- a/packages/app/cypress/e2e/settings.cy.ts +++ b/packages/app/cypress/e2e/settings.cy.ts @@ -99,7 +99,7 @@ describe('App: Settings', () => { cy.waitForSpecToFinish() // Wait for the test to pass, so the test is completed cy.get('.passed > .num').should('contain', 1) - cy.get(`[href='#/settings']`).click() + cy.get('[href="#/settings"]').click() cy.contains('Dashboard Settings').click() // Assert the data is not there before it arrives cy.contains('Record Key').should('not.exist') @@ -114,18 +114,18 @@ describe('App: Settings', () => { o.sinon.spy(ctx.actions.auth, 'logout') }) - cy.get(`[href='#/settings']`).click() + cy.get('[href="#/settings"]').click() cy.contains('Dashboard Settings').click() cy.contains('Record Key').should('exist') - cy.get(`[href='#/runs']`).click() - cy.get('[data-cy="user-avatar-title"]').click() + cy.get('[href="#/runs"]').click() + cy.findByTestId('user-avatar-title').click() cy.findByRole('button', { name: 'Log Out' }).click() cy.withRetryableCtx((ctx, o) => { expect(ctx.actions.auth.logout).to.have.been.calledOnce }) - cy.get(`[href='#/settings']`).click() + cy.get('[href="#/settings"]').click() cy.contains('Dashboard Settings').click() cy.contains('Record Key').should('not.exist') }) diff --git a/packages/app/cypress/e2e/sidebar_navigation.cy.ts b/packages/app/cypress/e2e/sidebar_navigation.cy.ts index e29761b68f..c8e937c940 100644 --- a/packages/app/cypress/e2e/sidebar_navigation.cy.ts +++ b/packages/app/cypress/e2e/sidebar_navigation.cy.ts @@ -1,6 +1,50 @@ import type { SinonStub } from 'sinon' describe('Sidebar Navigation', () => { + context('accessibility', () => { + beforeEach(() => { + cy.scaffoldProject('todos') + cy.openProject('todos') + cy.startAppServer() + cy.visitApp() + cy.contains('todos') + }) + + it('can tab through navigation', () => { + cy.get('body').focus() + .tab().should('have.attr', 'data-cy', 'toggle-sidebar').should('have.prop', 'tagName', 'BUTTON') + .tab().should('have.attr', 'data-cy', 'sidebar-header').should('have.attr', 'role', 'button') + .tab().should('have.attr', 'href', '#/specs').should('have.prop', 'tagName', 'A') + .tab().should('have.attr', 'href', '#/runs').should('have.prop', 'tagName', 'A') + .tab().should('have.attr', 'href', '#/settings').should('have.prop', 'tagName', 'A') + .tab().should('have.attr', 'data-cy', 'keyboard-modal-trigger').should('have.prop', 'tagName', 'BUTTON') + }) + + it('has no axe violations', () => { + cy.injectAxe() + cy.checkA11y('[data-cy="sidebar"]') + }) + + it('has appropriate aria attributes', () => { + cy.findByTestId('toggle-sidebar') + .should('have.attr', 'aria-controls', 'sidebar') + .should('have.attr', 'aria-expanded', 'true') + .should('have.attr', 'aria-label', 'Collapse sidebar') + .click() + .should('have.attr', 'aria-expanded', 'false') + .should('have.attr', 'aria-label', 'Expand sidebar') + + cy.findByTestId('keyboard-modal-trigger') + .should('have.attr', 'aria-label', 'Keyboard Shortcuts') + + cy.get('nav') + .should('have.attr', 'aria-label', 'Pages') + + cy.findByTestId('sidebar-header') + .should('have.attr', 'aria-label', 'todos - Choose a testing type') + }) + }) + context('as e2e testing type with localSettings', () => { it('use saved state for nav size', () => { cy.withCtx(async (ctx) => { @@ -16,7 +60,7 @@ describe('Sidebar Navigation', () => { cy.get('.toggle-specs-text').click() - cy.get('[data-cy="reporter-panel"]').invoke('outerWidth').then(($initialWidth) => { + cy.findByTestId('reporter-panel').invoke('outerWidth').then(($initialWidth) => { expect($initialWidth).eq(100) }) }) @@ -32,66 +76,45 @@ describe('Sidebar Navigation', () => { }) it('expands the left nav bar by default', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') + cy.findByTestId('sidebar').should('have.css', 'width', '248px') // assert width to ensure transition has finished cy.percySnapshot() }) it('highlights indicator on hover showing you can click to expand', () => { - const navIndicatorSelector = '[data-testid=sidebar-nav-indicator]' - const navExpansionToggleSelector = '[aria-label="toggle navigation"]' + const navIndicatorSelector = '[data-cy=sidebar-nav-indicator]' - cy.get(navIndicatorSelector) - .should('not.be.visible') - .get(navExpansionToggleSelector) - .realHover() - .get(navIndicatorSelector) - .should('be.visible') - .percySnapshot() + cy.get(navIndicatorSelector).should('not.be.visible') + cy.findByTestId('toggle-sidebar').realHover() + cy.get(navIndicatorSelector).should('be.visible') + + cy.percySnapshot() }) it('closes the left nav bar when clicking the expand button (if expanded)', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - cy.findAllByText('todos').eq(1).as('title') - cy.get('@title').should('be.visible') - - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() - - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'false') - cy.get('@title').should('not.be.visible') + cy.findByTestId('sidebar').contains('todos').should('be.visible') + cy.findByTestId('toggle-sidebar').click() + cy.findByTestId('sidebar').contains('todos').should('not.be.visible') cy.percySnapshot() }) it('closes the left nav bar when clicking the expand button and persist the state if browser is refreshed', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - cy.findAllByText('todos').eq(1).as('title') - cy.get('@title').should('be.visible') + cy.findByTestId('sidebar').contains('todos').should('be.visible') + cy.findByTestId('toggle-sidebar').click() - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() - - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'false') - cy.get('@title').should('not.be.visible') + cy.findByTestId('sidebar').contains('todos').should('not.be.visible') cy.reload() - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'false') - cy.findAllByText('todos').should('not.be.visible') + cy.findByTestId('sidebar').contains('todos').should('not.be.visible') cy.percySnapshot() }) - it('has unlabeled menu item that shows the keyboard shortcuts modal (unexpanded state)', () => { - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() + it('has menu item that shows the keyboard shortcuts modal (unexpanded state)', () => { + cy.findByTestId('toggle-sidebar').click() + cy.findByTestId('sidebar').should('have.css', 'width', '64px') // assert width to ensure transition has finished - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'false') - - cy.get('[data-cy="keyboard-shortcuts"]').should('be.visible') - cy.get('[data-cy="keyboard-shortcuts"]').click() + cy.findByTestId('keyboard-modal-trigger').should('be.visible').click() cy.contains('h2', 'Keyboard Shortcuts').should('be.visible') cy.get('li p').contains('Re-run tests').should('be.visible') cy.get('li p').contains('Stop tests').should('be.visible') @@ -106,52 +129,38 @@ describe('Sidebar Navigation', () => { }) it('shows a tooltip when hovering over menu item', () => { - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() + cy.findByTestId('toggle-sidebar').click() + cy.findByTestId('sidebar').should('have.css', 'width', '64px') // assert width to ensure transition has finished - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'false') - - cy.get('[data-cy="sidebar-header"').trigger('mouseenter') + cy.findByTestId('sidebar-header').trigger('mouseenter') cy.contains('.v-popper--some-open--tooltip', 'todos') cy.percySnapshot() - cy.get('[data-cy="sidebar-header"]').trigger('mouseout') + cy.findByTestId('sidebar-header').trigger('mouseout') - cy.get('[data-e2e-href="/runs"]').trigger('mouseenter') + cy.get('[href="#/runs"]').trigger('mouseenter') cy.contains('.v-popper--some-open--tooltip', 'Runs') - cy.get('[data-e2e-href="/runs"]').trigger('mouseout') + cy.get('[href="#/runs"]').trigger('mouseout') - cy.get('[data-e2e-href="/specs"]').trigger('mouseenter') + cy.get('[href="#/specs"]').trigger('mouseenter') cy.contains('.v-popper--some-open--tooltip', 'Specs') - cy.get('[data-e2e-href="/specs"]').trigger('mouseout') + cy.get('[href="#/specs"]').trigger('mouseout') - cy.get('[data-e2e-href="/settings"]').trigger('mouseenter') + cy.get('[href="#/settings"]').trigger('mouseenter') cy.contains('.v-popper--some-open--tooltip', 'Settings') - cy.get('[data-e2e-href="/settings"]').trigger('mouseout') + cy.get('[href="#/settings"]').trigger('mouseout') }) it('opens the left nav bar when clicking the expand button (if unexpanded)', () => { - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() - - cy.findAllByText('todos').eq(1).should('not.be.visible') - - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() - - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - cy.findAllByText('todos').eq(1).should('be.visible') + cy.findByTestId('toggle-sidebar').click() + cy.findByTestId('sidebar').contains('todos').should('not.be.visible') + cy.findByTestId('toggle-sidebar').click() + cy.findByTestId('sidebar').contains('todos').should('be.visible') }) it('displays the project name and opens a modal to switch testing type', () => { - cy.visitApp() - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - - cy.get('[data-cy="sidebar-header"]').within(() => { - cy.get('[data-cy="testing-type-e2e"]').should('be.visible') - cy.findByText('todos').should('be.visible') + cy.findByTestId('sidebar-header').within(() => { + cy.findByTestId('testing-type-e2e').should('be.visible') + cy.contains('todos').should('be.visible') }).as('switchTestingType').click() cy.findByRole('dialog', { @@ -192,20 +201,13 @@ describe('Sidebar Navigation', () => { cy.get('[aria-label="Close"]').click() cy.findByText('Choose a testing type').should('not.exist') - - cy.findByLabelText('toggle navigation', { - selector: 'button', - }).click() - - cy.get('[data-cy="sidebar-header"]').click() + cy.findByTestId('toggle-sidebar').click() + cy.findByTestId('sidebar-header').click() cy.findByText('Choose a testing type').should('be.visible') }) - it('has unlabeled menu item that shows the keyboard shortcuts modal (expanded state)', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - - cy.get('[data-cy="keyboard-shortcuts"]').should('be.visible') - cy.get('[data-cy="keyboard-shortcuts"]').click() + it('has menu item that shows the keyboard shortcuts modal (expanded state)', () => { + cy.findByTestId('keyboard-modal-trigger').should('be.visible').click() cy.contains('h2', 'Keyboard Shortcuts').should('be.visible') cy.get('li p').contains('Re-run tests').should('be.visible') cy.get('li p').contains('Stop tests').should('be.visible') @@ -218,20 +220,15 @@ describe('Sidebar Navigation', () => { }) it('has a menu item labeled "Runs" which takes you to the Runs page', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - cy.get('[data-cy="app-header-bar"]').findByText('Runs').should('not.exist') - cy.findByText('Runs').should('be.visible') - cy.findByText('Runs').click() + cy.findByText('Runs').should('be.visible').click() cy.get('[data-cy="app-header-bar"]').findByText('Runs').should('be.visible') cy.get('.router-link-active').findByText('Runs').should('be.visible') }) it('has a menu item labeled "Specs" which takes you to the Spec List page', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - cy.findByLabelText('Sidebar').within(() => { - cy.findByText('Specs').should('be.visible') - cy.findByText('Specs').click() + cy.findByTestId('sidebar').within(() => { + cy.findByText('Specs').should('be.visible').click() }) cy.get('[data-cy="app-header-bar"]').findByText('Specs').should('be.visible') @@ -239,18 +236,15 @@ describe('Sidebar Navigation', () => { }) it('has a menu item labeled "Settings" which takes you to the Settings page', () => { - cy.findByLabelText('Sidebar').closest('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') - - cy.get('[data-cy="app-header-bar"]').findByText('Settings').should('not.exist') + cy.findByTestId('app-header-bar').findByText('Settings').should('not.exist') cy.findByText('Settings').should('be.visible') cy.findByText('Settings').click() - cy.get('[data-cy="app-header-bar"]').findByText('Settings').should('be.visible') + cy.findByTestId('app-header-bar').findByText('Settings').should('be.visible') cy.get('.router-link-active').findByText('Settings').should('be.visible') }) it('resize nav sends the correct value on the mutation', () => { cy.contains('fixture.js').click() - cy.withCtx((ctx, o) => { o.sinon.stub(ctx.actions.localSettings, 'setPreferences').resolves() }) diff --git a/packages/app/cypress/e2e/specs_list_e2e.cy.ts b/packages/app/cypress/e2e/specs_list_e2e.cy.ts index 8fce933377..c0d2ddedd7 100644 --- a/packages/app/cypress/e2e/specs_list_e2e.cy.ts +++ b/packages/app/cypress/e2e/specs_list_e2e.cy.ts @@ -26,7 +26,7 @@ describe('App: Spec List (E2E)', () => { }) it('shows the "Specs" navigation as highlighted in the lefthand nav bar', () => { - cy.findByLabelText('Sidebar').within(() => { + cy.findByTestId('sidebar').within(() => { cy.findByText('Specs').should('be.visible') cy.findByText('Specs').click() }) diff --git a/packages/app/cypress/e2e/subscriptions/configChange-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/configChange-subscription.cy.ts index 37f3f2cc00..0e91b3a7f9 100644 --- a/packages/app/cypress/e2e/subscriptions/configChange-subscription.cy.ts +++ b/packages/app/cypress/e2e/subscriptions/configChange-subscription.cy.ts @@ -26,7 +26,7 @@ describe('specChange subscription', () => { describe('on config page', () => { it('responds to configChange event when viewport is changed', () => { - cy.get('a').contains('Settings').click() + cy.contains('a', 'Settings').click() cy.get('[data-cy="collapsible-header"]').contains('Project Settings').click() cy.contains(`projectId: 'abc123'`) updateProjectIdInCypressConfig('foo456') diff --git a/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx b/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx index 82c186c016..83b65eda68 100644 --- a/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx +++ b/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx @@ -7,7 +7,7 @@ describe('KeyboardBindingsModal', () => { return }) - const expectedContent = defaultMessages.sideBar.keyboardShortcuts + const expectedContent = defaultMessages.sidebar.keyboardShortcuts Object.values(expectedContent).forEach((text) => { cy.contains(text).should('be.visible') diff --git a/packages/app/src/navigation/KeyboardBindingsModal.vue b/packages/app/src/navigation/KeyboardBindingsModal.vue index 0a3fe3027a..fb1457dc70 100644 --- a/packages/app/src/navigation/KeyboardBindingsModal.vue +++ b/packages/app/src/navigation/KeyboardBindingsModal.vue @@ -2,9 +2,9 @@ @@ -46,15 +46,15 @@ const emits = defineEmits<{ const keyBindings = [ { key: ['r'], - description: t('sideBar.keyboardShortcuts.rerun'), + description: t('sidebar.keyboardShortcuts.rerun'), }, { key: ['s'], - description: t('sideBar.keyboardShortcuts.stop'), + description: t('sidebar.keyboardShortcuts.stop'), }, { key: ['f'], - description: t('sideBar.keyboardShortcuts.toggle'), + description: t('sidebar.keyboardShortcuts.toggle'), }, ] diff --git a/packages/app/src/navigation/SidebarNavigation.cy.tsx b/packages/app/src/navigation/SidebarNavigation.cy.tsx index 562a3bea9c..3a999e6e29 100644 --- a/packages/app/src/navigation/SidebarNavigation.cy.tsx +++ b/packages/app/src/navigation/SidebarNavigation.cy.tsx @@ -1,4 +1,5 @@ import SidebarNavigation from './SidebarNavigation.vue' +import { defaultMessages } from '@cy/i18n' function mountComponent (initialNavExpandedVal = true) { cy.mount(() => { @@ -16,32 +17,42 @@ describe('SidebarNavigation', () => { it('expands the bar when clicking the expand button', () => { mountComponent() - cy.get('[aria-expanded]').should('have.attr', 'aria-expanded', 'false') cy.findByText('test-project').should('not.be.visible') - cy.findByLabelText('toggle navigation', { + cy.findByLabelText(defaultMessages.sidebar.toggleLabel.collapsed, { selector: 'button', }).click() - cy.get('[aria-expanded]').should('have.attr', 'aria-expanded', 'true') + cy.findByLabelText(defaultMessages.sidebar.toggleLabel.expanded, { + selector: 'button', + }) + cy.findByText('test-project').should('be.visible') + cy.percySnapshot() }) it('shows tooltips on hover', () => { mountComponent(false) - cy.get('[data-cy="sidebar-header"').trigger('mouseenter') + cy.findByTestId('sidebar-header').trigger('mouseenter') cy.contains('.v-popper--some-open--tooltip', 'test-project').should('be.visible') - cy.get('[data-cy="sidebar-header"]').trigger('mouseout') + cy.findByTestId('sidebar-header').trigger('mouseout') - cy.get('[data-e2e-href="/runs"]').trigger('mouseenter') + cy.get('[href="#/runs"]').trigger('mouseenter') cy.contains('.v-popper--some-open--tooltip', 'Runs').should('be.visible') - cy.get('[data-e2e-href="/runs"]').trigger('mouseout') + cy.get('[href="#/runs"]').trigger('mouseout') cy.percySnapshot() }) it('opens a modal to switch testing type', { viewportWidth: 1280 }, () => { mountComponent() - cy.get('[data-cy="sidebar-header"]').click() + cy.findByTestId('sidebar-header').click() cy.percySnapshot() }) + + it('opens a modal to show keyboard shortcuts', () => { + mountComponent() + cy.findByTestId('keyboard-modal').should('not.exist') + cy.findByTestId('keyboard-modal-trigger').focus().type('{enter}') + cy.findByTestId('keyboard-modal').should('be.visible') + }) }) diff --git a/packages/app/src/navigation/SidebarNavigation.vue b/packages/app/src/navigation/SidebarNavigation.vue index 255b0866d7..7d4f0d9915 100644 --- a/packages/app/src/navigation/SidebarNavigation.vue +++ b/packages/app/src/navigation/SidebarNavigation.vue @@ -1,19 +1,22 @@ diff --git a/packages/app/src/runner/aut-iframe.ts b/packages/app/src/runner/aut-iframe.ts index b43a79ff13..f2dc8849a1 100644 --- a/packages/app/src/runner/aut-iframe.ts +++ b/packages/app/src/runner/aut-iframe.ts @@ -25,7 +25,8 @@ export class AutIframe { create (): JQuery { const $iframe = this.$('