diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0662c60..28cdb60 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,3 +35,13 @@ jobs: - name: "Report coverage" if: always() uses: davelosert/vitest-coverage-report-action@v2 + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright tests + run: pnpm test:e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 8f322f0..75c6a76 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,9 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package.json b/package.json index 483bb40..dd36017 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,12 @@ "dev": "next dev", "lint": "next lint", "start": "next start", - "test": "vitest" + "test": "vitest", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", + "test:e2e:update": "playwright test --update-snapshots", + "test:e2e:report": "playwright show-report" }, "dependencies": { "diff": "5.1.0", @@ -18,6 +23,7 @@ "tailwindcss": "3.3.3" }, "devDependencies": { + "@playwright/test": "1.53.1", "@types/diff": "5.2.3", "@types/node": "20.17.12", "@types/react": "18.3.18", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..349dec5 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,28 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [['html', { open: 'never' }]], + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + } + ], + + webServer: { + command: 'pnpm dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}); \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf3c39f..1d03342 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: specifier: 3.3.3 version: 3.3.3 devDependencies: + '@playwright/test': + specifier: 1.53.1 + version: 1.53.1 '@types/diff': specifier: 5.2.3 version: 5.2.3 @@ -381,6 +384,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.53.1': + resolution: {integrity: sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==} + engines: {node: '>=18'} + hasBin: true + '@rollup/rollup-android-arm-eabi@4.29.1': resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==} cpu: [arm] @@ -1040,6 +1048,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} @@ -1543,6 +1556,16 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + playwright-core@1.53.1: + resolution: {integrity: sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.53.1: + resolution: {integrity: sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==} + engines: {node: '>=18'} + hasBin: true + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -2229,6 +2252,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.53.1': + dependencies: + playwright: 1.53.1 + '@rollup/rollup-android-arm-eabi@4.29.1': optional: true @@ -2797,7 +2824,7 @@ snapshots: eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.0)(eslint@8.57.1) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.57.1) eslint-plugin-react: 7.33.2(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.1) @@ -2820,8 +2847,8 @@ snapshots: debug: 4.4.0 enhanced-resolve: 5.15.0 eslint: 8.57.1 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.57.1) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.0)(eslint@8.57.1) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.1 get-tsconfig: 4.7.0 is-core-module: 2.13.0 @@ -2832,7 +2859,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.57.1): + eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -2843,7 +2870,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.0)(eslint@8.57.1): + eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 @@ -2853,7 +2880,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0)(eslint@8.57.1) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.7.2(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.28.1(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -3040,6 +3067,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -3561,6 +3591,14 @@ snapshots: pirates@4.0.6: {} + playwright-core@1.53.1: {} + + playwright@1.53.1: + dependencies: + playwright-core: 1.53.1 + optionalDependencies: + fsevents: 2.3.2 + postcss-import@15.1.0(postcss@8.4.49): dependencies: postcss: 8.4.49 diff --git a/src/app/page.tsx b/src/app/page.tsx index 7589ec3..edbe8a9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ "use client"; import { useSearchParams } from "next/navigation"; -import { Fragment, useState } from "react"; +import { Fragment, useState, useEffect } from "react"; import { diffLines } from "diff"; import lz from "lz-string"; import { createShareUrl } from "@/create-share-url"; @@ -26,6 +26,11 @@ export default function Home() { const [text2, setText2] = useState(textB); const [diffs, setDiffs] = useState(diffLines(textA, textB)); const [copyButtonText, setCopyButtonText] = useState("Copy"); + const [currentURL, setCurrentURL] = useState(""); + + useEffect(() => { + setCurrentURL(window.location.href); + }, []); const calculateDiff = (a: string, b: string) => { setDiffs(diffLines(a, b)); @@ -33,7 +38,7 @@ export default function Home() { const copyUrl = () => { navigator.clipboard.writeText( - createShareUrl(window.location.href, text1, text2) + createShareUrl(currentURL, text1, text2) ); }; @@ -105,7 +110,8 @@ export default function Home() {