diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..f531b667 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Configure .npmrc + run: echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" > ~/.npmrc + + - uses: ./actions/setup-node-and-install-deps + with: + access-token-read-github-package-registry: ${{ secrets.ACCESS_TOKEN_READ_GITHUB_PACKAGE_REGISTRY }} + + - name: Install playwright browsers + run: npx playwright install --with-deps + + - name: Test + run: npm run test diff --git a/.gitignore b/.gitignore index 9a777219..2851fe26 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,8 @@ npm-debug.log* # storybook /storybook-static build-storybook.log + +# Test +/test-results/ +/playwright-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6ab40f0a..1efb36bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "license": "Apache-2.0", "devDependencies": { + "@axe-core/playwright": "^4.7.3", "@babel/core": "7.22.11", "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", @@ -16,6 +17,7 @@ "@headlessui/react": "^1.7.10", "@markuplint/jsx-parser": "3.9.0", "@markuplint/react-spec": "^3.10.0", + "@playwright/experimental-ct-react": "^1.38.1", "@rollup/plugin-terser": "0.4.3", "@rollup/plugin-typescript": "11.1.3", "@storybook/addon-actions": "7.4.1", @@ -52,6 +54,7 @@ "lint-staged": "14.0.1", "markuplint": "3.12.0", "npm-run-all": "4.1.5", + "playwright": "^1.38.1", "prettier": "3.0.2", "react": "18.2.0", "react-dom": "18.2.0", @@ -110,6 +113,18 @@ "x-default-browser": "bin/x-default-browser.js" } }, + "node_modules/@axe-core/playwright": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.7.3.tgz", + "integrity": "sha512-v2PRgAyGvop7bamrTpNJtc5b1R7giAPnMzZXrS/VDZBCY5+uwVYtCNgDvBsqp5P1QMZxUMoBN+CERJUTMjFN0A==", + "dev": true, + "dependencies": { + "axe-core": "^4.7.0" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -207,12 +222,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -318,22 +333,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -376,16 +391,16 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.19.tgz", - "integrity": "sha512-m6h1cJvn+OJ+R3jOHp30faq5xKJ7VbjwDj5RGgHuRlU9hrMeKsGC+JpihkR5w1g7IfseCPPtZ0r7/hB4UKaYlA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.19" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -495,9 +510,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.19.tgz", - "integrity": "sha512-Tinq7ybnEPFFXhlYOYFiSjespWQk0dq2dRNAiMdRTOYQzEGqnnNyrTxPYHP5r6wGjlF1rFgABdDV0g8EwD6Qbg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -527,14 +542,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -599,9 +614,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -2221,19 +2236,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.19.tgz", - "integrity": "sha512-ZCcpVPK64krfdScRbpxF6xA5fz7IOsfMwx1tcACvCzt6JY+0aHkBk7eIU8FRDSZRU5Zei6Z4JfgAxN1bqXGECg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2242,13 +2257,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -4295,6 +4310,94 @@ "node": ">=14" } }, + "node_modules/@playwright/experimental-ct-core": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.38.1.tgz", + "integrity": "sha512-2EzqzwBJty08elQonIXrQxUd8HKL7dHLJ2T/dJTmgVFwGsnJYsnU03EUlKlatUfajMYVC7xGT+Bqhur2hJPC3A==", + "dev": true, + "dependencies": { + "playwright": "1.38.1", + "playwright-core": "1.38.1", + "vite": "^4.3.9" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/experimental-ct-react": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.38.1.tgz", + "integrity": "sha512-V5Eu28jrF8Vd3/dvweJaewa49MrnAOQdR3U24j3gu8oj7SNS6lWv09pioZVTI8cBj3yKCSX1Bcq8Vd2bSGYtOg==", + "dev": true, + "dependencies": { + "@playwright/experimental-ct-core": "1.38.1", + "@vitejs/plugin-react": "^4.0.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@playwright/experimental-ct-react/node_modules/@babel/core": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@playwright/experimental-ct-react/node_modules/@vitejs/plugin-react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz", + "integrity": "sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.20", + "@babel/plugin-transform-react-jsx-self": "^7.22.5", + "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "@types/babel__core": "^7.20.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0" + } + }, + "node_modules/@playwright/experimental-ct-react/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -7241,9 +7344,9 @@ "dev": true }, "node_modules/@types/babel__core": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", + "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", "dev": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -20225,6 +20328,50 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plur": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", @@ -26793,6 +26940,15 @@ "default-browser-id": "3.0.0" } }, + "@axe-core/playwright": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.7.3.tgz", + "integrity": "sha512-v2PRgAyGvop7bamrTpNJtc5b1R7giAPnMzZXrS/VDZBCY5+uwVYtCNgDvBsqp5P1QMZxUMoBN+CERJUTMjFN0A==", + "dev": true, + "requires": { + "axe-core": "^4.7.0" + } + }, "@babel/code-frame": { "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", @@ -26867,12 +27023,12 @@ } }, "@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -26951,19 +27107,19 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { @@ -26994,16 +27150,16 @@ } }, "@babel/helper-module-transforms": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.19.tgz", - "integrity": "sha512-m6h1cJvn+OJ+R3jOHp30faq5xKJ7VbjwDj5RGgHuRlU9hrMeKsGC+JpihkR5w1g7IfseCPPtZ0r7/hB4UKaYlA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.19" + "@babel/helper-validator-identifier": "^7.22.20" } }, "@babel/helper-optimise-call-expression": { @@ -27077,9 +27233,9 @@ "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.19.tgz", - "integrity": "sha512-Tinq7ybnEPFFXhlYOYFiSjespWQk0dq2dRNAiMdRTOYQzEGqnnNyrTxPYHP5r6wGjlF1rFgABdDV0g8EwD6Qbg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -27100,14 +27256,14 @@ } }, "@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", "dev": true, "requires": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" } }, "@babel/highlight": { @@ -27156,9 +27312,9 @@ } }, "@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -28238,31 +28394,31 @@ } }, "@babel/traverse": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.19.tgz", - "integrity": "sha512-ZCcpVPK64krfdScRbpxF6xA5fz7IOsfMwx1tcACvCzt6JY+0aHkBk7eIU8FRDSZRU5Zei6Z4JfgAxN1bqXGECg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", "dev": true, "requires": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -29817,6 +29973,71 @@ "dev": true, "optional": true }, + "@playwright/experimental-ct-core": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.38.1.tgz", + "integrity": "sha512-2EzqzwBJty08elQonIXrQxUd8HKL7dHLJ2T/dJTmgVFwGsnJYsnU03EUlKlatUfajMYVC7xGT+Bqhur2hJPC3A==", + "dev": true, + "requires": { + "playwright": "1.38.1", + "playwright-core": "1.38.1", + "vite": "^4.3.9" + } + }, + "@playwright/experimental-ct-react": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.38.1.tgz", + "integrity": "sha512-V5Eu28jrF8Vd3/dvweJaewa49MrnAOQdR3U24j3gu8oj7SNS6lWv09pioZVTI8cBj3yKCSX1Bcq8Vd2bSGYtOg==", + "dev": true, + "requires": { + "@playwright/experimental-ct-core": "1.38.1", + "@vitejs/plugin-react": "^4.0.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@vitejs/plugin-react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.1.0.tgz", + "integrity": "sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==", + "dev": true, + "requires": { + "@babel/core": "^7.22.20", + "@babel/plugin-transform-react-jsx-self": "^7.22.5", + "@babel/plugin-transform-react-jsx-source": "^7.22.5", + "@types/babel__core": "^7.20.2", + "react-refresh": "^0.14.0" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } + } + }, "@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -31749,9 +31970,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", + "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", "dev": true, "requires": { "@babel/parser": "^7.20.7", @@ -41476,6 +41697,31 @@ "pathe": "^1.1.0" } }, + "playwright": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.38.1" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", + "dev": true + }, "plur": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", diff --git a/package.json b/package.json index 68ea162c..6e2bb5d7 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "sideEffects": false, "scripts": { "build": "tsup", - "test": "vitest run", + "test": "npm run test:unit && npm run test:e2e", + "test:unit": "vitest run", + "test:e2e": "playwright test -c playwright.config.ts", "lint": "run-p lint:*", "lint:eslint": "eslint --ext .ts,.tsx ./src --max-warnings=0", "lint:prettier": "prettier --check src", @@ -42,6 +44,7 @@ "url": "git+https://github.com/ubie-oss/ubie-ui.git" }, "devDependencies": { + "@axe-core/playwright": "^4.7.3", "@babel/core": "7.22.11", "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", @@ -49,6 +52,7 @@ "@headlessui/react": "^1.7.10", "@markuplint/jsx-parser": "3.9.0", "@markuplint/react-spec": "^3.10.0", + "@playwright/experimental-ct-react": "^1.38.1", "@rollup/plugin-terser": "0.4.3", "@rollup/plugin-typescript": "11.1.3", "@storybook/addon-actions": "7.4.1", @@ -85,6 +89,7 @@ "lint-staged": "14.0.1", "markuplint": "3.12.0", "npm-run-all": "4.1.5", + "playwright": "^1.38.1", "prettier": "3.0.2", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..c9166546 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,45 @@ +import { defineConfig, devices } from '@playwright/experimental-ct-react'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests/playwright', + /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ + snapshotDir: './__snapshots__', + /* Maximum time one test can run for. */ + timeout: 10 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html', { open: process.env.CI ? 'never' : 'on-failure' }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + /* Port to use for Playwright component endpoint. */ + ctPort: 3100, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['iPhone 13 Pro'] }, + }, + ], +}); diff --git a/playwright/index.html b/playwright/index.html new file mode 100644 index 00000000..2032be59 --- /dev/null +++ b/playwright/index.html @@ -0,0 +1,12 @@ + + + + + + Testing Page + + +
+ + + diff --git a/playwright/index.tsx b/playwright/index.tsx new file mode 100644 index 00000000..ac6de14b --- /dev/null +++ b/playwright/index.tsx @@ -0,0 +1,2 @@ +// Import styles, initialize component theme here. +// import '../src/common.css'; diff --git a/tests/playwright/Button.spec.tsx b/tests/playwright/Button.spec.tsx new file mode 100644 index 00000000..ea961a93 --- /dev/null +++ b/tests/playwright/Button.spec.tsx @@ -0,0 +1,203 @@ +import AxeBuilder from '@axe-core/playwright'; +import { test, expect } from '@playwright/experimental-ct-react'; +import { Button } from '../../src/components/Button/Button'; +import type { Result } from 'axe-core'; + +test.use({ viewport: { width: 500, height: 500 } }); + +test.describe('Axe', () => { + test('Buttonコンポーネントに有効なテキストを与えていないのでbutton-nameの警告が発生する', async ({ mount, page }) => { + await mount(); + const a11yScanResult = await new AxeBuilder({ page }) + .disableRules(['page-has-heading-one', 'landmark-one-main']) + .analyze(); + expect(a11yScanResult.violations.length).toEqual(1); + expect(a11yScanResult.violations[0].id).toEqual('button-name'); + }); + + test('Buttonコンポーネントのすべてのバリエーションをチェックする', async ({ mount, page }) => { + test.slow(); + + await mount( +
+ + + + + + + + + + +
, + ); + + const buttons = await page.$$('button'); + + const axe = new AxeBuilder({ page }).disableRules(['page-has-heading-one', 'landmark-one-main']); + + const violations: Result[] = []; + + const result = await axe.analyze(); + violations.push(...result.violations); + + // button要素にフォーカスが当たった際のスタイルも検証する + for (const button of buttons) { + await button.focus(); + const result = await axe.analyze(); + // すでに発見された問題の重複を除外 + const newViolations = result.violations.filter((violation) => { + return !violations.find((v) => v.id === violation.id && v.nodes[0].html === violation.nodes[0].html); + }); + violations.push(...newViolations); + } + + // button要素にマウスポインターがホバーした際のスタイルも検証する + for (const button of buttons) { + const box = await button.boundingBox(); + const x = box?.x ?? 0; + const y = box?.y ?? 0; + const width = box?.width ?? 0; + const height = box?.height ?? 0; + await page.mouse.move(x + width / 2, y + height / 2); + const result = await axe.analyze(); + // すでに発見された問題の重複を除外 + const newViolations = result.violations.filter((violation) => { + return !violations.find((v) => v.id === violation.id && v.nodes[0].html === violation.nodes[0].html); + }); + violations.push(...newViolations); + } + + // authLINEのみ、色のコントラストが不十分 + expect(violations.length).toEqual(1); + expect(violations[0].id).toEqual('color-contrast'); + expect(violations[0].nodes.length).toEqual(1); + expect(violations[0].nodes[0].html).toContain('LINE'); + }); +}); + +test.describe('アクセシビリティ', () => { + const normalButtons = () => ( +
+ + + + + + + + + + +
+ ); + + const disabledButtons = () => ( +
+ + + + + + + + + + +
+ ); + + test('すべてのButtonコンポーネントのroleは`button`である', async ({ page, mount }) => { + await mount(normalButtons()); + + const buttons = await page.getByRole('button').all(); + + await expect(buttons[0]).toHaveText('Button'); + await expect(buttons[1]).toHaveText('Primary button'); + await expect(buttons[2]).toHaveText('Secondary button'); + await expect(buttons[3]).toHaveText('Accent button'); + await expect(buttons[4]).toHaveText('Alert button'); + await expect(buttons[5]).toHaveText('Text button'); + await expect(buttons[6]).toHaveText('Text Alert button'); + await expect(buttons[7]).toHaveText('Google button'); + await expect(buttons[8]).toHaveText('LINE button'); + await expect(buttons[9]).toHaveText('Apple button'); + }); + + test('すべてのButtonコンポーネントのフォーカス可能である', async ({ page, mount }) => { + await mount(normalButtons()); + + const buttons = await page.getByRole('button').all(); + + await buttons[0].focus(); + await expect(buttons[0]).toBeFocused(); + await buttons[1].focus(); + await expect(buttons[1]).toBeFocused(); + await buttons[2].focus(); + await expect(buttons[2]).toBeFocused(); + await buttons[3].focus(); + await expect(buttons[3]).toBeFocused(); + await buttons[4].focus(); + await expect(buttons[4]).toBeFocused(); + await buttons[5].focus(); + await expect(buttons[5]).toBeFocused(); + await buttons[6].focus(); + await expect(buttons[6]).toBeFocused(); + await buttons[7].focus(); + await expect(buttons[7]).toBeFocused(); + await buttons[8].focus(); + await expect(buttons[8]).toBeFocused(); + await buttons[9].focus(); + await expect(buttons[9]).toBeFocused(); + + // toBeFocusedの誤検知がないかの確認 + await buttons[0].focus(); + await expect(buttons[9]).not.toBeFocused(); + }); + + test('disabledが有効なButtonコンポーネントのフォーカス不可能である', async ({ page, mount }) => { + await mount(disabledButtons()); + + const buttons = await page.getByRole('button').all(); + + await buttons[0].focus(); + await expect(buttons[0]).not.toBeFocused(); + await buttons[1].focus(); + await expect(buttons[1]).not.toBeFocused(); + await buttons[2].focus(); + await expect(buttons[2]).not.toBeFocused(); + await buttons[3].focus(); + await expect(buttons[3]).not.toBeFocused(); + await buttons[4].focus(); + await expect(buttons[4]).not.toBeFocused(); + await buttons[5].focus(); + await expect(buttons[5]).not.toBeFocused(); + await buttons[6].focus(); + await expect(buttons[6]).not.toBeFocused(); + await buttons[7].focus(); + await expect(buttons[7]).not.toBeFocused(); + await buttons[8].focus(); + await expect(buttons[8]).not.toBeFocused(); + await buttons[9].focus(); + await expect(buttons[9]).not.toBeFocused(); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..7c6b61df --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +/// +import { defineConfig } from 'vite'; + +export default defineConfig({ + test: { + include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'], + }, +});