From 73ddbdd046338bafbbaed1bd92daa5b400f4291a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 09:33:49 +0200 Subject: [PATCH 01/12] Add end-to-end testing skeleton for fdm-app --- .github/workflows/tests.yml | 92 ++++++++++++++++ fdm-app/.c8rc | 12 ++ fdm-app/.gitignore | 7 ++ fdm-app/package.json | 4 + fdm-app/playwright.config.ts | 53 +++++++++ fdm-app/tests/signin._index.test.ts | 32 ++++++ pnpm-lock.yaml | 165 ++++++++++++++++++++++++++++ 7 files changed, 365 insertions(+) create mode 100644 fdm-app/.c8rc create mode 100644 fdm-app/playwright.config.ts create mode 100644 fdm-app/tests/signin._index.test.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 413eb9609..4628fe44d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -252,3 +252,95 @@ jobs: files: ./fdm-data/coverage/coverage-final.json flags: fdm-data token: ${{ secrets.CODECOV_TOKEN }} + + test-app: + name: app + # Containers must run in Linux based operating systems + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22] + permissions: + contents: read + packages: write + # Docker Hub image that `container-job` executes in + container: node:${{ matrix.node-version }}-bookworm-slim + + # Service containers to run with `container-job` + services: + # Label used to access the service container + postgres: + # Docker Hub image with postgis extension + image: postgis/postgis:17-3.5 + # Provide the password for postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + # Include dependencies for codecov + - name: Install system dependencies + run: apt-get update && apt-get install -y git curl gpg + + # Downloads a copy of the code in your repository before running CI tests + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Cache turbo build setup + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - name: Setup pnpm 10 + uses: pnpm/action-setup@v4 + with: + version: 10.13.1 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + registry-url: 'https://npm.pkg.github.com' + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm i + + - name: Build fdm-data + run: pnpm build + working-directory: ./fdm-data + + - name: Build fdm-core + run: pnpm build + working-directory: ./fdm-core + + - name: Run tests with coverage + run: pnpm test + working-directory: ./fdm-app + env: + # The hostname used to communicate with the PostgreSQL service container + POSTGRES_HOST: postgres + # The default PostgreSQL port + POSTGRES_PORT: 5432 + # the default usernam + POSTGRES_USER: postgres + # the default password + POSTGRES_PASSWORD: postgres + # the default database + POSTGRES_DB: postgres + + - name: fdm-app - Upload results to Codecov + uses: codecov/codecov-action@v5 + with: + files: ./fdm-app/coverage/tmp/*.json + flags: fdm-app + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/fdm-app/.c8rc b/fdm-app/.c8rc new file mode 100644 index 000000000..7219b19b6 --- /dev/null +++ b/fdm-app/.c8rc @@ -0,0 +1,12 @@ +{ + "reporter": ["text", "json", "html"], + "exclude": [ + "**/node_modules/**", + "**/dist/**", + "**/turbo**", + "**/setup-test.ts", + "**.d.ts", + "*.config.ts", + "*.config.js" + ] +} diff --git a/fdm-app/.gitignore b/fdm-app/.gitignore index f99244234..5e8a44fd1 100644 --- a/fdm-app/.gitignore +++ b/fdm-app/.gitignore @@ -6,6 +6,13 @@ node_modules .env .env.production +dist +dist-ssr +playwright-report +test-results +coverage +*.local + .react-router/ # Sentry Config File .env.sentry-build-plugin diff --git a/fdm-app/package.json b/fdm-app/package.json index a6b744214..9d7c60377 100644 --- a/fdm-app/package.json +++ b/fdm-app/package.json @@ -10,6 +10,7 @@ "dotenvx": "dotenvx", "start": "pnpm db:migrate && react-router-serve ./build/server/index.js", "start-dev": "dotenvx run -- pnpm db:migrate && dotenvx run -- react-router-serve ./build/server/index.js", + "test": "dotenvx run playwright test", "db:migrate": "node ./app/lib/fdm-migrate.server.js", "typecheck": "react-router typegen && tsc" }, @@ -81,6 +82,7 @@ }, "devDependencies": { "@dotenvx/dotenvx": "catalog:", + "@playwright/test": "^1.54.1", "@react-router/dev": "^7.7.0", "@react-router/fs-routes": "^7.7.0", "@svenvw/fdm-calculator": "workspace:*", @@ -96,6 +98,8 @@ "@types/react-dom": "^19.1.6", "@types/react-map-gl": "^6.1.7", "@types/validator": "^13.15.2", + "c8": "^10.1.3", + "playwright": "^1.54.1", "postcss": "^8.5.6", "tailwindcss": "^4.1.11", "typescript": "catalog:", diff --git a/fdm-app/playwright.config.ts b/fdm-app/playwright.config.ts new file mode 100644 index 000000000..6376a9a12 --- /dev/null +++ b/fdm-app/playwright.config.ts @@ -0,0 +1,53 @@ +import { defineConfig, devices } from "@playwright/test" + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* 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", + /* 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://localhost:3000", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ], + + /* Start the server in a CI environment such as GitHub Actions */ + webServer: { + command: + "pnpm dotenvx run c8 react-router-serve ./build/server/index.js", + url: "http://localhost:3000", + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/fdm-app/tests/signin._index.test.ts b/fdm-app/tests/signin._index.test.ts new file mode 100644 index 000000000..e12e301c0 --- /dev/null +++ b/fdm-app/tests/signin._index.test.ts @@ -0,0 +1,32 @@ +import { expect, type Page, test } from "@playwright/test" + +async function submitCookieBanner(page: Page) { + const cookieSubmitButton = page.getByRole("button", { name: "accept" }) + await expect(cookieSubmitButton).toBeVisible() + await cookieSubmitButton.click() +} + +test("Sign-in via magic link", async ({ browser }) => { + const ctx = await browser.newContext() + const page = await ctx.newPage() + await page.goto("/") + + await submitCookieBanner(page) + + const loginSubmitButton = page.getByRole("button", { name: "e-mail" }) + await expect(loginSubmitButton, "Login submit button exists.").toBeVisible() + + const loginEmailBox = page.getByPlaceholder("e-mail") + await expect(loginSubmitButton, "Email input box exists.").toBeVisible() + + await loginEmailBox.fill("xyz@gmail.com") + + await loginSubmitButton.click() + + const message = page.getByText("aanmelden...") + expect(message).toBeDefined() + + await page.waitForURL("**/check-your-email") + + return ctx.close() +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1da8abda0..09461b2df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -275,6 +275,9 @@ importers: '@dotenvx/dotenvx': specifier: 'catalog:' version: 1.48.3 + '@playwright/test': + specifier: ^1.54.1 + version: 1.54.1 '@react-router/dev': specifier: ^7.7.0 version: 7.7.0(@react-router/serve@7.7.0(react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3))(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(terser@5.43.1)(typescript@5.8.3)(vite@7.0.5(@types/node@24.1.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0))(yaml@2.8.0) @@ -311,6 +314,12 @@ importers: '@types/validator': specifier: ^13.15.2 version: 13.15.2 + c8: + specifier: ^10.1.3 + version: 10.1.3 + playwright: + specifier: ^1.54.1 + version: 1.54.1 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -2701,6 +2710,11 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@playwright/test@1.54.1': + resolution: {integrity: sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==} + engines: {node: '>=18'} + hasBin: true + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -5336,6 +5350,16 @@ packages: bytewise@1.1.0: resolution: {integrity: sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==} + c8@10.1.3: + resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + monocart-coverage-reports: ^2 + peerDependenciesMeta: + monocart-coverage-reports: + optional: true + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -5487,6 +5511,10 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone-deep@4.0.1: resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} engines: {node: '>=6'} @@ -6461,6 +6489,10 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + find-up@6.3.0: resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6554,6 +6586,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} @@ -6593,6 +6630,10 @@ packages: resolution: {integrity: sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==} engines: {node: '>=10.19'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -7424,6 +7465,10 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + locate-path@7.2.0: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8070,6 +8115,10 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + p-limit@4.0.0: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8078,6 +8127,10 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-locate@6.0.0: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8252,6 +8305,16 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} + playwright-core@1.54.1: + resolution: {integrity: sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.54.1: + resolution: {integrity: sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==} + engines: {node: '>=18'} + hasBin: true + point-in-polygon-hao@1.2.4: resolution: {integrity: sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ==} @@ -9190,6 +9253,10 @@ packages: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -10130,6 +10197,10 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + valibot@0.41.0: resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==} peerDependencies: @@ -10470,6 +10541,10 @@ packages: xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -10489,6 +10564,18 @@ packages: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yocto-queue@1.2.1: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} @@ -13519,6 +13606,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@playwright/test@1.54.1': + dependencies: + playwright: 1.54.1 + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -17087,6 +17178,20 @@ snapshots: bytewise-core: 1.2.3 typewise: 1.0.3 + c8@10.1.3: + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 3.3.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.7 + test-exclude: 7.0.1 + v8-to-istanbul: 9.3.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + cac@6.7.14: {} cacheable-lookup@5.0.4: {} @@ -17252,6 +17357,12 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clone-deep@4.0.1: dependencies: is-plain-object: 2.0.4 @@ -18285,6 +18396,11 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + find-up@6.3.0: dependencies: locate-path: 7.2.0 @@ -18372,6 +18488,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -18415,6 +18534,8 @@ snapshots: xml-utils: 1.10.2 zstddec: 0.1.0 + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -19298,6 +19419,10 @@ snapshots: dependencies: p-locate: 4.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + locate-path@7.2.0: dependencies: p-locate: 6.0.0 @@ -20228,6 +20353,10 @@ snapshots: dependencies: p-try: 2.2.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + p-limit@4.0.0: dependencies: yocto-queue: 1.2.1 @@ -20236,6 +20365,10 @@ snapshots: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-locate@6.0.0: dependencies: p-limit: 4.0.0 @@ -20401,6 +20534,14 @@ snapshots: dependencies: find-up: 6.3.0 + playwright-core@1.54.1: {} + + playwright@1.54.1: + dependencies: + playwright-core: 1.54.1 + optionalDependencies: + fsevents: 2.3.2 + point-in-polygon-hao@1.2.4: dependencies: robust-predicates: 3.0.2 @@ -21488,6 +21629,8 @@ snapshots: repeat-string@1.6.1: {} + require-directory@2.1.1: {} + require-from-string@2.0.2: {} require-in-the-middle@7.5.2: @@ -22478,6 +22621,12 @@ snapshots: uuid@8.3.2: {} + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + valibot@0.41.0(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -22890,6 +23039,8 @@ snapshots: xxhash-wasm@1.1.0: {} + y18n@5.0.8: {} + yallist@3.1.1: {} yallist@4.0.0: {} @@ -22900,6 +23051,20 @@ snapshots: yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + yocto-queue@1.2.1: {} zod@3.25.76: {} From a6c33190981f9933424573e0ef26de2f0070c1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 10:30:23 +0200 Subject: [PATCH 02/12] Move fdm-app environment variables to the workflow yaml --- .github/workflows/tests.yml | 19 ++++++++++++++++++- fdm-app/package.json | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4628fe44d..9d4f2e58d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -324,7 +324,7 @@ jobs: working-directory: ./fdm-core - name: Run tests with coverage - run: pnpm test + run: pnpm run test-ci working-directory: ./fdm-app env: # The hostname used to communicate with the PostgreSQL service container @@ -338,6 +338,23 @@ jobs: # the default database POSTGRES_DB: postgres + # Public name of the application displayed in the UI. + PUBLIC_FDM_NAME: MINAS4 + # Base URL of the application (used for API calls, etc.). + PUBLIC_FDM_URL: http://localhost:3000 + # URL to the privacy policy document. + PUBLIC_FDM_PRIVACY_URL: http://localhost:3000/privacy + # Secret key used to sign session cookies. MUST be a strong, random string. + FDM_SESSION_SECRET: blablabla1 + # Authentication (Better Auth & OAuth Providers) + BETTER_AUTH_SECRET: blablabla2 + # Full base URL of this application (used for redirects by better-auth). + BETTER_AUTH_URL: http://localhost:3000 + # Mapbox API token for displaying maps. + PUBLIC_MAPBOX_TOKEN: ${{ secrets.PUBLIC_MAPBOX_TOKEN }} + # URL to the FlatGeobuf (.fgb) file containing selectable field geometries. + AVAILABLE_FIELDS_URL: https://storage.googleapis.com/fdm-public-data/fields/nl/2024/draft.fgb + - name: fdm-app - Upload results to Codecov uses: codecov/codecov-action@v5 with: diff --git a/fdm-app/package.json b/fdm-app/package.json index 9d7c60377..c5608f87a 100644 --- a/fdm-app/package.json +++ b/fdm-app/package.json @@ -11,6 +11,7 @@ "start": "pnpm db:migrate && react-router-serve ./build/server/index.js", "start-dev": "dotenvx run -- pnpm db:migrate && dotenvx run -- react-router-serve ./build/server/index.js", "test": "dotenvx run playwright test", + "test-ci": "playwright test", "db:migrate": "node ./app/lib/fdm-migrate.server.js", "typecheck": "react-router typegen && tsc" }, From 5749b90ae812163fc8acd5528c11de02023047fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 10:47:26 +0200 Subject: [PATCH 03/12] Add build for fdm-app and remove unnecessary playwright dependency --- .github/workflows/tests.yml | 4 ++++ fdm-app/package.json | 1 - pnpm-lock.yaml | 39 ------------------------------------- 3 files changed, 4 insertions(+), 40 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9d4f2e58d..0e521c898 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -323,6 +323,10 @@ jobs: run: pnpm build working-directory: ./fdm-core + - name: Build fdm-app + run: pnpm build + working-directory: ./fdm-app + - name: Run tests with coverage run: pnpm run test-ci working-directory: ./fdm-app diff --git a/fdm-app/package.json b/fdm-app/package.json index c5608f87a..0cd1e0e15 100644 --- a/fdm-app/package.json +++ b/fdm-app/package.json @@ -100,7 +100,6 @@ "@types/react-map-gl": "^6.1.7", "@types/validator": "^13.15.2", "c8": "^10.1.3", - "playwright": "^1.54.1", "postcss": "^8.5.6", "tailwindcss": "^4.1.11", "typescript": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09461b2df..cd9c9a0f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,45 +9,12 @@ catalogs: '@dotenvx/dotenvx': specifier: ^1.48.3 version: 1.48.3 - '@rollup/plugin-commonjs': - specifier: ^28.0.6 - version: 28.0.6 - '@rollup/plugin-json': - specifier: ^6.1.0 - version: 6.1.0 - '@rollup/plugin-node-resolve': - specifier: ^16.0.1 - version: 16.0.1 - '@rollup/plugin-terser': - specifier: ^0.4.4 - version: 0.4.4 - '@rollup/plugin-typescript': - specifier: ^12.1.4 - version: 12.1.4 - '@vitest/coverage-v8': - specifier: 3.2.4 - version: 3.2.4 better-auth: specifier: ^1.3.3 version: 1.3.3 - drizzle-kit: - specifier: ^0.31.4 - version: 0.31.4 drizzle-orm: specifier: ^0.44.3 version: 0.44.3 - rollup: - specifier: ^4.45.1 - version: 4.45.1 - rollup-plugin-polyfill-node: - specifier: ^0.13.0 - version: 0.13.0 - typedoc: - specifier: ^0.28.7 - version: 0.28.7 - typedoc-plugin-missing-exports: - specifier: ^4.0.0 - version: 4.0.0 typescript: specifier: ^5.8.3 version: 5.8.3 @@ -57,9 +24,6 @@ catalogs: vite-tsconfig-paths: specifier: ^5.1.4 version: 5.1.4 - vitest: - specifier: ^3.2.4 - version: 3.2.4 packageExtensionsChecksum: sha256-VQuFGSJ2NQgmUfUYujaw/wyx7PoF3FcUJ5DmmDpAEDo= @@ -317,9 +281,6 @@ importers: c8: specifier: ^10.1.3 version: 10.1.3 - playwright: - specifier: ^1.54.1 - version: 1.54.1 postcss: specifier: ^8.5.6 version: 8.5.6 From 272b85fd065423e81a6c19f7fc0a475aadafa277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 10:47:54 +0200 Subject: [PATCH 04/12] Apply signin test nitpick comments --- fdm-app/tests/signin._index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fdm-app/tests/signin._index.test.ts b/fdm-app/tests/signin._index.test.ts index e12e301c0..4fd3e979f 100644 --- a/fdm-app/tests/signin._index.test.ts +++ b/fdm-app/tests/signin._index.test.ts @@ -17,14 +17,14 @@ test("Sign-in via magic link", async ({ browser }) => { await expect(loginSubmitButton, "Login submit button exists.").toBeVisible() const loginEmailBox = page.getByPlaceholder("e-mail") - await expect(loginSubmitButton, "Email input box exists.").toBeVisible() + await expect(loginEmailBox, "Email input box exists.").toBeVisible() await loginEmailBox.fill("xyz@gmail.com") await loginSubmitButton.click() const message = page.getByText("aanmelden...") - expect(message).toBeDefined() + expect(message).toBeVisible() await page.waitForURL("**/check-your-email") From 6a85f15c002032e51094529c74b0c4100a0bb205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 11:11:33 +0200 Subject: [PATCH 05/12] Add missing workflow steps --- .github/workflows/tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e521c898..d616a3043 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -315,6 +315,9 @@ jobs: - name: Install Dependencies run: pnpm i + - name: Install Playwright browsers + run: npx playwright install --with-deps\ + - name: Build fdm-data run: pnpm build working-directory: ./fdm-data @@ -323,6 +326,10 @@ jobs: run: pnpm build working-directory: ./fdm-core + - name: Build fdm-calculator + run: pnpm build + working-directory: ./fdm-calculator + - name: Build fdm-app run: pnpm build working-directory: ./fdm-app From cef09834acafbe0681ca34a87ff53021209150fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 11:15:25 +0200 Subject: [PATCH 06/12] Fix playwright install --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d616a3043..d5ed93388 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -316,7 +316,8 @@ jobs: run: pnpm i - name: Install Playwright browsers - run: npx playwright install --with-deps\ + run: pnpm exec playwright install --with-deps + working-directory: ./fdm-app - name: Build fdm-data run: pnpm build From 5149da863461572ac330bd972e173c689dbcddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 30 Jul 2025 11:36:16 +0200 Subject: [PATCH 07/12] Add db:migrate --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5ed93388..a1f6c9358 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -336,7 +336,7 @@ jobs: working-directory: ./fdm-app - name: Run tests with coverage - run: pnpm run test-ci + run: pnpm db:migrate & pnpm test-ci working-directory: ./fdm-app env: # The hostname used to communicate with the PostgreSQL service container From e41c37e7c641bfa6b22276782ee214ff8d4d5e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 1 Aug 2025 15:52:47 +0200 Subject: [PATCH 08/12] Split tests into two stages to be able to complete the user and farm creation wizards only once --- .github/workflows/tests.yml | 67 ++++++----- fdm-app/.gitignore | 2 + fdm-app/app/lib/email.server.ts | 21 +++- fdm-app/package.json | 5 +- ...ght.config.ts => playwright-app.config.ts} | 2 +- fdm-app/playwright-login.config.ts | 46 ++++++++ .../farm.$b_id_farm.fertilizers.test.ts | 11 ++ .../login-tests/sign-in-and-farm.test.ts | 105 +++++++++++++++++ fdm-app/tests/signin._index.test.ts | 32 ----- fdm-app/tests/test-io.ts | 109 ++++++++++++++++++ fdm-app/tests/util.ts | 7 ++ 11 files changed, 341 insertions(+), 66 deletions(-) rename fdm-app/{playwright.config.ts => playwright-app.config.ts} (97%) create mode 100644 fdm-app/playwright-login.config.ts create mode 100644 fdm-app/tests/app-tests/farm.$b_id_farm.fertilizers.test.ts create mode 100644 fdm-app/tests/login-tests/sign-in-and-farm.test.ts delete mode 100644 fdm-app/tests/signin._index.test.ts create mode 100644 fdm-app/tests/test-io.ts create mode 100644 fdm-app/tests/util.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a1f6c9358..1d55240da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -282,7 +282,34 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - + env: + # The hostname used to communicate with the PostgreSQL service container + POSTGRES_HOST: postgres + # The default PostgreSQL port + POSTGRES_PORT: 5432 + # the default usernam + POSTGRES_USER: postgres + # the default password + POSTGRES_PASSWORD: postgres + # the default database + POSTGRES_DB: postgres + + # Public name of the application displayed in the UI. + PUBLIC_FDM_NAME: MINAS4 + # Base URL of the application (used for API calls, etc.). + PUBLIC_FDM_URL: http://localhost:3000 + # URL to the privacy policy document. + PUBLIC_FDM_PRIVACY_URL: http://localhost:3000/privacy + # Secret key used to sign session cookies. MUST be a strong, random string. + FDM_SESSION_SECRET: blablabla1 + # Authentication (Better Auth & OAuth Providers) + BETTER_AUTH_SECRET: blablabla2 + # Full base URL of this application (used for redirects by better-auth). + BETTER_AUTH_URL: http://localhost:3000 + # Mapbox API token for displaying maps. + PUBLIC_MAPBOX_TOKEN: ${{ secrets.PUBLIC_MAPBOX_TOKEN }} + # URL to the FlatGeobuf (.fgb) file containing selectable field geometries. + AVAILABLE_FIELDS_URL: https://storage.googleapis.com/fdm-public-data/fields/nl/2024/draft.fgb steps: # Include dependencies for codecov - name: Install system dependencies @@ -335,37 +362,17 @@ jobs: run: pnpm build working-directory: ./fdm-app - - name: Run tests with coverage - run: pnpm db:migrate & pnpm test-ci + - name: Migrate database + run: pnpm db:migrate + working-directory: ./fdm-app + + - name: Run login tests with coverage + run: pnpm test-login-ci working-directory: ./fdm-app - env: - # The hostname used to communicate with the PostgreSQL service container - POSTGRES_HOST: postgres - # The default PostgreSQL port - POSTGRES_PORT: 5432 - # the default usernam - POSTGRES_USER: postgres - # the default password - POSTGRES_PASSWORD: postgres - # the default database - POSTGRES_DB: postgres - # Public name of the application displayed in the UI. - PUBLIC_FDM_NAME: MINAS4 - # Base URL of the application (used for API calls, etc.). - PUBLIC_FDM_URL: http://localhost:3000 - # URL to the privacy policy document. - PUBLIC_FDM_PRIVACY_URL: http://localhost:3000/privacy - # Secret key used to sign session cookies. MUST be a strong, random string. - FDM_SESSION_SECRET: blablabla1 - # Authentication (Better Auth & OAuth Providers) - BETTER_AUTH_SECRET: blablabla2 - # Full base URL of this application (used for redirects by better-auth). - BETTER_AUTH_URL: http://localhost:3000 - # Mapbox API token for displaying maps. - PUBLIC_MAPBOX_TOKEN: ${{ secrets.PUBLIC_MAPBOX_TOKEN }} - # URL to the FlatGeobuf (.fgb) file containing selectable field geometries. - AVAILABLE_FIELDS_URL: https://storage.googleapis.com/fdm-public-data/fields/nl/2024/draft.fgb + - name: Run app tests with coverage + run: pnpm test-ci + working-directory: ./fdm-app - name: fdm-app - Upload results to Codecov uses: codecov/codecov-action@v5 diff --git a/fdm-app/.gitignore b/fdm-app/.gitignore index 5e8a44fd1..cfb131f96 100644 --- a/fdm-app/.gitignore +++ b/fdm-app/.gitignore @@ -4,12 +4,14 @@ node_modules /build /uploads .env +.env.test .env.production dist dist-ssr playwright-report test-results +test-tmp coverage *.local diff --git a/fdm-app/app/lib/email.server.ts b/fdm-app/app/lib/email.server.ts index c78e6a11a..7599e0d69 100644 --- a/fdm-app/app/lib/email.server.ts +++ b/fdm-app/app/lib/email.server.ts @@ -10,6 +10,12 @@ import { serverConfig } from "~/lib/config.server" import type { ExtendedUser } from "~/types/extended-user" const client = new postmark.ServerClient(String(process.env.POSTMARK_API_KEY)) +const writeMagicLinkFile = + (process.env.CI && process.env.CI.length > 0) || + (process.env.WRITE_MAGIC_LINK_FILE && + process.env.WRITE_MAGIC_LINK_FILE.length > 0) +const sendRealEmail = + process.env.POSTMARK_API_KEY && process.env.POSTMARK_API_KEY.length > 0 interface Email { From: string @@ -95,7 +101,9 @@ export async function renderMagicLinkEmail( } export async function sendEmail(email: Email): Promise { - await client.sendEmail(email) + if (sendRealEmail) { + await client.sendEmail(email) + } } // Helper function to send magic link emails, to be passed to fdm-core @@ -103,6 +111,17 @@ export async function sendMagicLinkEmailToUser( emailAddress: string, magicLinkUrl: string, ): Promise { + if (writeMagicLinkFile) { + const testIo = await import("@/tests/test-io") + await testIo.writeTestFileLine( + testIo.magicLinkUrlFileName, + magicLinkUrl, + { + tmpUrl: testIo.runtimeTestTmpUrl(), + }, + ) + } + const email = await renderMagicLinkEmail(emailAddress, magicLinkUrl) await sendEmail(email) } diff --git a/fdm-app/package.json b/fdm-app/package.json index 0cd1e0e15..ac442808e 100644 --- a/fdm-app/package.json +++ b/fdm-app/package.json @@ -10,8 +10,9 @@ "dotenvx": "dotenvx", "start": "pnpm db:migrate && react-router-serve ./build/server/index.js", "start-dev": "dotenvx run -- pnpm db:migrate && dotenvx run -- react-router-serve ./build/server/index.js", - "test": "dotenvx run playwright test", - "test-ci": "playwright test", + "test": "dotenvx run -- playwright test -c ./playwright-login.config.ts & dotenvx run playwright test -c ./playwright-app.config.ts", + "test-login-ci": "playwright test -c ./playwright-login.config.ts", + "test-ci": "playwright test -c ./playwright-app.config.ts", "db:migrate": "node ./app/lib/fdm-migrate.server.js", "typecheck": "react-router typegen && tsc" }, diff --git a/fdm-app/playwright.config.ts b/fdm-app/playwright-app.config.ts similarity index 97% rename from fdm-app/playwright.config.ts rename to fdm-app/playwright-app.config.ts index 6376a9a12..ad0887ca8 100644 --- a/fdm-app/playwright.config.ts +++ b/fdm-app/playwright-app.config.ts @@ -4,7 +4,7 @@ import { defineConfig, devices } from "@playwright/test" * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./tests", + testDir: "./tests/app-tests", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/fdm-app/playwright-login.config.ts b/fdm-app/playwright-login.config.ts new file mode 100644 index 000000000..0aab094e9 --- /dev/null +++ b/fdm-app/playwright-login.config.ts @@ -0,0 +1,46 @@ +import { defineConfig, devices } from "@playwright/test" + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests/login-tests", + /* 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", + /* 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://localhost:3000", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + + /* Start the server in a CI environment such as GitHub Actions */ + webServer: { + command: + "pnpm dotenvx run -- c8 react-router-serve ./build/server/index.js", + env: { + WRITE_MAGIC_LINK_FILE: "1", + }, + url: "http://localhost:3000", + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/fdm-app/tests/app-tests/farm.$b_id_farm.fertilizers.test.ts b/fdm-app/tests/app-tests/farm.$b_id_farm.fertilizers.test.ts new file mode 100644 index 000000000..b18fce774 --- /dev/null +++ b/fdm-app/tests/app-tests/farm.$b_id_farm.fertilizers.test.ts @@ -0,0 +1,11 @@ +import { expect, test } from "@playwright/test" +import { loadSessionFromFile } from "../test-io" + +test.beforeEach(async ({ page, context }) => { + await loadSessionFromFile(context) + await page.goto("/farm") +}) + +test.fixme("There is a set of default fertilizers", async ({ page }) => { + await expect(page.getByRole("table")).toBeVisible() +}) diff --git a/fdm-app/tests/login-tests/sign-in-and-farm.test.ts b/fdm-app/tests/login-tests/sign-in-and-farm.test.ts new file mode 100644 index 000000000..cbf2a830c --- /dev/null +++ b/fdm-app/tests/login-tests/sign-in-and-farm.test.ts @@ -0,0 +1,105 @@ +import test, { expect } from "@playwright/test" +import { + loadSessionFromFile, + saveSessionToFile, + testFileLine, + writeTestFile, +} from "../test-io" +import { submitCookieBanner } from "../util" +import { magicLinkUrlFileName } from "../test-io" +import fs from "node:fs/promises" + +test.describe.configure({ mode: "serial" }) + +test("User can sign in via email and complete creating their profile", async ({ + page, + context, +}) => { + await page.goto("/") + await submitCookieBanner(page) + + const loginSubmitButton = page.getByRole("button", { name: "e-mail" }) + await expect( + loginSubmitButton, + "Login submit button does not exist.", + ).toBeVisible() + + const loginEmailBox = page.getByPlaceholder("e-mail") + await expect(loginEmailBox, "Email input box does not exist.").toBeVisible() + + await loginEmailBox.fill("xyz@gmail.com") + + await loginSubmitButton.click() + + const message = page.getByText("aanmelden...") + expect(message).toBeVisible() + + await writeTestFile(magicLinkUrlFileName, "") + await page.waitForURL("**/check-your-email") + const url = await testFileLine(magicLinkUrlFileName) + await page.goto(url) + + const firstNameBox = page.getByLabel("Voornaam") + await expect(firstNameBox, "There is no first name box.").toBeVisible() + await firstNameBox.fill("Test") + const lastNameBox = page.getByLabel("Achternaam") + await expect(lastNameBox, "There is no last name box.").toBeVisible() + await lastNameBox.fill("User") + + const nameSubmitButton = page.getByRole("button", { name: "Doorgaan" }) + await expect(nameSubmitButton, "There is no submit button.").toBeVisible() + await nameSubmitButton.click() + + await page.waitForURL("/farm") + + await saveSessionToFile(context) +}) + +test("User can create a farm business and select parcels", async ({ + page, + context, +}) => { + // Load session from previous test if it is missing for some reason + const currentCookies = await context.cookies() + if ( + !currentCookies.find((c) => c.name === "better-auth.session_token") || + !currentCookies.find((c) => c.name === "toast-session") + ) { + await loadSessionFromFile(context) + } + + // Click the Create Business button + await page.goto("/farm") + const createBusinessButton = page.getByRole("link", { + name: "Maak een bedrijf", + }) + await expect( + createBusinessButton, + "There is no create business button", + ).toBeVisible() + await createBusinessButton.click() + + // Fill in the business creation form and submit + const businessNameBox = page.getByLabel("bedrijfsnaam") + await expect(businessNameBox, "There is no business name box").toBeVisible() + await businessNameBox.fill("Example Business") + + const businessNameSubmitButton = page.getByRole("button", { + name: "volgende", + }) + await expect( + businessNameSubmitButton, + "There is no continue button after business name and address", + ).toBeVisible() + await businessNameSubmitButton.click() + + // Go to the Shape File Upload + const shapeFileUploadButton = page.getByRole("button", { + name: "Bestand uploaden", + }) + await expect( + shapeFileUploadButton, + "There is no shape file upload button", + ).toBeVisible() + await shapeFileUploadButton.click() +}) diff --git a/fdm-app/tests/signin._index.test.ts b/fdm-app/tests/signin._index.test.ts deleted file mode 100644 index 4fd3e979f..000000000 --- a/fdm-app/tests/signin._index.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect, type Page, test } from "@playwright/test" - -async function submitCookieBanner(page: Page) { - const cookieSubmitButton = page.getByRole("button", { name: "accept" }) - await expect(cookieSubmitButton).toBeVisible() - await cookieSubmitButton.click() -} - -test("Sign-in via magic link", async ({ browser }) => { - const ctx = await browser.newContext() - const page = await ctx.newPage() - await page.goto("/") - - await submitCookieBanner(page) - - const loginSubmitButton = page.getByRole("button", { name: "e-mail" }) - await expect(loginSubmitButton, "Login submit button exists.").toBeVisible() - - const loginEmailBox = page.getByPlaceholder("e-mail") - await expect(loginEmailBox, "Email input box exists.").toBeVisible() - - await loginEmailBox.fill("xyz@gmail.com") - - await loginSubmitButton.click() - - const message = page.getByText("aanmelden...") - expect(message).toBeVisible() - - await page.waitForURL("**/check-your-email") - - return ctx.close() -}) diff --git a/fdm-app/tests/test-io.ts b/fdm-app/tests/test-io.ts new file mode 100644 index 000000000..071586b10 --- /dev/null +++ b/fdm-app/tests/test-io.ts @@ -0,0 +1,109 @@ +import type { BrowserContext } from "@playwright/test" +import fs from "node:fs/promises" +import url from "node:url" + +export const testTmpDir = "../test-tmp/" +export const sessionFileName = "session.json" +export const magicLinkUrlFileName = "magicLink.txt" + +interface TestIOCommonOptions { + tmpUrl?: URL +} + +function testFileUrl(fileName: string, options: TestIOCommonOptions) { + return new URL( + fileName, + options.tmpUrl ?? new URL(testTmpDir, import.meta.url), + ) +} + +export function runtimeTestTmpUrl(cwd = process.cwd()) { + return new URL( + "./test-tmp/", + url.pathToFileURL( + cwd.endsWith("/") || cwd.endsWith("\\") ? cwd : `${cwd}/`, + ), + ) +} + +async function ensureSessionDir(options: TestIOCommonOptions) { + let exists = false + const url = options.tmpUrl ?? new URL(testTmpDir, import.meta.url) + try { + exists = (await fs.stat(url)).isDirectory() + } catch (_) {} + + if (!exists) { + await fs.mkdir(url, { recursive: true }) + } +} + +async function checkTestFileExists( + fileName: string, + options: TestIOCommonOptions, +) { + let exists = false + try { + exists = (await fs.stat(testFileUrl(fileName, options))).isFile() + } catch (_) {} + + return exists +} + +export function readTestFile( + fileName: string, + options: TestIOCommonOptions = {}, +) { + if (!checkTestFileExists(fileName, options)) + throw new Error(`Test file ${fileName} does not exist.`) + + return fs.readFile(testFileUrl(fileName, options), { encoding: "utf-8" }) +} + +export async function writeTestFile( + fileName: string, + contents: string, + flag = "w", + options: TestIOCommonOptions = {}, +) { + await ensureSessionDir(options) + return fs.writeFile(testFileUrl(fileName, options), contents, { + encoding: "utf-8", + flag, + }) +} + +export async function loadSessionFromFile(context: BrowserContext) { + const cookies = JSON.parse(await readTestFile(sessionFileName)) + + context.addCookies(cookies) +} + +export async function saveSessionToFile(context: BrowserContext) { + return writeTestFile( + sessionFileName, + JSON.stringify(await context.cookies()), + ) +} + +export async function writeTestFileLine( + fileName: string, + line: string, + options: TestIOCommonOptions = {}, +) { + return writeTestFile(fileName, `${line}\n`, "w+", options) +} + +export async function testFileLine( + fileName: string, + options: TestIOCommonOptions = {}, +) { + const readStream = await fs.open(testFileUrl(fileName, options)) + let myLine = "" + for await (const line of readStream.readLines()) { + myLine = line + break + } + readStream.close() + return myLine +} diff --git a/fdm-app/tests/util.ts b/fdm-app/tests/util.ts new file mode 100644 index 000000000..3ed24b7f0 --- /dev/null +++ b/fdm-app/tests/util.ts @@ -0,0 +1,7 @@ +import { expect } from "@playwright/test" + +export async function submitCookieBanner(page: Page) { + const cookieSubmitButton = page.getByRole("button", { name: "accept" }) + await expect(cookieSubmitButton).toBeVisible() + await cookieSubmitButton.click() +} From 9ea1c72f5498cafe141a723559ce0e77318f8389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 7 Aug 2025 13:10:18 +0200 Subject: [PATCH 09/12] Try to fix code coverage --- fdm-app/.c8rc | 12 ------------ fdm-app/playwright-app.config.ts | 2 +- fdm-app/playwright-login.config.ts | 2 +- fdm-app/tests/login-tests/sign-in-and-farm.test.ts | 1 - fdm-app/v8-reporter.config.json | 8 ++++++++ 5 files changed, 10 insertions(+), 15 deletions(-) delete mode 100644 fdm-app/.c8rc create mode 100644 fdm-app/v8-reporter.config.json diff --git a/fdm-app/.c8rc b/fdm-app/.c8rc deleted file mode 100644 index 7219b19b6..000000000 --- a/fdm-app/.c8rc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "reporter": ["text", "json", "html"], - "exclude": [ - "**/node_modules/**", - "**/dist/**", - "**/turbo**", - "**/setup-test.ts", - "**.d.ts", - "*.config.ts", - "*.config.js" - ] -} diff --git a/fdm-app/playwright-app.config.ts b/fdm-app/playwright-app.config.ts index ad0887ca8..dd9adb768 100644 --- a/fdm-app/playwright-app.config.ts +++ b/fdm-app/playwright-app.config.ts @@ -45,7 +45,7 @@ export default defineConfig({ /* Start the server in a CI environment such as GitHub Actions */ webServer: { command: - "pnpm dotenvx run c8 react-router-serve ./build/server/index.js", + "pnpm dotenvx run -- c8 -c v8-reporter.config.json --reports-dir coverage/app react-router-serve ./build/server/index.js", url: "http://localhost:3000", timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/fdm-app/playwright-login.config.ts b/fdm-app/playwright-login.config.ts index 0aab094e9..b8352f744 100644 --- a/fdm-app/playwright-login.config.ts +++ b/fdm-app/playwright-login.config.ts @@ -35,7 +35,7 @@ export default defineConfig({ /* Start the server in a CI environment such as GitHub Actions */ webServer: { command: - "pnpm dotenvx run -- c8 react-router-serve ./build/server/index.js", + "pnpm dotenvx run -- c8 -c v8-reporter.config.json --reports-dir coverage/login react-router-serve ./build/server/index.js", env: { WRITE_MAGIC_LINK_FILE: "1", }, diff --git a/fdm-app/tests/login-tests/sign-in-and-farm.test.ts b/fdm-app/tests/login-tests/sign-in-and-farm.test.ts index cbf2a830c..80574a505 100644 --- a/fdm-app/tests/login-tests/sign-in-and-farm.test.ts +++ b/fdm-app/tests/login-tests/sign-in-and-farm.test.ts @@ -7,7 +7,6 @@ import { } from "../test-io" import { submitCookieBanner } from "../util" import { magicLinkUrlFileName } from "../test-io" -import fs from "node:fs/promises" test.describe.configure({ mode: "serial" }) diff --git a/fdm-app/v8-reporter.config.json b/fdm-app/v8-reporter.config.json new file mode 100644 index 000000000..172deb0c8 --- /dev/null +++ b/fdm-app/v8-reporter.config.json @@ -0,0 +1,8 @@ +{ + "reporter": ["text", "json", "html"], + "all": true, + "include": ["app/*", "build/*"], + "exclude": ["node_modules", "test/", "**/*.d.ts"], + "exclude-after-remap": true, + "extension": [".js", ".ts", ".tsx"] +} From a29453dc962735508824421496fed8cc002cb327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 7 Aug 2025 13:12:13 +0200 Subject: [PATCH 10/12] Update CI --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1d55240da..e5065ff60 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -377,6 +377,6 @@ jobs: - name: fdm-app - Upload results to Codecov uses: codecov/codecov-action@v5 with: - files: ./fdm-app/coverage/tmp/*.json + files: ./fdm-app/coverage/**/*.json flags: fdm-app token: ${{ secrets.CODECOV_TOKEN }} From 89da25c0c678db982a2537aad36b6fe3ad7e0089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 18 Aug 2025 15:47:52 +0200 Subject: [PATCH 11/12] Produce proper coverage reports --- .github/workflows/tests.yml | 26 +++- fdm-app/package.json | 3 +- fdm-app/playwright-app.config.ts | 6 +- fdm-app/playwright-login.config.ts | 6 +- .../farm.$b_id_farm.fertilizers.test.ts | 0 .../login-tests/sign-in-and-farm.test.ts | 104 ---------------- fdm-app/tests/login/sign-in-and-farm.test.ts | 113 ++++++++++++++++++ fdm-app/tests/test-db.ts | 61 ++++++++++ fdm-app/tests/util.ts | 23 ++++ fdm-app/v8-reporter.config.json | 1 + 10 files changed, 231 insertions(+), 112 deletions(-) rename fdm-app/tests/{app-tests => app}/farm.$b_id_farm.fertilizers.test.ts (100%) delete mode 100644 fdm-app/tests/login-tests/sign-in-and-farm.test.ts create mode 100644 fdm-app/tests/login/sign-in-and-farm.test.ts create mode 100644 fdm-app/tests/test-db.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f11fa234e..7469eff3c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -370,13 +370,37 @@ jobs: run: pnpm test-login-ci working-directory: ./fdm-app + - name: Move client coverage report for login tests + run: mv coverage/coverage-final.json coverage/coverage-final-login.json + working-directory: ./fdm-app + + - name: Compile server coverage report + run: pnpm coverage-report-ci + working-directory: ./fdm-app + + - name: Move server coverage report for login tests + run: mv coverage/coverage-final.json coverage/coverage-final-login-server.json + working-directory: ./fdm-app + - name: Run app tests with coverage run: pnpm test-ci working-directory: ./fdm-app + - name: Move client coverage report for app tests + run: mv coverage/coverage-final.json coverage/coverage-final-app.json + working-directory: ./fdm-app + + - name: Compile server coverage report + run: pnpm coverage-report-ci + working-directory: ./fdm-app + + - name: Move server coverage report for app tests + run: mv coverage/coverage-final.json coverage/coverage-final-app-server.json + working-directory: ./fdm-app + - name: fdm-app - Upload results to Codecov uses: codecov/codecov-action@v5 with: - files: ./fdm-app/coverage/**/*.json + files: ./fdm-app/coverage/coverage-final*.json flags: fdm-app token: ${{ secrets.CODECOV_TOKEN }} diff --git a/fdm-app/package.json b/fdm-app/package.json index 43f54147b..3649e9ecb 100644 --- a/fdm-app/package.json +++ b/fdm-app/package.json @@ -10,9 +10,10 @@ "dotenvx": "dotenvx", "start": "pnpm db:migrate && react-router-serve ./build/server/index.js", "start-dev": "dotenvx run -- pnpm db:migrate && dotenvx run -- react-router-serve ./build/server/index.js", - "test": "dotenvx run -- playwright test -c ./playwright-login.config.ts & dotenvx run playwright test -c ./playwright-app.config.ts", + "test": "dotenvx run -- playwright test -c ./playwright-login.config.ts & dotenvx run -- playwright test -c ./playwright-app.config.ts", "test-login-ci": "playwright test -c ./playwright-login.config.ts", "test-ci": "playwright test -c ./playwright-app.config.ts", + "coverage-report-ci": "c8 report -c v8-reporter.config.json", "db:migrate": "node ./app/lib/fdm-migrate.server.js", "typecheck": "react-router typegen && tsc" }, diff --git a/fdm-app/playwright-app.config.ts b/fdm-app/playwright-app.config.ts index dd9adb768..2cfea65b6 100644 --- a/fdm-app/playwright-app.config.ts +++ b/fdm-app/playwright-app.config.ts @@ -4,7 +4,7 @@ import { defineConfig, devices } from "@playwright/test" * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./tests/app-tests", + testDir: "./tests/app", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -14,7 +14,7 @@ export default defineConfig({ /* 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", + reporter: process.env.CI ? [["dot"], ["json"]] : [["list"], ["json"]], /* 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('/')`. */ @@ -45,7 +45,7 @@ export default defineConfig({ /* Start the server in a CI environment such as GitHub Actions */ webServer: { command: - "pnpm dotenvx run -- c8 -c v8-reporter.config.json --reports-dir coverage/app react-router-serve ./build/server/index.js", + "pnpm dotenvx run -- c8 -c v8-reporter.config.json react-router-serve ./build/server/index.js", url: "http://localhost:3000", timeout: 120 * 1000, reuseExistingServer: !process.env.CI, diff --git a/fdm-app/playwright-login.config.ts b/fdm-app/playwright-login.config.ts index b8352f744..1d9856373 100644 --- a/fdm-app/playwright-login.config.ts +++ b/fdm-app/playwright-login.config.ts @@ -4,7 +4,7 @@ import { defineConfig, devices } from "@playwright/test" * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: "./tests/login-tests", + testDir: "./tests/login", /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -14,7 +14,7 @@ export default defineConfig({ /* 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", + reporter: process.env.CI ? [["dot"], ["json"]] : [["list"], ["json"]], /* 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('/')`. */ @@ -35,7 +35,7 @@ export default defineConfig({ /* Start the server in a CI environment such as GitHub Actions */ webServer: { command: - "pnpm dotenvx run -- c8 -c v8-reporter.config.json --reports-dir coverage/login react-router-serve ./build/server/index.js", + "pnpm dotenvx run -- c8 -c v8-reporter.config.json react-router-serve ./build/server/index.js", env: { WRITE_MAGIC_LINK_FILE: "1", }, diff --git a/fdm-app/tests/app-tests/farm.$b_id_farm.fertilizers.test.ts b/fdm-app/tests/app/farm.$b_id_farm.fertilizers.test.ts similarity index 100% rename from fdm-app/tests/app-tests/farm.$b_id_farm.fertilizers.test.ts rename to fdm-app/tests/app/farm.$b_id_farm.fertilizers.test.ts diff --git a/fdm-app/tests/login-tests/sign-in-and-farm.test.ts b/fdm-app/tests/login-tests/sign-in-and-farm.test.ts deleted file mode 100644 index 80574a505..000000000 --- a/fdm-app/tests/login-tests/sign-in-and-farm.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import test, { expect } from "@playwright/test" -import { - loadSessionFromFile, - saveSessionToFile, - testFileLine, - writeTestFile, -} from "../test-io" -import { submitCookieBanner } from "../util" -import { magicLinkUrlFileName } from "../test-io" - -test.describe.configure({ mode: "serial" }) - -test("User can sign in via email and complete creating their profile", async ({ - page, - context, -}) => { - await page.goto("/") - await submitCookieBanner(page) - - const loginSubmitButton = page.getByRole("button", { name: "e-mail" }) - await expect( - loginSubmitButton, - "Login submit button does not exist.", - ).toBeVisible() - - const loginEmailBox = page.getByPlaceholder("e-mail") - await expect(loginEmailBox, "Email input box does not exist.").toBeVisible() - - await loginEmailBox.fill("xyz@gmail.com") - - await loginSubmitButton.click() - - const message = page.getByText("aanmelden...") - expect(message).toBeVisible() - - await writeTestFile(magicLinkUrlFileName, "") - await page.waitForURL("**/check-your-email") - const url = await testFileLine(magicLinkUrlFileName) - await page.goto(url) - - const firstNameBox = page.getByLabel("Voornaam") - await expect(firstNameBox, "There is no first name box.").toBeVisible() - await firstNameBox.fill("Test") - const lastNameBox = page.getByLabel("Achternaam") - await expect(lastNameBox, "There is no last name box.").toBeVisible() - await lastNameBox.fill("User") - - const nameSubmitButton = page.getByRole("button", { name: "Doorgaan" }) - await expect(nameSubmitButton, "There is no submit button.").toBeVisible() - await nameSubmitButton.click() - - await page.waitForURL("/farm") - - await saveSessionToFile(context) -}) - -test("User can create a farm business and select parcels", async ({ - page, - context, -}) => { - // Load session from previous test if it is missing for some reason - const currentCookies = await context.cookies() - if ( - !currentCookies.find((c) => c.name === "better-auth.session_token") || - !currentCookies.find((c) => c.name === "toast-session") - ) { - await loadSessionFromFile(context) - } - - // Click the Create Business button - await page.goto("/farm") - const createBusinessButton = page.getByRole("link", { - name: "Maak een bedrijf", - }) - await expect( - createBusinessButton, - "There is no create business button", - ).toBeVisible() - await createBusinessButton.click() - - // Fill in the business creation form and submit - const businessNameBox = page.getByLabel("bedrijfsnaam") - await expect(businessNameBox, "There is no business name box").toBeVisible() - await businessNameBox.fill("Example Business") - - const businessNameSubmitButton = page.getByRole("button", { - name: "volgende", - }) - await expect( - businessNameSubmitButton, - "There is no continue button after business name and address", - ).toBeVisible() - await businessNameSubmitButton.click() - - // Go to the Shape File Upload - const shapeFileUploadButton = page.getByRole("button", { - name: "Bestand uploaden", - }) - await expect( - shapeFileUploadButton, - "There is no shape file upload button", - ).toBeVisible() - await shapeFileUploadButton.click() -}) diff --git a/fdm-app/tests/login/sign-in-and-farm.test.ts b/fdm-app/tests/login/sign-in-and-farm.test.ts new file mode 100644 index 000000000..3b6431aa6 --- /dev/null +++ b/fdm-app/tests/login/sign-in-and-farm.test.ts @@ -0,0 +1,113 @@ +import { test } from "../test-db" +import { + loadSessionFromFile, + saveSessionToFile, + testFileLine, + writeTestFile, +} from "../test-io" +import { submitCookieBannerWithTimeout } from "../util" +import { magicLinkUrlFileName } from "../test-io" + +const { expect } = test + +test.describe.configure({ mode: "serial" }) + +test("User can sign in via email and complete creating their profile", async ({ + page, + context, + sql, +}) => { + sql()`delete from "fdm-authn"."user" where email = 'xyz@example.com'` + + await writeTestFile(magicLinkUrlFileName, "") + + await page.goto("/") + await submitCookieBannerWithTimeout(page) + + const loginSubmitButton = page.getByRole("button", { name: "e-mail" }) + await expect( + loginSubmitButton, + "Login submit button does not exist.", + ).toBeVisible() + + const loginEmailBox = page.getByPlaceholder("e-mail") + await expect(loginEmailBox, "Email input box does not exist.").toBeVisible() + + await loginEmailBox.fill("xyz@example.com") + + await loginSubmitButton.click() + + await page.waitForURL("**/check-your-email") + const url = await testFileLine(magicLinkUrlFileName) + console.log(url) + await page.goto(url) + + const firstNameBox = page.getByLabel("Voornaam") + await expect(firstNameBox, "There is no first name box.").toBeVisible() + await firstNameBox.fill("Test") + const lastNameBox = page.getByLabel("Achternaam") + await expect(lastNameBox, "There is no last name box.").toBeVisible() + await lastNameBox.fill("User") + + const nameSubmitButton = page.getByRole("button", { name: "Doorgaan" }) + await expect(nameSubmitButton, "There is no submit button.").toBeVisible() + await nameSubmitButton.click() + + await page.waitForURL("/farm") + + await saveSessionToFile(context) +}) + +test.fixme( + "User can create a farm business and select parcels", + async ({ page, context }) => { + // Load session from previous test if it is missing for some reason + const currentCookies = await context.cookies() + if ( + !currentCookies.find( + (c) => c.name === "better-auth.session_token", + ) || + !currentCookies.find((c) => c.name === "toast-session") + ) { + await loadSessionFromFile(context) + } + + // Click the Create Business button + await page.goto("/farm") + const createBusinessButton = page.getByRole("link", { + name: "Start wizard", + }) + await expect( + createBusinessButton, + "There is no create business button", + ).toBeVisible() + await createBusinessButton.click() + + // Fill in the business creation form and submit + const businessNameBox = page.getByLabel("bedrijfsnaam") + await expect( + businessNameBox, + "There is no business name box", + ).toBeVisible() + await businessNameBox.fill("Example Business") + + const businessNameSubmitButton = page.getByRole("button", { + name: "volgende", + }) + await expect( + businessNameSubmitButton, + "There is no continue button after business name and address", + ).toBeVisible() + await businessNameSubmitButton.click() + + // Go to the Shape File Upload + const shapeFileUploadButton = page.getByRole("button", { + name: "Bestand uploaden", + }) + await expect( + shapeFileUploadButton, + "There is no shape file upload button", + ).toBeVisible() + await shapeFileUploadButton.click() + }, +) diff --git a/fdm-app/tests/test-db.ts b/fdm-app/tests/test-db.ts new file mode 100644 index 000000000..ed8289365 --- /dev/null +++ b/fdm-app/tests/test-db.ts @@ -0,0 +1,61 @@ +import base from "@playwright/test" +import postgres from "postgres" + +function makeDb() { + // Get credentials to connect to db + const host = + process.env.POSTGRES_HOST ?? + (() => { + throw new Error("POSTGRES_HOST environment variable is required") + })() + const port = + Number(process.env.POSTGRES_PORT) || + (() => { + throw new Error("POSTGRES_PORT environment variable is required") + })() + const user = + process.env.POSTGRES_USER ?? + (() => { + throw new Error("POSTGRES_USER environment variable is required") + })() + const password = + process.env.POSTGRES_PASSWORD ?? + (() => { + throw new Error( + "POSTGRES_PASSWORD environment variable is required", + ) + })() + const database = + process.env.POSTGRES_DB ?? + (() => { + throw new Error("POSTGRES_DB environment variable is required") + })() + + return postgres({ + host: host, + port: port, + database: database, + user: user, + password: password, + max: 1, + }) +} + +let sqlInstance + +export const test = base.extend< + {}, + { + sql: () => postgres.Sql + } +>({ + sql: [ + async ({}, use) => { + await use(() => { + sqlInstance ??= makeDb() + return sqlInstance + }) + }, + { scope: "worker" }, + ], +}) diff --git a/fdm-app/tests/util.ts b/fdm-app/tests/util.ts index 3ed24b7f0..7f94cd03a 100644 --- a/fdm-app/tests/util.ts +++ b/fdm-app/tests/util.ts @@ -5,3 +5,26 @@ export async function submitCookieBanner(page: Page) { await expect(cookieSubmitButton).toBeVisible() await cookieSubmitButton.click() } + +export async function submitCookieBannerWithTimeout( + page: Page, + timeout = 1000, +) { + let cookieSubmitButton + await Promise.race([ + new Promise((resolve) => { + setTimeout(() => resolve(), timeout) + }), + (async () => { + const cand = page.getByRole("button", { + name: "accept", + }) + await expect(cand).toBeVisible() + cookieSubmitButton = cand + })(), + ]) + + if (cookieSubmitButton) { + await cookieSubmitButton.click() + } +} diff --git a/fdm-app/v8-reporter.config.json b/fdm-app/v8-reporter.config.json index 172deb0c8..645e862db 100644 --- a/fdm-app/v8-reporter.config.json +++ b/fdm-app/v8-reporter.config.json @@ -1,5 +1,6 @@ { "reporter": ["text", "json", "html"], + "reports-dir": "./coverage", "all": true, "include": ["app/*", "build/*"], "exclude": ["node_modules", "test/", "**/*.d.ts"], From f8983fed7161d9e5441bd83f32865807e878a7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 18 Aug 2025 16:08:42 +0200 Subject: [PATCH 12/12] Update node version for the test-app workflow job --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7469eff3c..dce42a558 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -330,7 +330,7 @@ jobs: - name: Setup pnpm 10 uses: pnpm/action-setup@v4 with: - version: 10.13.1 + version: 10.14.0 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4