diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a2a00e7..e30c265 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -31,3 +31,37 @@ jobs: - name: ๐Ÿ› ๏ธ Generate static site run: pnpm generate + + # run Playwright tests in parallel job to speed up workflow speed + playwright: + name: Run tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v3 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + cache: pnpm + + - name: ๐Ÿ“ฆ Install dependencies + run: pnpm install + + # install system dependencies for Playwright + # see: https://playwright.dev/docs/browsers#install-system-dependencies + - name: ๐Ÿ“ฆ Install Playwright system dependencies + run: pnpm exec playwright install-deps + + - name: ๐Ÿงช Run tests + run: pnpm test:playwright + + # upload report if tests failed for debugging + - uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: playwright-report + path: | + playwright-report/ + test-results/ diff --git a/.gitignore b/.gitignore index dc13514..a5268a4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ logs .env .env.* !.env.example + +# Tests +playwright-report +test-results diff --git a/README.md b/README.md index f5f135f..9e1cbc7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Additional features: - [nuxt i18n](https://nuxt.com/modules/i18n) - [eslint](https://nuxt.com/modules/eslint) and [prettier](https://prettier.io/) setup (also runs pre-commit) +- [Playwright](https://playwright.dev) for testing - [Docker](https://www.docker.com) setup - GitHub action to check and build code in CI diff --git a/eslint.config.mjs b/eslint.config.mjs index 0318787..9b0c0ab 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,11 @@ // @ts-check +import playwright from "eslint-plugin-playwright"; import withNuxt from "./.nuxt/eslint.config.mjs"; export default withNuxt([ // Your custom configs here + { + ...playwright.configs["flat/recommended"], + files: ["tests/**"], + }, ]); diff --git a/package.json b/package.json index bce474a..ab523bd 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dev": "nuxt dev", "generate": "nuxt generate", "preview": "nuxt preview", + "test:playwright": "playwright install && playwright test", "lint": "eslint .", "lint:fix": "eslint . --fix", "format": "prettier --write .", @@ -23,6 +24,9 @@ }, "devDependencies": { "@nuxt/eslint": "~0.3.10", + "@nuxt/test-utils": "^3.12.1", + "@playwright/test": "~1.43.1", + "eslint-plugin-playwright": "^1.6.0", "lint-staged": "^15.2.2", "prettier": "^3.2.5", "sass": "^1.76.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..7380ab6 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,27 @@ +import type { ConfigOptions } from "@nuxt/test-utils/playwright"; +import { defineConfig, devices } from "@playwright/test"; +import { fileURLToPath } from "node:url"; + +/** + * Global Playwright configuration. + * + * @see https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: !!process.env.CI, + reporter: [["html", { open: "never" }]], + use: { + trace: process.env.CI ? "retain-on-failure" : "off", + video: process.env.CI ? "retain-on-failure" : "off", + nuxt: { + rootDir: fileURLToPath(new URL(".", import.meta.url)), + }, + }, + projects: [ + { name: "chromium", use: { ...devices["Desktop Chrome"] } }, + { name: "firefox", use: { ...devices["Desktop Firefox"] } }, + { name: "webkit", use: { ...devices["Desktop Safari"] } }, + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f50b218..b92a571 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,15 @@ importers: '@nuxt/eslint': specifier: ~0.3.10 version: 0.3.10(eslint@8.57.0)(nuxt@3.11.2(@parcel/watcher@2.4.1)(@types/node@20.12.7)(@unocss/reset@0.59.4)(encoding@0.1.13)(eslint@8.57.0)(floating-vue@5.2.2(@nuxt/kit@3.11.2(rollup@4.17.2))(vue@3.4.26(typescript@5.4.5)))(ioredis@5.4.1)(optionator@0.9.3)(rollup@4.17.2)(sass@1.76.0)(terser@5.31.0)(typescript@5.4.5)(unocss@0.59.4(postcss@8.4.38)(rollup@4.17.2)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0)))(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0)))(rollup@4.17.2)(typescript@5.4.5)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0)) + '@nuxt/test-utils': + specifier: ^3.12.1 + version: 3.12.1(@playwright/test@1.43.1)(h3@1.11.1)(playwright-core@1.43.1)(rollup@4.17.2)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0))(vue-router@4.3.2(vue@3.4.26(typescript@5.4.5)))(vue@3.4.26(typescript@5.4.5)) + '@playwright/test': + specifier: ~1.43.1 + version: 1.43.1 + eslint-plugin-playwright: + specifier: ^1.6.0 + version: 1.6.0(eslint@8.57.0) lint-staged: specifier: ^15.2.2 version: 15.2.2 @@ -653,6 +662,46 @@ packages: resolution: {integrity: sha512-KH6wxzsNys69daSO0xUv0LEBAfhwwjK1M+0Cdi1/vxmifCslMIY7lN11B4eywSfscbyVPAYJvANyc7XiVPImBQ==} hasBin: true + '@nuxt/test-utils@3.12.1': + resolution: {integrity: sha512-VRLNcDz9Ad/4pjHdNRVLPs5DVIO5IJ0ij81PLmsE/lt+5oeeIQld+AgHgcqM4BM1YKsXTBuavbk1mEBqj7h/+A==} + engines: {node: ^14.18.0 || >=16.10.0} + peerDependencies: + '@cucumber/cucumber': ^10.3.1 + '@jest/globals': ^29.5.0 + '@playwright/test': ^1.42.1 + '@testing-library/vue': ^7.0.0 || ^8.0.1 + '@vitest/ui': ^0.34.6 || ^1.0.0 + '@vue/test-utils': ^2.4.2 + h3: '*' + happy-dom: ^9.10.9 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 + jsdom: ^22.0.0 || ^23.0.0 || ^24.0.0 + playwright-core: ^1.34.3 + vite: '*' + vitest: ^0.34.6 || ^1.0.0 + vue: ^3.3.4 + vue-router: ^4.0.0 + peerDependenciesMeta: + '@cucumber/cucumber': + optional: true + '@jest/globals': + optional: true + '@playwright/test': + optional: true + '@testing-library/vue': + optional: true + '@vitest/ui': + optional: true + '@vue/test-utils': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright-core: + optional: true + vitest: + optional: true + '@nuxt/ui-templates@1.3.3': resolution: {integrity: sha512-3BG5doAREcD50dbKyXgmjD4b1GzY8CUy3T41jMhHZXNDdaNwOd31IBq+D6dV00OSrDVhzrTVj0IxsUsnMyHvIQ==} @@ -752,6 +801,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.43.1': + resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==} + engines: {node: '>=16'} + hasBin: true + '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} @@ -2029,6 +2083,16 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + eslint-plugin-playwright@1.6.0: + resolution: {integrity: sha512-tI1E/EDbHT4Fx5KvukUG3RTIT0gk44gvTP8bNwxLCFsUXVM98ZJG5zWU6Om5JOzH9FrmN4AhMu/UKyEsu0ZoDA==} + engines: {node: '>=16.6.0'} + peerDependencies: + eslint: '>=8.40.0' + eslint-plugin-jest: '>=25' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true + eslint-plugin-unicorn@52.0.0: resolution: {integrity: sha512-1Yzm7/m+0R4djH0tjDjfVei/ju2w3AzUGjG6q8JnuNIL5xIwsflyCooW5sfBvQp2pMYQFSWWCFONsjCax1EHng==} engines: {node: '>=16'} @@ -2123,6 +2187,10 @@ packages: externality@1.0.2: resolution: {integrity: sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==} + fake-indexeddb@5.0.2: + resolution: {integrity: sha512-cB507r5T3D55DfclY01GLkninZLfU7HXV/mhVRTnTRm5k2u+fY7Fof2dBkr80p5t7G7dlA/G5dI87QiMdPpMCQ==} + engines: {node: '>=18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2210,6 +2278,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3217,6 +3290,16 @@ packages: pkg-types@1.1.0: resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} + playwright-core@1.43.1: + resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==} + engines: {node: '>=16'} + hasBin: true + + playwright@1.43.1: + resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==} + engines: {node: '>=16'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -4173,6 +4256,9 @@ packages: terser: optional: true + vitest-environment-nuxt@1.0.0: + resolution: {integrity: sha512-AWMO9h4HdbaFdPWZw34gALFI8gbBiOpvfbyeZwHIPfh4kWg/TwElYHvYMQ61WPUlCGaS5LebfHkaI0WPyb//Iw==} + vscode-jsonrpc@6.0.0: resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} engines: {node: '>=8.0.0 || >=10.0.0'} @@ -5196,6 +5282,42 @@ snapshots: - rollup - supports-color + '@nuxt/test-utils@3.12.1(@playwright/test@1.43.1)(h3@1.11.1)(playwright-core@1.43.1)(rollup@4.17.2)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0))(vue-router@4.3.2(vue@3.4.26(typescript@5.4.5)))(vue@3.4.26(typescript@5.4.5))': + dependencies: + '@nuxt/kit': 3.11.2(rollup@4.17.2) + '@nuxt/schema': 3.11.2(rollup@4.17.2) + c12: 1.10.0 + consola: 3.2.3 + defu: 6.1.4 + destr: 2.0.3 + estree-walker: 3.0.3 + execa: 8.0.1 + fake-indexeddb: 5.0.2 + get-port-please: 3.1.2 + h3: 1.11.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + node-fetch-native: 1.6.4 + ofetch: 1.3.4 + pathe: 1.1.2 + perfect-debounce: 1.0.0 + radix3: 1.1.2 + scule: 1.3.0 + std-env: 3.7.0 + ufo: 1.5.3 + unenv: 1.9.0 + unplugin: 1.10.1 + vite: 5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0) + vitest-environment-nuxt: 1.0.0(@playwright/test@1.43.1)(h3@1.11.1)(playwright-core@1.43.1)(rollup@4.17.2)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0))(vue-router@4.3.2(vue@3.4.26(typescript@5.4.5)))(vue@3.4.26(typescript@5.4.5)) + vue: 3.4.26(typescript@5.4.5) + vue-router: 4.3.2(vue@3.4.26(typescript@5.4.5)) + optionalDependencies: + '@playwright/test': 1.43.1 + playwright-core: 1.43.1 + transitivePeerDependencies: + - rollup + - supports-color + '@nuxt/ui-templates@1.3.3': {} '@nuxt/vite-builder@3.11.2(@types/node@20.12.7)(eslint@8.57.0)(optionator@0.9.3)(rollup@4.17.2)(sass@1.76.0)(terser@5.31.0)(typescript@5.4.5)(vue@3.4.26(typescript@5.4.5))': @@ -5350,6 +5472,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.43.1': + dependencies: + playwright: 1.43.1 + '@polka/url@1.0.0-next.25': {} '@rollup/plugin-alias@5.1.0(rollup@4.17.2)': @@ -6826,6 +6952,11 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-playwright@1.6.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + globals: 13.24.0 + eslint-plugin-unicorn@52.0.0(eslint@8.57.0): dependencies: '@babel/helper-validator-identifier': 7.24.5 @@ -6998,6 +7129,8 @@ snapshots: pathe: 1.1.2 ufo: 1.5.3 + fake-indexeddb@5.0.2: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -7089,6 +7222,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -8298,6 +8434,14 @@ snapshots: mlly: 1.7.0 pathe: 1.1.2 + playwright-core@1.43.1: {} + + playwright@1.43.1: + dependencies: + playwright-core: 1.43.1 + optionalDependencies: + fsevents: 2.3.2 + pluralize@8.0.0: {} postcss-calc@9.0.1(postcss@8.4.38): @@ -9287,6 +9431,27 @@ snapshots: sass: 1.76.0 terser: 5.31.0 + vitest-environment-nuxt@1.0.0(@playwright/test@1.43.1)(h3@1.11.1)(playwright-core@1.43.1)(rollup@4.17.2)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0))(vue-router@4.3.2(vue@3.4.26(typescript@5.4.5)))(vue@3.4.26(typescript@5.4.5)): + dependencies: + '@nuxt/test-utils': 3.12.1(@playwright/test@1.43.1)(h3@1.11.1)(playwright-core@1.43.1)(rollup@4.17.2)(vite@5.2.10(@types/node@20.12.7)(sass@1.76.0)(terser@5.31.0))(vue-router@4.3.2(vue@3.4.26(typescript@5.4.5)))(vue@3.4.26(typescript@5.4.5)) + transitivePeerDependencies: + - '@cucumber/cucumber' + - '@jest/globals' + - '@playwright/test' + - '@testing-library/vue' + - '@vitest/ui' + - '@vue/test-utils' + - h3 + - happy-dom + - jsdom + - playwright-core + - rollup + - supports-color + - vite + - vitest + - vue + - vue-router + vscode-jsonrpc@6.0.0: {} vscode-languageclient@7.0.0: diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 0000000..815210b --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,6 @@ +import { expect, test } from "@nuxt/test-utils/playwright"; + +test("example test", async ({ page, goto }) => { + await goto("/", { waitUntil: "hydration" }); + await expect(page.getByRole("heading", { name: "Welcome to Nuxt!" })).toBeVisible(); +});