diff --git a/apps/web/.gitignore b/apps/web/.gitignore index f886745..e134596 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -34,3 +34,8 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index c2d22db..9903988 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -1,9 +1,17 @@ +'use client'; import { Button } from '@repo/ui/components/ui/button'; +import { useState } from 'react'; export default function Page() { + const [clicked, setClicked] = useState(false); + + function handleClick() { + setClicked(!clicked); + } + return (
- +
); } diff --git a/apps/web/package.json b/apps/web/package.json index 59033db..9e81b07 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,7 +11,8 @@ "check-types": "tsc --noEmit", "lint-staged": "lint-staged", "graphql:codegen": "graphql-codegen --config graphql.config.cjs", - "test:unit": "vitest" + "test:unit": "vitest", + "test:e2e": "playwright test" }, "dependencies": { "@apollo/client": "catalog:", @@ -27,6 +28,7 @@ "@graphql-codegen/typescript": "catalog:", "@graphql-codegen/typescript-operations": "catalog:", "@graphql-typed-document-node/core": "catalog:", + "@playwright/test": "^1.49.1", "@repo/eslint-config": "workspace:*", "@repo/lint-staged-config": "workspace:*", "@repo/typescript-config": "workspace:*", diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts new file mode 100644 index 0000000..757616f --- /dev/null +++ b/apps/web/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: Boolean(process.env.CI), + /* Run tests in files in parallel */ + fullyParallel: true, + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + testDir: './tests', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/apps/web/tests/e2e/app/page.spec.ts b/apps/web/tests/e2e/app/page.spec.ts new file mode 100644 index 0000000..ef28f76 --- /dev/null +++ b/apps/web/tests/e2e/app/page.spec.ts @@ -0,0 +1,7 @@ +import test, { expect } from '@playwright/test'; + +test('should navigate page and click button', async ({ page }) => { + await page.goto('http://localhost:3000/'); + await page.getByRole('button', { name: 'Click me' }).click(); + await expect(page.getByRole('button', { name: 'Clicked' })).toBeVisible(); +}); diff --git a/apps/web/vitest.config.mts b/apps/web/vitest.config.mts index 2dac6ad..4915b13 100644 --- a/apps/web/vitest.config.mts +++ b/apps/web/vitest.config.mts @@ -6,5 +6,7 @@ export default defineConfig({ plugins: [tsconfigPaths(), react()], test: { environment: 'jsdom', + exclude: ['**/e2e/**', '**/*.spec.ts'], + include: ['**/*.test.{ts,tsx}'], }, }); diff --git a/package.json b/package.json index 5428a10..cf284ab 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "prepare": "husky", "lint-staged": "turbo lint-staged", "graphql:codegen": "turbo graphql:codegen", - "test:unit": "turbo test:unit" + "test:unit": "turbo test:unit", + "test:e2e": "turbo test:e2e" }, "devDependencies": { "husky": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de5662a..881c079 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,7 +148,7 @@ importers: version: 16.9.0 next: specifier: 'catalog:' - version: 15.0.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 'catalog:' version: 19.0.0 @@ -171,6 +171,9 @@ importers: '@graphql-typed-document-node/core': specifier: 'catalog:' version: 3.2.0(graphql@16.9.0) + '@playwright/test': + specifier: ^1.49.1 + version: 1.49.1 '@repo/eslint-config': specifier: workspace:* version: link:../../packages/eslint-config @@ -1673,6 +1676,11 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playwright/test@1.49.1': + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + engines: {node: '>=18'} + hasBin: true + '@radix-ui/react-compose-refs@1.1.0': resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} peerDependencies: @@ -3169,6 +3177,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} @@ -4174,6 +4187,16 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + engines: {node: '>=18'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -7055,6 +7078,10 @@ snapshots: '@pkgr/core@0.1.1': {} + '@playwright/test@1.49.1': + dependencies: + playwright: 1.49.1 + '@radix-ui/react-compose-refs@1.1.0(@types/react@19.0.1)(react@19.0.0)': dependencies: react: 19.0.0 @@ -8933,6 +8960,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -9687,7 +9717,7 @@ snapshots: natural-orderby@5.0.0: {} - next@15.0.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.0.4 '@swc/counter': 0.1.3 @@ -9707,6 +9737,7 @@ snapshots: '@next/swc-linux-x64-musl': 15.0.4 '@next/swc-win32-arm64-msvc': 15.0.4 '@next/swc-win32-x64-msvc': 15.0.4 + '@playwright/test': 1.49.1 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' @@ -9936,6 +9967,14 @@ snapshots: dependencies: find-up: 5.0.0 + playwright-core@1.49.1: {} + + playwright@1.49.1: + dependencies: + playwright-core: 1.49.1 + optionalDependencies: + fsevents: 2.3.2 + pluralize@8.0.0: {} possible-typed-array-names@1.0.0: {} diff --git a/turbo.json b/turbo.json index bc6dbbd..95cc23f 100644 --- a/turbo.json +++ b/turbo.json @@ -25,6 +25,9 @@ }, "test:unit": { "cache": false + }, + "test:e2e": { + "cache": false } } }