diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index bc45d187..08ed50f9 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -32,7 +32,7 @@ jobs: parallel: false browser: chrome working-directory: acceptance - spec: cypress/tests/*.js + spec: cypress/tests/main/**/*.js install: false start: | docker compose -f ci.yml --profile prod up @@ -51,3 +51,48 @@ jobs: with: name: cypress-videos-acceptance path: acceptance/cypress/videos + + acceptance-a11y: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Install Cypress + run: | + cd acceptance + yarn + + - name: "Cypress: Acceptance tests" + uses: cypress-io/github-action@v6 + env: + BABEL_ENV: production + CYPRESS_RETRIES: 2 + CYPRESS_a11y: 1 + with: + parallel: false + browser: chrome + working-directory: acceptance + spec: cypress/tests/a11y/**/*.js + install: false + start: | + docker compose -f ci-a11y.yml --profile prod up + wait-on: 'npx wait-on --httpTimeout 20000 http-get://localhost:8080/Plone http://localhost:3000' + + # Upload Cypress screenshots + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: cypress-screenshots-acceptance + path: acceptance/cypress/screenshots + + # Upload Cypress videos + - uses: actions/upload-artifact@v3 + if: failure() + with: + name: cypress-videos-acceptance + path: acceptance/cypress/videos diff --git a/Makefile b/Makefile index b1d5f56b..c1540a0f 100644 --- a/Makefile +++ b/Makefile @@ -28,11 +28,13 @@ ADDON_NAME='@kitconcept/volto-light-theme' ADDON_PATH='volto-light-theme' COMPOSE_FILE=dockerfiles/docker-compose.yml ACCEPTANCE_COMPOSE=acceptance/docker-compose.yml +ACCEPTANCE_COMPOSE_A11Y=acceptance/docker-compose-a11y.yml CMD=CURRENT_DIR=${CURRENT_DIR} ADDON_NAME=${ADDON_NAME} ADDON_PATH=${ADDON_PATH} VOLTO_VERSION=${VOLTO_VERSION} PLONE_VERSION=${PLONE_VERSION} docker compose DOCKER_COMPOSE=${CMD} -p ${ADDON_PATH} -f ${COMPOSE_FILE} DEV_COMPOSE=COMPOSE_PROFILES=dev ${DOCKER_COMPOSE} LIVE_COMPOSE=COMPOSE_PROFILES=dev ${DOCKER_COMPOSE} ACCEPTANCE=${CMD} -p ${ADDON_PATH}-acceptance -f ${ACCEPTANCE_COMPOSE} +ACCEPTANCE_A11Y=${CMD} -p ${ADDON_PATH}-acceptance -f ${ACCEPTANCE_COMPOSE_A11Y} .PHONY: build-backend build-backend: ## Build @@ -124,7 +126,7 @@ start-test-acceptance-server-prod: ## Start acceptance server in prod (used by C .PHONY: test-acceptance test-acceptance: ## Start Cypress (for use it while developing) - (cd acceptance && ./node_modules/.bin/cypress open) + (cd acceptance && ./node_modules/.bin/cypress open --config specPattern='cypress/tests/main/**/*.{js,jsx,ts,tsx}') .PHONY: test-acceptance-headless test-acceptance-headless: ## Run cypress tests in CI @@ -138,6 +140,18 @@ stop-test-acceptance-server: ## Stop acceptance server (for use it while finishe status-test-acceptance-server: ## Status of Acceptance Server (for use it while developing) ${ACCEPTANCE} ps +.PHONY: start-test-acceptance-server-a11y +start-test-acceptance-server-a11y: ## Start a11y acceptance server (for use it in while developing) + ${ACCEPTANCE_A11Y} --profile dev up + +.PHONY: stop-test-acceptance-server-a11y +stop-test-acceptance-server-a11y: ## Stop a11y acceptance server (for use it while finished developing) + ${ACCEPTANCE_A11Y} --profile dev down + +.PHONY: test-acceptance-a11y +test-acceptance-a11y: ## Start Cypress (for use it while developing) + (cd acceptance && CYPRESS_a11y=1 ./node_modules/.bin/cypress open --config specPattern='cypress/tests/a11y/**/*.{js,jsx,ts,tsx}') + .PHONY: debug-frontend debug-frontend: ## Run bash in the Frontend container (for debug infrastructure purposes) ${DEV_COMPOSE} run --entrypoint bash addon-dev diff --git a/README.md b/README.md index de81af13..83245f80 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,38 @@ When finished, don't forget to shutdown the backend server. make stop-test-acceptance-server ``` +### Accessibility Acceptance tests + +Run once + +```shell +make install-acceptance +``` + +For starting the servers + +Run + +```shell +make start-test-acceptance-server-a11y +``` + +The frontend is run in dev mode, so development while writing tests is possible. + +Run + +```shell +make test-acceptance-a11y +``` + +To run Cypress tests afterwards. + +When finished, don't forget to shutdown the backend server. + +```shell +make stop-test-acceptance-server-a11y +``` + ### Release Run diff --git a/acceptance/ci-a11y.yml b/acceptance/ci-a11y.yml new file mode 100644 index 00000000..83ca3a4e --- /dev/null +++ b/acceptance/ci-a11y.yml @@ -0,0 +1,31 @@ +version: "3" + +services: + + addon-acceptance: + build: + context: ../ + dockerfile: ./dockerfiles/Dockerfile + args: + ADDON_NAME: "${ADDON_NAME}" + ADDON_PATH: "${ADDON_PATH}" + VOLTO_VERSION: ${VOLTO_VERSION:-17} + environment: + RAZZLE_INTERNAL_API_PATH: http://backend-acceptance-a11y:8080/Plone + RAZZLE_API_PATH: http://localhost:8080/Plone + ports: + - 3000:3000 + depends_on: + - backend-acceptance-a11y + profiles: + - prod + + backend-acceptance-a11y: + image: ghcr.io/kitconcept/voltolighttheme:latest + environment: + CORS_: true + ports: + - 8080:8080 + profiles: + - dev + - prod diff --git a/acceptance/cypress.config.js b/acceptance/cypress.config.js index d841d7ab..4c4f133e 100644 --- a/acceptance/cypress.config.js +++ b/acceptance/cypress.config.js @@ -4,6 +4,14 @@ module.exports = defineConfig({ viewportWidth: 1280, e2e: { baseUrl: 'http://localhost:3000', - specPattern: 'cypress/tests/*.cy.{js,jsx}', + specPattern: 'cypress/tests/**/*.cy.{js,jsx,ts,tsx}', + setupNodeEvents(on, config) { + on('task', { + table(message) { + console.table(message); + return null; + }, + }); + }, }, }); diff --git a/acceptance/cypress/support/commands.js b/acceptance/cypress/support/commands.js index 44a50003..929e9e04 100644 --- a/acceptance/cypress/support/commands.js +++ b/acceptance/cypress/support/commands.js @@ -1 +1,23 @@ import '@plone/volto-testing/cypress/support/commands'; + +// Print cypress-axe violations to the terminal +function printAccessibilityViolations(violations) { + cy.task( + 'table', + violations.map(({ id, impact, description, nodes }) => ({ + impact, + description: `${description} (${id})`, + nodes: nodes.length, + })), + ); +} + +Cypress.Commands.add( + 'checkAccessibility', + (subject, { skipFailures = false } = {}) => { + cy.checkA11y(subject, null, printAccessibilityViolations, skipFailures); + }, + { + prevSubject: 'optional', + }, +); diff --git a/acceptance/cypress/support/e2e.js b/acceptance/cypress/support/e2e.js index 47dfa732..cb54caed 100644 --- a/acceptance/cypress/support/e2e.js +++ b/acceptance/cypress/support/e2e.js @@ -1,6 +1,7 @@ import 'cypress-axe'; import 'cypress-file-upload'; import './commands'; +import 'cypress-axe'; import { setup, teardown } from './reset-fixture'; beforeEach(function () { @@ -9,10 +10,14 @@ beforeEach(function () { cy.setCookie('confirm_facebook', '1'); cy.setCookie('confirm_youtube', '1'); cy.log('Setting up API fixture'); - setup(); + if (!Cypress.env('a11y')) { + setup(); + } }); afterEach(function () { cy.log('Tearing down API fixture'); - teardown(); + if (!Cypress.env('a11y')) { + teardown(); + } }); diff --git a/acceptance/cypress/tests/a11y/accordionBlock.cy.js b/acceptance/cypress/tests/a11y/accordionBlock.cy.js new file mode 100644 index 00000000..72428fc5 --- /dev/null +++ b/acceptance/cypress/tests/a11y/accordionBlock.cy.js @@ -0,0 +1,30 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Accordion Block + it('Accordion Block (/block/block-accordion)', () => { + cy.navigate('/block/block-accordion'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // the example page intentionally omits the h1 + { + id: 'page-has-heading-one', + enabled: false, + }, + { + id: 'duplicate-id', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); diff --git a/acceptance/cypress/tests/a11y/basic.cy.js b/acceptance/cypress/tests/a11y/basic.cy.js new file mode 100644 index 00000000..1da4e782 --- /dev/null +++ b/acceptance/cypress/tests/a11y/basic.cy.js @@ -0,0 +1,26 @@ + +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + it('Home page (/)', () => { + cy.navigate('/'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // the example home page intentionally omits the h1 + { + id: 'page-has-heading-one', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); diff --git a/acceptance/cypress/tests/a11y/buttonBlock.cy.js b/acceptance/cypress/tests/a11y/buttonBlock.cy.js new file mode 100644 index 00000000..0f1fd15c --- /dev/null +++ b/acceptance/cypress/tests/a11y/buttonBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //Button + it('Button-Block (/block/button-block)', () => { + cy.navigate('/block/button-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/eventContenttype.cy.js b/acceptance/cypress/tests/a11y/eventContenttype.cy.js new file mode 100644 index 00000000..1e285a6c --- /dev/null +++ b/acceptance/cypress/tests/a11y/eventContenttype.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //Event + it('Event (/content-types/event)', () => { + cy.navigate('/content-types/event'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/fileContenttype.cy.js b/acceptance/cypress/tests/a11y/fileContenttype.cy.js new file mode 100644 index 00000000..ea68a01e --- /dev/null +++ b/acceptance/cypress/tests/a11y/fileContenttype.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //File + it('File (/content-types/file)', () => { + cy.navigate('/content-types/file'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/gridBlock.cy.js b/acceptance/cypress/tests/a11y/gridBlock.cy.js new file mode 100644 index 00000000..463f938e --- /dev/null +++ b/acceptance/cypress/tests/a11y/gridBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // grid block + it('Grid-Block (/block/grid-block)', () => { + cy.navigate('/block/grid-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/gridImageBlock.cy.js b/acceptance/cypress/tests/a11y/gridImageBlock.cy.js new file mode 100644 index 00000000..a6287788 --- /dev/null +++ b/acceptance/cypress/tests/a11y/gridImageBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // grid block Image + it('Grid-Block Image (/block/grid-block/image)', () => { + cy.navigate('/block/grid-block/image'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/gridListingBlock.cy.js b/acceptance/cypress/tests/a11y/gridListingBlock.cy.js new file mode 100644 index 00000000..8a2f4228 --- /dev/null +++ b/acceptance/cypress/tests/a11y/gridListingBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // grid block listing + it('Grid-Block Listing (/block/grid-block/listing)', () => { + cy.navigate('/block/grid-block/listing'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/gridTeaserBlock.cy.js b/acceptance/cypress/tests/a11y/gridTeaserBlock.cy.js new file mode 100644 index 00000000..06c3a5dd --- /dev/null +++ b/acceptance/cypress/tests/a11y/gridTeaserBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // grid Teaser block + it('Grid-Block Teaser (/block/grid-block/teaser)', () => { + cy.navigate('/block/grid-block/teaser'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/gridTextBlock.cy.js b/acceptance/cypress/tests/a11y/gridTextBlock.cy.js new file mode 100644 index 00000000..0937e22f --- /dev/null +++ b/acceptance/cypress/tests/a11y/gridTextBlock.cy.js @@ -0,0 +1,33 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // grid tex block + it('Grid-Block text (/block/grid-block/text)', () => { + cy.navigate('/block/grid-block/text'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // there are copies of slate h2, + // which have with the same id + { + id: 'duplicate-id-active', + enabled: false, + }, + // there are missing heading of grid-block + // because we are using multiple grid block + { + id: 'empty-heading', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/headingBlock.cy.js b/acceptance/cypress/tests/a11y/headingBlock.cy.js new file mode 100644 index 00000000..06fa25bb --- /dev/null +++ b/acceptance/cypress/tests/a11y/headingBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Heading + it('Heading-Block (/block/heading-block)', () => { + cy.navigate('/block/heading-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/highlightBlock.cy.js b/acceptance/cypress/tests/a11y/highlightBlock.cy.js new file mode 100644 index 00000000..cc918d0a --- /dev/null +++ b/acceptance/cypress/tests/a11y/highlightBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Highlight Block + it('Highlight-Block (/block/highlight-block)', () => { + cy.navigate('/block/highlight-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/imageBlock.cy.js b/acceptance/cypress/tests/a11y/imageBlock.cy.js new file mode 100644 index 00000000..e0909de8 --- /dev/null +++ b/acceptance/cypress/tests/a11y/imageBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Image-Block + it('Image-Block (/block/image-block)', () => { + cy.navigate('/block/image-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/imageContenttype.cy.js b/acceptance/cypress/tests/a11y/imageContenttype.cy.js new file mode 100644 index 00000000..e93a10a0 --- /dev/null +++ b/acceptance/cypress/tests/a11y/imageContenttype.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //Image + it('Image (/content-types/image)', () => { + cy.navigate('/content-types/image'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/introductionBlock.cy.js b/acceptance/cypress/tests/a11y/introductionBlock.cy.js new file mode 100644 index 00000000..8900c121 --- /dev/null +++ b/acceptance/cypress/tests/a11y/introductionBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Introduction-Block + it('Introduction-Block (/block/introduction-block)', () => { + cy.navigate('/block/introduction-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/linkContenttype.cy.js b/acceptance/cypress/tests/a11y/linkContenttype.cy.js new file mode 100644 index 00000000..f8e8a8d7 --- /dev/null +++ b/acceptance/cypress/tests/a11y/linkContenttype.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //Link + it('link (/content-types/internal-link)', () => { + cy.navigate('/content-types/internal-link'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/listingBlock.cy.js b/acceptance/cypress/tests/a11y/listingBlock.cy.js new file mode 100644 index 00000000..7789452f --- /dev/null +++ b/acceptance/cypress/tests/a11y/listingBlock.cy.js @@ -0,0 +1,31 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Listing-block + it('Listing-block (/block/listing-block)', () => { + cy.navigate('/block/listing-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + // Disabling 'image-alt' + // semantic-ui-react's Embed doesn't include an alt tag for the placeholder image + rules: [ + { + id: 'image-alt', + enabled: false, + }, + { + id: 'nested-interactive', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/mapsBlock.cy.js b/acceptance/cypress/tests/a11y/mapsBlock.cy.js new file mode 100644 index 00000000..e012f2a7 --- /dev/null +++ b/acceptance/cypress/tests/a11y/mapsBlock.cy.js @@ -0,0 +1,27 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Maps block + it('Maps Block (/block/maps-block)', () => { + cy.navigate('/block/maps-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // there are two copies of slate h3, + // which have with the same id + { + id: 'duplicate-id-active', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/newsItemContenttype.cy.js b/acceptance/cypress/tests/a11y/newsItemContenttype.cy.js new file mode 100644 index 00000000..b69ab382 --- /dev/null +++ b/acceptance/cypress/tests/a11y/newsItemContenttype.cy.js @@ -0,0 +1,27 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //news-item + it('news-item (/content-types/news-item)', () => { + cy.navigate('/content-types/news-item'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // there are two copies of slate h3, + // which have with the same id + { + id: 'duplicate-id-active', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/pageContent.cy.js b/acceptance/cypress/tests/a11y/pageContent.cy.js new file mode 100644 index 00000000..cb9ef0e1 --- /dev/null +++ b/acceptance/cypress/tests/a11y/pageContent.cy.js @@ -0,0 +1,27 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + //Page + it('Page (/content-types/page)', () => { + cy.navigate('/content-types/page'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // there are two copies of slate h3, + // which have with the same id + { + id: 'duplicate-id-active', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/searchBlock.cy.js b/acceptance/cypress/tests/a11y/searchBlock.cy.js new file mode 100644 index 00000000..b7c09e54 --- /dev/null +++ b/acceptance/cypress/tests/a11y/searchBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Search-block + it('Search-block (/block/search-block)', () => { + cy.navigate('/block/search-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/separatorBlock.cy.js b/acceptance/cypress/tests/a11y/separatorBlock.cy.js new file mode 100644 index 00000000..3f095ec3 --- /dev/null +++ b/acceptance/cypress/tests/a11y/separatorBlock.cy.js @@ -0,0 +1,28 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // separator-block + it('Separator-block (/block/separator-block)', () => { + cy.navigate('/block/separator-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // there are copies of slate h3, + // which have with the same id + { + id: 'duplicate-id-active', + enabled: false, + }, + + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/tableBlock.cy.js b/acceptance/cypress/tests/a11y/tableBlock.cy.js new file mode 100644 index 00000000..dc755b09 --- /dev/null +++ b/acceptance/cypress/tests/a11y/tableBlock.cy.js @@ -0,0 +1,26 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Table-block + it('Table-block (/block/table-block)', () => { + cy.navigate('/block/table-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + rules: [ + // the example page intentionally omits the h1 + { + id: 'page-has-heading-one', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/teaserBlock.cy.js b/acceptance/cypress/tests/a11y/teaserBlock.cy.js new file mode 100644 index 00000000..dbe4b4db --- /dev/null +++ b/acceptance/cypress/tests/a11y/teaserBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Teaser-block + it('Teaser-block (/block/teaser-block)', () => { + cy.navigate('/block/teaser-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/textBlock.cy.js b/acceptance/cypress/tests/a11y/textBlock.cy.js new file mode 100644 index 00000000..7a4d6ee7 --- /dev/null +++ b/acceptance/cypress/tests/a11y/textBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Text-block + it('Text-block (/block/text-block)', () => { + cy.navigate('/block/text-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/tocBlock.cy.js b/acceptance/cypress/tests/a11y/tocBlock.cy.js new file mode 100644 index 00000000..bc3bb27d --- /dev/null +++ b/acceptance/cypress/tests/a11y/tocBlock.cy.js @@ -0,0 +1,18 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Table of Contents + it('Table of Contents (/block/toc-block)', () => { + cy.navigate('/block/toc-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe(); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/a11y/videoBlock.cy.js b/acceptance/cypress/tests/a11y/videoBlock.cy.js new file mode 100644 index 00000000..80ba07e4 --- /dev/null +++ b/acceptance/cypress/tests/a11y/videoBlock.cy.js @@ -0,0 +1,27 @@ +describe('a11y tests', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('/'); + }); + + // Video Block + it('Video Block (/block/video-block)', () => { + cy.navigate('/block/video-block'); + cy.wait(2000); + cy.injectAxe(); + cy.configureAxe({ + // Disabling 'image-alt' + // semantic-ui-react's Embed doesn't include an alt tag for the placeholder image + rules: [ + { + id: 'image-alt', + enabled: false, + }, + ], + }); + cy.checkAccessibility(); + }); +}); \ No newline at end of file diff --git a/acceptance/cypress/tests/basic.cy.js b/acceptance/cypress/tests/main/basic.cy.js similarity index 98% rename from acceptance/cypress/tests/basic.cy.js rename to acceptance/cypress/tests/main/basic.cy.js index 98bae1dc..edd532f3 100644 --- a/acceptance/cypress/tests/basic.cy.js +++ b/acceptance/cypress/tests/main/basic.cy.js @@ -1,7 +1,7 @@ import { getSlateEditorAndType, getSelectedSlateEditor, -} from '../support/slate'; +} from '../../support/slate'; context('Basic Acceptance Tests', () => { describe('Text Block Tests', () => { diff --git a/acceptance/cypress/tests/blocks-map.cy.js b/acceptance/cypress/tests/main/blocks-map.cy.js similarity index 100% rename from acceptance/cypress/tests/blocks-map.cy.js rename to acceptance/cypress/tests/main/blocks-map.cy.js diff --git a/acceptance/cypress/tests/blocks-table.cy.js b/acceptance/cypress/tests/main/blocks-table.cy.js similarity index 100% rename from acceptance/cypress/tests/blocks-table.cy.js rename to acceptance/cypress/tests/main/blocks-table.cy.js diff --git a/acceptance/cypress/tests/listing-grid.cy.js b/acceptance/cypress/tests/main/listing-grid.cy.js similarity index 100% rename from acceptance/cypress/tests/listing-grid.cy.js rename to acceptance/cypress/tests/main/listing-grid.cy.js diff --git a/acceptance/cypress/tests/nav.cy.js b/acceptance/cypress/tests/main/nav.cy.js similarity index 100% rename from acceptance/cypress/tests/nav.cy.js rename to acceptance/cypress/tests/main/nav.cy.js diff --git a/acceptance/docker-compose-a11y.yml b/acceptance/docker-compose-a11y.yml new file mode 100644 index 00000000..39c14a34 --- /dev/null +++ b/acceptance/docker-compose-a11y.yml @@ -0,0 +1,55 @@ +version: "3" + +services: + + addon-dev: + build: + context: ../ + dockerfile: ./dockerfiles/Dockerfile.acceptance + args: + ADDON_NAME: "${ADDON_NAME}" + ADDON_PATH: "${ADDON_PATH}" + VOLTO_VERSION: ${VOLTO_VERSION:-17} + volumes: + - ${CURRENT_DIR}:/app/src/addons/${ADDON_PATH}/ + environment: + RAZZLE_INTERNAL_API_PATH: http://backend-acceptance-a11y:8080/Plone + RAZZLE_API_PATH: http://localhost:8080/Plone + HOST: 0.0.0.0 + ports: + - 3000:3000 + - 3001:3001 + tty: true + depends_on: + - backend-acceptance-a11y + profiles: + - dev + + addon-acceptance: + build: + context: ../ + dockerfile: ./dockerfiles/Dockerfile + args: + ADDON_NAME: "${ADDON_NAME}" + ADDON_PATH: "${ADDON_PATH}" + VOLTO_VERSION: ${VOLTO_VERSION:-17} + environment: + RAZZLE_INTERNAL_API_PATH: http://backend-acceptance-a11y:8080/Plone + RAZZLE_API_PATH: http://localhost:8080/Plone + HOST: 0.0.0.0 + ports: + - 3000:3000 + depends_on: + - backend-acceptance-a11y + profiles: + - prod + + backend-acceptance-a11y: + image: ghcr.io/kitconcept/voltolighttheme:latest + environment: + CORS_: true + ports: + - 8080:8080 + profiles: + - dev + - prod diff --git a/news/300.feature b/news/300.feature new file mode 100644 index 00000000..b8270f0c --- /dev/null +++ b/news/300.feature @@ -0,0 +1 @@ +Added a11y tests infrastructure @sneridagh diff --git a/src/components/Blocks/Listing/ImageGallery.jsx b/src/components/Blocks/Listing/ImageGallery.jsx index ebfa0a22..67922fc8 100644 --- a/src/components/Blocks/Listing/ImageGallery.jsx +++ b/src/components/Blocks/Listing/ImageGallery.jsx @@ -22,6 +22,7 @@ const renderLeftNav = (onClick, disabled) => { className="image-gallery-icon image-gallery-left-nav primary basic" disabled={disabled} onClick={onClick} + aria-label="Go to previous slide" > @@ -33,6 +34,7 @@ const renderRightNav = (onClick, disabled) => { className="image-gallery-icon image-gallery-right-nav primary basic" disabled={disabled} onClick={onClick} + aria-label="Go to next slide" > diff --git a/src/theme/_footer.scss b/src/theme/_footer.scss index 2256f016..3aae8dde 100644 --- a/src/theme/_footer.scss +++ b/src/theme/_footer.scss @@ -7,7 +7,10 @@ background-color: $lightgrey; font-size: 18px; - a.powered-by, + a.powered-by { + color: $blue-for-grey-contrast; + font-size: 14px; + } .footer-branding { font-size: 14px; } @@ -15,6 +18,7 @@ .footer-message { font-weight: $bold; a { + color: $blue-for-grey-contrast; font-weight: inherit; text-decoration: underline; } @@ -35,6 +39,9 @@ &:last-of-type { border: none; } + a { + color: $blue-for-grey-contrast; + } } }