diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9617b413f30..40ab7c19091 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -376,3 +376,33 @@ jobs: with: name: 'cypress-visual-screenshots' path: '${{ github.workspace }}/apps/storybook/cypress-visual-screenshots' + a11y: + name: Test for a11y violations + needs: install + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Use Node 18.X + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Cache node_modules + uses: actions/cache@v3 + id: cache-node-modules + with: + path: | + node_modules + packages/*/node_modules + apps/*/node_modules + playgrounds/*/node_modules + key: modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }} + + - run: yarn build --filter=itwinui-react + + - uses: cypress-io/github-action@v5 + with: + working-directory: testing/a11y + component: true diff --git a/apps/storybook/cypress-visual-screenshots/baseline/Breadcrumbs.test.ts-Overflow.png b/apps/storybook/cypress-visual-screenshots/baseline/Breadcrumbs.test.ts-Overflow.png index ef2dfad1297..03c63b35023 100755 Binary files a/apps/storybook/cypress-visual-screenshots/baseline/Breadcrumbs.test.ts-Overflow.png and b/apps/storybook/cypress-visual-screenshots/baseline/Breadcrumbs.test.ts-Overflow.png differ diff --git a/apps/storybook/package.json b/apps/storybook/package.json index 60d17fa1531..1dd5ff51505 100644 --- a/apps/storybook/package.json +++ b/apps/storybook/package.json @@ -18,7 +18,7 @@ "@types/react": "^18.0.2", "@types/react-dom": "^18.0.2", "configs": "*", - "cypress": "12.11.0", + "cypress": "12.16.0", "cypress-image-diff-js": "1.23.0", "dotenv-cli": "7.0.0", "eslint": "^8.14.0", diff --git a/apps/storybook/scripts/run-tests.js b/apps/storybook/scripts/run-tests.js index fe88d617da4..5cdeb25a0f0 100644 --- a/apps/storybook/scripts/run-tests.js +++ b/apps/storybook/scripts/run-tests.js @@ -5,7 +5,7 @@ const spawn = require('child_process').spawn; const args = process.argv.slice(2).join(' '); -const IMAGE_NAME = 'cypress/included:12.11.0'; // https://hub.docker.com/r/cypress/included +const IMAGE_NAME = 'cypress/included:12.16.0'; // https://hub.docker.com/r/cypress/included // Need to use this script because current directory variable is different in different shells const dockerProcess = spawn( diff --git a/examples/Anchor.asbutton.tsx b/examples/Anchor.asbutton.tsx index a55af7691dd..e4960a98510 100644 --- a/examples/Anchor.asbutton.tsx +++ b/examples/Anchor.asbutton.tsx @@ -7,7 +7,7 @@ import { Anchor } from '@itwin/itwinui-react'; export default () => { return ( - + console.log('Button clicked!')}> Anchor as a button ); diff --git a/examples/ButtonGroup.input.tsx b/examples/ButtonGroup.input.tsx index eee0472139f..6c9e48f01f0 100644 --- a/examples/ButtonGroup.input.tsx +++ b/examples/ButtonGroup.input.tsx @@ -10,7 +10,7 @@ import { Flex, Button, } from '@itwin/itwinui-react'; -import { SvgSearch, SvgAdd } from '@itwin/itwinui-icons-react'; +import { SvgSearch } from '@itwin/itwinui-icons-react'; export default () => { return ( diff --git a/examples/ButtonGroup.overflow.tsx b/examples/ButtonGroup.overflow.tsx index 0c3d0537ccb..b6fdc6b099a 100644 --- a/examples/ButtonGroup.overflow.tsx +++ b/examples/ButtonGroup.overflow.tsx @@ -9,14 +9,7 @@ import { IconButton, MenuItem, } from '@itwin/itwinui-react'; -import { - SvgAdd, - SvgEdit, - SvgDelete, - SvgUndo, - SvgMore, - SvgPlaceholder, -} from '@itwin/itwinui-icons-react'; +import { SvgMore, SvgPlaceholder } from '@itwin/itwinui-icons-react'; export default () => { const buttons = Array(12) diff --git a/examples/ColorPicker.advancedPopover.tsx b/examples/ColorPicker.advancedPopover.tsx index d792719314e..2baca0ee17a 100644 --- a/examples/ColorPicker.advancedPopover.tsx +++ b/examples/ColorPicker.advancedPopover.tsx @@ -5,7 +5,6 @@ import * as React from 'react'; import { ColorBuilder, - ColorInputPanel, ColorPalette, ColorPicker, ColorValue, diff --git a/examples/ComboBox.custom.tsx b/examples/ComboBox.custom.tsx index 6f7245b15ba..1ff65912a2a 100644 --- a/examples/ComboBox.custom.tsx +++ b/examples/ComboBox.custom.tsx @@ -24,7 +24,10 @@ export default () => { }, []); const itemRenderer = React.useCallback( - ({ value, label }, { isSelected, id }) => ( + ( + { value, label }: { value: string; label: string }, + { isSelected, id }: { isSelected: boolean; id: string }, + ) => ( { const [value, setValue] = React.useState(''); diff --git a/examples/ComboBox.loading.tsx b/examples/ComboBox.loading.tsx index fd77a24e5ed..08ba3620a39 100644 --- a/examples/ComboBox.loading.tsx +++ b/examples/ComboBox.loading.tsx @@ -3,7 +3,8 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import { ComboBox, MenuItemSkeleton, SelectOption } from '@itwin/itwinui-react'; +import type { SelectOption } from '@itwin/itwinui-react'; +import { ComboBox, MenuItemSkeleton } from '@itwin/itwinui-react'; export default () => { const countriesList = React.useMemo( diff --git a/examples/Dialog.dismissible.tsx b/examples/Dialog.dismissible.tsx index c5177f341c2..e8e6c4995b8 100644 --- a/examples/Dialog.dismissible.tsx +++ b/examples/Dialog.dismissible.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +// import * as ReactDOM from 'react-dom'; import { Dialog, Button, diff --git a/examples/Dialog.draggable.tsx b/examples/Dialog.draggable.tsx index 62bd7d548ff..9d651263a81 100644 --- a/examples/Dialog.draggable.tsx +++ b/examples/Dialog.draggable.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +// import * as ReactDOM from 'react-dom'; import { Dialog, Button, diff --git a/examples/Dialog.main.tsx b/examples/Dialog.main.tsx index f643e254cb7..35f8bab9006 100644 --- a/examples/Dialog.main.tsx +++ b/examples/Dialog.main.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +// import * as ReactDOM from 'react-dom'; import { Dialog, Button } from '@itwin/itwinui-react'; export default () => { diff --git a/examples/Dialog.nondismissible.tsx b/examples/Dialog.nondismissible.tsx index d140d1fb38c..51e9389c653 100644 --- a/examples/Dialog.nondismissible.tsx +++ b/examples/Dialog.nondismissible.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +// import * as ReactDOM from 'react-dom'; import { Dialog, Button } from '@itwin/itwinui-react'; export default () => { diff --git a/examples/Dialog.placement.tsx b/examples/Dialog.placement.tsx index b618e5037bd..9d4289ffb98 100644 --- a/examples/Dialog.placement.tsx +++ b/examples/Dialog.placement.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +// import * as ReactDOM from 'react-dom'; import { Dialog, Button } from '@itwin/itwinui-react'; export default () => { diff --git a/examples/Fieldset.disabled.tsx b/examples/Fieldset.disabled.tsx index fbf01a44727..f5d400e26dc 100644 --- a/examples/Fieldset.disabled.tsx +++ b/examples/Fieldset.disabled.tsx @@ -3,7 +3,7 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import * as React from 'react'; -import { Fieldset, LabeledInput, Flex } from '@itwin/itwinui-react'; +import { Fieldset, LabeledInput } from '@itwin/itwinui-react'; export default () => { return ( diff --git a/package.json b/package.json index ba9a0a1419a..03d252974c2 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "packages/*", "playgrounds/*", "internal/*", - "examples" + "examples", + "testing/*" ] }, "lint-staged": { diff --git a/testing/a11y/.gitignore b/testing/a11y/.gitignore new file mode 100644 index 00000000000..69d7a533360 --- /dev/null +++ b/testing/a11y/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +.next/ +cypress/videos +cypress/screenshots diff --git a/testing/a11y/cypress.config.js b/testing/a11y/cypress.config.js new file mode 100644 index 00000000000..f8ff4f1fa03 --- /dev/null +++ b/testing/a11y/cypress.config.js @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import { defineConfig } from 'cypress'; +import react from '@vitejs/plugin-react'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); +const axeCorePath = require.resolve('axe-core'); + +export default defineConfig({ + component: { + devServer: { + framework: 'react', + bundler: 'vite', + viteConfig: { + plugins: [react()], + }, + }, + env: { axeCorePath }, + video: false, + screenshotOnRunFailure: false, + setupNodeEvents(on, config) { + on('task', { + log(message) { + console.log(message); + return null; + }, + table(message) { + console.table(message); + return null; + }, + }); + }, + }, +}); diff --git a/testing/a11y/cypress/support/component-index.html b/testing/a11y/cypress/support/component-index.html new file mode 100644 index 00000000000..94af47b3d14 --- /dev/null +++ b/testing/a11y/cypress/support/component-index.html @@ -0,0 +1,25 @@ + + + + + + + + a11y testing + + +
+

a11y testing

+
+
+ + diff --git a/testing/a11y/cypress/support/component.ts b/testing/a11y/cypress/support/component.ts new file mode 100644 index 00000000000..5f05ff04d80 --- /dev/null +++ b/testing/a11y/cypress/support/component.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +// *********************************************************** +// This example support/component.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +import { mount } from 'cypress/react18'; + +declare global { + namespace Cypress { + interface Chainable { + mount: typeof mount; + } + } +} + +Cypress.Commands.add('mount', mount); + +import '@itwin/itwinui-react/styles.css'; + +import 'cypress-axe'; diff --git a/testing/a11y/package.json b/testing/a11y/package.json new file mode 100644 index 00000000000..efbd4a214f8 --- /dev/null +++ b/testing/a11y/package.json @@ -0,0 +1,18 @@ +{ + "name": "a11y", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "cypress run --component", + "open": "cypress open --component", + "clean": "rimraf .turbo && rimraf node_modules" + }, + "dependencies": { + "@itwin/itwinui-react": "3.0.0-dev.3", + "axe-core": "^4.7.2", + "cypress": "^12.16.0", + "cypress-axe": "^1.4.0", + "examples": "*" + } +} diff --git a/testing/a11y/src/Component.cy.tsx b/testing/a11y/src/Component.cy.tsx new file mode 100644 index 00000000000..b050e5f4729 --- /dev/null +++ b/testing/a11y/src/Component.cy.tsx @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import * as React from 'react'; +import * as allExamples from 'examples'; +import { ThemeProvider } from '@itwin/itwinui-react'; + +describe('Should have no WCAG violations', () => { + Object.entries(allExamples).forEach(([name, Component]) => { + it(name, () => { + cy.mount( + + + , + ); + cy.injectAxe({ + axeCorePath: Cypress.env('axeCorePath'), + }); + cy.checkA11y(undefined, undefined, (violations) => { + const violationData = violations.map(({ id, help }) => ({ + Component: name, + 'Rule ID': id, + Description: help, + })); + cy.task('table', violationData); + }); + }); + }); +}); diff --git a/turbo.json b/turbo.json index a22e21711f6..7658dd8fd01 100644 --- a/turbo.json +++ b/turbo.json @@ -27,6 +27,9 @@ "@itwin/itwinui-css#test": { "dependsOn": ["build"] }, + "a11y#test": { + "dependsOn": ["^build"] + }, "lint": { "outputs": [] }, diff --git a/yarn.lock b/yarn.lock index 5daed719723..9d648ba1c1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4747,6 +4747,11 @@ axe-core@^4.2.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.2.tgz#dcf7fb6dea866166c3eab33d68208afe4d5f670c" integrity sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA== +axe-core@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0" + integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== + babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" @@ -5981,6 +5986,11 @@ csv@^5.5.0: csv-stringify "^5.6.5" stream-transform "^2.1.3" +cypress-axe@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-1.4.0.tgz#e67482bfe9e740796bf77c7823f19781a8a2faff" + integrity sha512-Ut7NKfzjyKm0BEbt2WxuKtLkIXmx6FD2j0RwdvO/Ykl7GmB/qRQkwbKLk3VP35+83hiIr8GKD04PDdrTK5BnyA== + cypress-image-diff-js@1.23.0: version "1.23.0" resolved "https://registry.yarnpkg.com/cypress-image-diff-js/-/cypress-image-diff-js-1.23.0.tgz#13b830998b0737a4a8b66283851ebd33184957f9" @@ -6000,10 +6010,10 @@ cypress-recurse@^1.13.1: resolved "https://registry.yarnpkg.com/cypress-recurse/-/cypress-recurse-1.20.0.tgz#66c09d876ce1c143daa62fea222b5b80067a7fc9" integrity sha512-8/gqot/XnVkSF8ssgn3zLRTfPw7Bum2tMIOxf6NO+Wqk0MBQdd4NPNVCObllZmmviLsGmF6ZXwlbXZ8TYvD6dw== -cypress@12.11.0: - version "12.11.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.11.0.tgz#b46dc6a1d0387f59a4b5c6a18cc03884fd61876e" - integrity sha512-TJE+CCWI26Hwr5Msb9GpQhFLubdYooW0fmlPwTsfiyxmngqc7+SZGLPeIkj2dTSSZSEtpQVzOzvcnzH0o8G7Vw== +cypress@12.16.0, cypress@^12.16.0: + version "12.16.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.16.0.tgz#d0dcd0725a96497f4c60cf54742242259847924c" + integrity sha512-mwv1YNe48hm0LVaPgofEhGCtLwNIQEjmj2dJXnAkY1b4n/NE9OtgPph4TyS+tOtYp5CKtRmDvBzWseUXQTjbTg== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -11534,17 +11544,17 @@ pkg-dir@^5.0.0: dependencies: find-up "^5.0.0" -playwright-core@1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.33.0.tgz#269efe29a927cd6d144d05f3c2d2f72bd72447a1" - integrity sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw== +playwright-core@1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.0.tgz#b7871b742b4a5c8714b7fa2f570c280a061cb414" + integrity sha512-muMXyPmIx/2DPrCHOD1H1ePT01o7OdKxKj2ebmCAYvqhUy+Y1bpal7B0rdoxros7YrXI294JT/DWw2LqyiqTPA== playwright@^1.32.1: - version "1.33.0" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.33.0.tgz#88df1cffe97718ab8a02303e12c9133681ec7fab" - integrity sha512-+zzU3V2TslRX2ETBRgQKsKytYBkJeLZ2xzUj4JohnZnxQnivoUvOvNbRBYWSYykQTO0Y4zb8NwZTYFUO+EpPBQ== + version "1.35.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.35.0.tgz#4e3b3ea2495d6fd671700f77b2f97b3adedf80f1" + integrity sha512-xhFhsoBmKPQfj3dM+HbIiFVlqRCZp2rwdJd/QFd9YBuidabo3TkVv0iuxPQ4vZoMwtSI7qzjY93f5ohdC97hww== dependencies: - playwright-core "1.33.0" + playwright-core "1.35.0" pngjs@^3.0.0, pngjs@^3.3.3, pngjs@^3.4.0: version "3.4.0"