From 1bc40a39b707215b5ae2d6f00a2b5eb78d545969 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Mon, 27 Oct 2025 16:58:29 +0100 Subject: [PATCH 1/9] Added test container setup --- .env | 10 +- .github/workflows/playwright.yml | 116 ++++-------------- package-lock.json | 89 +++++++++++++- package.json | 3 +- test-setup/setup.ts | 79 ++++++++++++ tests/auth/auth.setup.ts | 13 +- tests/example.spec.ts | 3 +- .../consultanten/consultanten.spec.ts | 17 ++- 8 files changed, 234 insertions(+), 96 deletions(-) create mode 100644 test-setup/setup.ts diff --git a/.env b/.env index e3001b2..07aa051 100644 --- a/.env +++ b/.env @@ -1 +1,9 @@ -BASE_URL=http://localhost:3000 \ No newline at end of file +BASE_URL=http://localhost:3000 +CONFAC_APP_PATH= ../confac +#TESTCONTAINERS_RYUK_DISABLED=true + +MONGO_HOST=localhost +MONGO_PORT=27017 +MONGO_USERNAME=admin +MONGO_PASSWORD=pwd +MONGO_DB=confac \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 54249b6..4fc0dca 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,102 +1,40 @@ -name: Playwright Tests +name: E2E Tests -on: - push: - branches: [ "**" ] - pull_request: - branches: [ "**" ] +on: [push, pull_request] jobs: test: - timeout-minutes: 60 runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Checkout confac repo + - name: Checkout E2E tests + uses: actions/checkout@v4 + + - name: Checkout Confac App uses: actions/checkout@v4 with: - repository: itenium-be/confac + repository: your-org/confac path: confac - - - uses: actions/setup-node@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: - node-version: lts/* - - - name: Start MongoDB - run: | - docker volume create mongodata - docker run -d \ - -p 27017:27017 \ - -e MONGO_INITDB_ROOT_USERNAME=admin \ - -e MONGO_INITDB_ROOT_PASSWORD=pwd \ - -v mongodata:/data/db \ - --name confac-mongo \ - mongo:3.6.3 - - - name: Install confac dependencies - run: | - cd confac/backend - npm ci - cd ../frontend - npm ci - - - name: Copy sample.env to .env - run: | - cd confac/backend - cp .env.sample .env - - - name: Start confac backend and frontend - run: | - cd confac/backend - nohup npm start > backend.log 2>&1 & - cd ../frontend - nohup npm start > frontend.log 2>&1 & - shell: bash - - - name: inject dummy data - run: | - cd confac/backend - node ./public/faker/index.js - shell: bash - continue-on-error: true - - - name: Install test dependencies + node-version: '18' + + - name: Install dependencies (E2E) run: npm ci - - - name: Install Playwright Browsers + + - name: Install dependencies (Backend) + run: npm ci + working-directory: confac/backend + + - name: Install dependencies (Frontend) + run: npm ci + working-directory: confac/frontend + + - name: Install Playwright browsers run: npx playwright install --with-deps - - - name: Run Playwright tests + + - name: Run E2E tests run: npx playwright test - - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: - playwright-report/ - retention-days: 30 - - - name: Upload backend log - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - name: backend-log - path: confac/backend/backend.log - retention-days: 30 - - - name: Upload frontend log - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - name: frontend-log - path: confac/frontend/frontend.log - retention-days: 30 - - - name: Cleanup containers - if: always() - run: | - docker stop confac-mongo || true - docker rm confac-mongo || true - docker volume rm mongodata || true + env: + CONFAC_APP_PATH: ${{ github.workspace }}/confac diff --git a/package-lock.json b/package-lock.json index 983f399..889119c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "dotenv": "^17.2.3" + "dotenv": "^17.2.3", + "redis": "^5.9.0" }, "devDependencies": { "@playwright/test": "^1.56.1", @@ -206,6 +207,67 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@redis/bloom": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.9.0.tgz", + "integrity": "sha512-W9D8yfKTWl4tP8lkC3MRYkMz4OfbuzE/W8iObe0jFgoRmgMfkBV+Vj38gvIqZPImtY0WB34YZkX3amYuQebvRQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.9.0" + } + }, + "node_modules/@redis/client": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.9.0.tgz", + "integrity": "sha512-EI0Ti5pojD2p7TmcS7RRa+AJVahdQvP/urpcSbK/K9Rlk6+dwMJTQ354pCNGCwfke8x4yKr5+iH85wcERSkwLQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.9.0.tgz", + "integrity": "sha512-Bm2jjLYaXdUWPb9RaEywxnjmzw7dWKDZI4MS79mTWPV16R982jVWBj6lY2ZGelJbwxHtEVg4/FSVgYDkuO/MxA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.9.0" + } + }, + "node_modules/@redis/search": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.9.0.tgz", + "integrity": "sha512-jdk2csmJ29DlpvCIb2ySjix2co14/0iwIT3C0I+7ZaToXgPbgBMB+zfEilSuncI2F9JcVxHki0YtLA0xX3VdpA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.9.0" + } + }, + "node_modules/@redis/time-series": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.9.0.tgz", + "integrity": "sha512-W6ILxcyOqhnI7ELKjJXOktIg3w4+aBHugDbVpgVLPZ+YDjObis1M0v7ZzwlpXhlpwsfePfipeSK+KWNuymk52w==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.9.0" + } + }, "node_modules/@types/docker-modem": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", @@ -745,6 +807,15 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1576,6 +1647,22 @@ "node": ">=10" } }, + "node_modules/redis": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.9.0.tgz", + "integrity": "sha512-E8dQVLSyH6UE/C9darFuwq4usOPrqfZ1864kI4RFbr5Oj9ioB9qPF0oJMwX7s8mf6sPYrz84x/Dx1PGF3/0EaQ==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.9.0", + "@redis/client": "5.9.0", + "@redis/json": "5.9.0", + "@redis/search": "5.9.0", + "@redis/time-series": "5.9.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 8983f45..73d26c1 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "testcontainers": "^11.7.2" }, "dependencies": { - "dotenv": "^17.2.3" + "dotenv": "^17.2.3", + "redis": "^5.9.0" } } diff --git a/test-setup/setup.ts b/test-setup/setup.ts new file mode 100644 index 0000000..c03359a --- /dev/null +++ b/test-setup/setup.ts @@ -0,0 +1,79 @@ +import { GenericContainer, StartedTestContainer, Wait } from "testcontainers"; +import { ChildProcess, spawn } from "child_process"; +import path from "path"; + +let mongoContainer: StartedTestContainer; +let backendProcess: ChildProcess; +let frontendProcess: ChildProcess; +let seedProcess: ChildProcess; + +const getAppPath = () => { + if (process.env.CONFAC_APP_PATH) { + return process.env.CONFAC_APP_PATH; + } + return path.resolve(__dirname, "../../../confac"); +}; + +export async function setupTestEnvironment() { + const appPath = getAppPath(); + console.log(`Using confac app path: ${appPath}`); + mongoContainer = await new GenericContainer("mongo:latest") + .withExposedPorts(27017) + .withEnvironment({ + MONGO_INITDB_DATABASE: "confac", + MONGO_INITDB_ROOT_USERNAME: process.env.MONGO_USERNAME?.toString() || "", + MONGO_INITDB_ROOT_PASSWORD: process.env.MONGO_PASSWORD?.toString() || "", + }) + .withStartupTimeout(120000) + .withWaitStrategy(Wait.forLogMessage("Waiting for connections")) + /*.withLogConsumer(stream => { + stream.on("data", (line: Buffer) => console.log(`MongoDB: ${line.toString().trim()}`)); + })*/ + .start(); + console.log( + `MongoDB started at mongodb://${mongoContainer.getHost()}:${mongoContainer.getMappedPort( + 27017 + )}` + ); + + const mongoPort = mongoContainer.getMappedPort(27017); + const mongoUrl = `mongodb://${mongoContainer.getHost()}:${mongoPort}/confac`; + + backendProcess = spawn("npm", ["start"], { + cwd: path.join(appPath, "backend"), + stdio: ["inherit", "pipe", "pipe"], + shell: true, + env: { + ...process.env, + MONGODB_URI: mongoUrl, + MONGO_PORT: mongoPort.toString(), + }, + }); + console.log("Backend process started"); + + seedProcess = spawn("cd backend/public && node ./faker/index.j", { + shell: true, + stdio: "inherit", + env: process.env, + }); + + frontendProcess = spawn("npm", ["start"], { + cwd: path.join(appPath, "frontend"), + //stdio: ['inherit', 'pipe', 'pipe'] + shell: true, + }); + console.log("Frontend process started"); + await new Promise((resolve) => setTimeout(resolve, 10000)); +} + +export async function teardownTestEnvironment() { + if (mongoContainer) { + await mongoContainer.stop(); + } + if (backendProcess) { + backendProcess.kill(); + } + if (frontendProcess) { + frontendProcess.kill(); + } +} diff --git a/tests/auth/auth.setup.ts b/tests/auth/auth.setup.ts index 5b59b77..134b793 100644 --- a/tests/auth/auth.setup.ts +++ b/tests/auth/auth.setup.ts @@ -1,10 +1,19 @@ -import { test as setup, expect } from '@playwright/test'; +import test, { test as setup, expect } from '@playwright/test'; import path from 'path'; -import { LoginPage } from '../../src/pages/LoginPage'; + +import { setupTestEnvironment, teardownTestEnvironment } from '../../test-setup/setup'; +import { LoginPage } from '../../src/pages/loginPage'; const authFile = path.join(__dirname, '../../playwright/.auth/user.json'); let login: LoginPage; +test.beforeAll(async () => { + await setupTestEnvironment(); +}); + +test.afterAll(async () => { + await teardownTestEnvironment(); +}); setup('authenticate', async ({ page }) => { login = new LoginPage(page); diff --git a/tests/example.spec.ts b/tests/example.spec.ts index b9bd751..9b9c3bd 100644 --- a/tests/example.spec.ts +++ b/tests/example.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "@playwright/test"; -import { LoginPage } from "../src/pages/LoginPage"; +import { LoginPage } from "../src/pages/loginPage"; + let loginPage: LoginPage; diff --git a/tests/projecten/consultanten/consultanten.spec.ts b/tests/projecten/consultanten/consultanten.spec.ts index 7c64204..c00b7b3 100644 --- a/tests/projecten/consultanten/consultanten.spec.ts +++ b/tests/projecten/consultanten/consultanten.spec.ts @@ -1,13 +1,28 @@ import { test, expect } from "@playwright/test"; import { ConsultantsPage } from "../../../src/pages/projecten/ConsultantsPage"; import { CreateConsultantsPage } from "../../../src/pages/projecten/CreateConsultantsPage"; +import { GenericContainer } from "testcontainers"; +import { setupTestEnvironment, teardownTestEnvironment } from "../../../test-setup/setup"; + +let container; + let consultantsPage: ConsultantsPage; let createConsultantsPage: CreateConsultantsPage; +test.beforeAll(async () => { + await setupTestEnvironment(); +}); + +test.afterAll(async () => { + await teardownTestEnvironment(); +}); + test.beforeEach(async ({ page }) => { consultantsPage = new ConsultantsPage(page); - createConsultantsPage = new CreateConsultantsPage(page);}); + createConsultantsPage = new CreateConsultantsPage(page); + +}); test("consultant toevoegen", async ({ page }) => { await consultantsPage.goto(); From c2819415fe9c07697ee4938d3dbf98ad762f15d1 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Mon, 27 Oct 2025 17:03:40 +0100 Subject: [PATCH 2/9] Changes to workflow --- .github/workflows/playwright.yml | 74 ++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 4fc0dca..27deddd 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,40 +1,60 @@ -name: E2E Tests +name: Playwright Tests -on: [push, pull_request] +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] jobs: test: + timeout-minutes: 60 runs-on: ubuntu-latest - steps: - - name: Checkout E2E tests - uses: actions/checkout@v4 - - name: Checkout Confac App + steps: + - uses: actions/checkout@v4 + - name: Checkout confac repo uses: actions/checkout@v4 with: - repository: your-org/confac + repository: itenium-be/confac path: confac - - - name: Setup Node.js - uses: actions/setup-node@v4 + + - uses: actions/setup-node@v4 with: - node-version: '18' - - - name: Install dependencies (E2E) - run: npm ci - - - name: Install dependencies (Backend) - run: npm ci - working-directory: confac/backend - - - name: Install dependencies (Frontend) + node-version: lts/* + + - name: Install confac dependencies + run: | + cd confac/backend + npm ci + cd ../frontend + npm ci + + - name: Copy sample.env to .env + run: | + cd confac/backend + cp .env.sample .env + + - name: Install test dependencies run: npm ci - working-directory: confac/frontend - - - name: Install Playwright browsers + + - name: Install Playwright Browsers run: npx playwright install --with-deps - - - name: Run E2E tests + + - name: Run Playwright tests run: npx playwright test - env: - CONFAC_APP_PATH: ${{ github.workspace }}/confac + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: + playwright-report/ + retention-days: 30 + + - name: Cleanup containers + if: always() + run: | + docker stop confac-mongo || true + docker rm confac-mongo || true + docker volume rm mongodata || true From a9036477adbfe6f5d9627138a772a4def75732c6 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Mon, 27 Oct 2025 17:12:04 +0100 Subject: [PATCH 3/9] Fixed capitalization Removed unused import --- tests/auth/auth.setup.ts | 2 +- tests/example.spec.ts | 2 +- tests/projecten/consultanten/consultanten.spec.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/auth/auth.setup.ts b/tests/auth/auth.setup.ts index 134b793..9abf426 100644 --- a/tests/auth/auth.setup.ts +++ b/tests/auth/auth.setup.ts @@ -2,7 +2,7 @@ import test, { test as setup, expect } from '@playwright/test'; import path from 'path'; import { setupTestEnvironment, teardownTestEnvironment } from '../../test-setup/setup'; -import { LoginPage } from '../../src/pages/loginPage'; +import { LoginPage } from '../../src/pages/LoginPage'; const authFile = path.join(__dirname, '../../playwright/.auth/user.json'); let login: LoginPage; diff --git a/tests/example.spec.ts b/tests/example.spec.ts index 9b9c3bd..75df5f3 100644 --- a/tests/example.spec.ts +++ b/tests/example.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from "@playwright/test"; -import { LoginPage } from "../src/pages/loginPage"; +import { LoginPage } from "../src/pages/LoginPage"; let loginPage: LoginPage; diff --git a/tests/projecten/consultanten/consultanten.spec.ts b/tests/projecten/consultanten/consultanten.spec.ts index c00b7b3..1825370 100644 --- a/tests/projecten/consultanten/consultanten.spec.ts +++ b/tests/projecten/consultanten/consultanten.spec.ts @@ -1,7 +1,6 @@ import { test, expect } from "@playwright/test"; import { ConsultantsPage } from "../../../src/pages/projecten/ConsultantsPage"; import { CreateConsultantsPage } from "../../../src/pages/projecten/CreateConsultantsPage"; -import { GenericContainer } from "testcontainers"; import { setupTestEnvironment, teardownTestEnvironment } from "../../../test-setup/setup"; let container; From 7e479ca8447e80de583b17b677352317fcc238da Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Tue, 28 Oct 2025 18:51:33 +0100 Subject: [PATCH 4/9] eod commit --- .github/workflows/playwright copy.yml | 102 ++++++++ .../workflows/playwright-testcontainer.yml | 40 +++ .github/workflows/playwright.yml | 7 - .../CreateComponent.ts | 0 src/enum/klantTypes.ts | 5 + src/pages/Klanten/CreateKlantPage.ts | 243 ++++++++++++++++++ src/pages/Klanten/KlantenPage.ts | 73 ++++++ src/pages/projecten/ConsultantsPage.ts | 4 +- src/pages/projecten/CreateConsultantsPage.ts | 4 +- test-setup/setup.ts | 12 +- tests/klanten/klanten.spec.ts | 48 ++++ 11 files changed, 524 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/playwright copy.yml create mode 100644 .github/workflows/playwright-testcontainer.yml rename src/{ccomponents => components}/CreateComponent.ts (100%) create mode 100644 src/enum/klantTypes.ts create mode 100644 src/pages/Klanten/CreateKlantPage.ts create mode 100644 src/pages/Klanten/KlantenPage.ts create mode 100644 tests/klanten/klanten.spec.ts diff --git a/.github/workflows/playwright copy.yml b/.github/workflows/playwright copy.yml new file mode 100644 index 0000000..54249b6 --- /dev/null +++ b/.github/workflows/playwright copy.yml @@ -0,0 +1,102 @@ +name: Playwright Tests + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "**" ] + +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Checkout confac repo + uses: actions/checkout@v4 + with: + repository: itenium-be/confac + path: confac + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Start MongoDB + run: | + docker volume create mongodata + docker run -d \ + -p 27017:27017 \ + -e MONGO_INITDB_ROOT_USERNAME=admin \ + -e MONGO_INITDB_ROOT_PASSWORD=pwd \ + -v mongodata:/data/db \ + --name confac-mongo \ + mongo:3.6.3 + + - name: Install confac dependencies + run: | + cd confac/backend + npm ci + cd ../frontend + npm ci + + - name: Copy sample.env to .env + run: | + cd confac/backend + cp .env.sample .env + + - name: Start confac backend and frontend + run: | + cd confac/backend + nohup npm start > backend.log 2>&1 & + cd ../frontend + nohup npm start > frontend.log 2>&1 & + shell: bash + + - name: inject dummy data + run: | + cd confac/backend + node ./public/faker/index.js + shell: bash + continue-on-error: true + + - name: Install test dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + - name: Run Playwright tests + run: npx playwright test + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: + playwright-report/ + retention-days: 30 + + - name: Upload backend log + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: backend-log + path: confac/backend/backend.log + retention-days: 30 + + - name: Upload frontend log + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v4 + with: + name: frontend-log + path: confac/frontend/frontend.log + retention-days: 30 + + - name: Cleanup containers + if: always() + run: | + docker stop confac-mongo || true + docker rm confac-mongo || true + docker volume rm mongodata || true diff --git a/.github/workflows/playwright-testcontainer.yml b/.github/workflows/playwright-testcontainer.yml new file mode 100644 index 0000000..33e5694 --- /dev/null +++ b/.github/workflows/playwright-testcontainer.yml @@ -0,0 +1,40 @@ +name: E2E Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout E2E tests + uses: actions/checkout@v4 + + - name: Checkout Confac App + uses: actions/checkout@v4 + with: + repository: your-org/confac + path: confac + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '16.10.0' + + - name: Install dependencies (E2E) + run: npm ci + + - name: Install dependencies (Backend) + run: npm ci + working-directory: confac/backend + + - name: Install dependencies (Frontend) + run: npm ci + working-directory: confac/frontend + + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Run E2E tests + run: npx playwright test + env: + CONFAC_APP_PATH: ${{ github.workspace }}/confac \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 27deddd..120e315 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -51,10 +51,3 @@ jobs: path: playwright-report/ retention-days: 30 - - - name: Cleanup containers - if: always() - run: | - docker stop confac-mongo || true - docker rm confac-mongo || true - docker volume rm mongodata || true diff --git a/src/ccomponents/CreateComponent.ts b/src/components/CreateComponent.ts similarity index 100% rename from src/ccomponents/CreateComponent.ts rename to src/components/CreateComponent.ts diff --git a/src/enum/klantTypes.ts b/src/enum/klantTypes.ts new file mode 100644 index 0000000..82cbd4d --- /dev/null +++ b/src/enum/klantTypes.ts @@ -0,0 +1,5 @@ +export enum klantTypes { + Onderaannemer = 'Onderaannemer', + Klant = 'Klant', + Eindklant = 'Eindklant', +} \ No newline at end of file diff --git a/src/pages/Klanten/CreateKlantPage.ts b/src/pages/Klanten/CreateKlantPage.ts new file mode 100644 index 0000000..92a1e87 --- /dev/null +++ b/src/pages/Klanten/CreateKlantPage.ts @@ -0,0 +1,243 @@ +import { Locator, Page } from "playwright"; +import { BasePage } from "../BasePage"; +import { CreateComponent } from "../../components/CreateComponent"; +import { klantTypes } from "../../enum/klantTypes"; + +export class CreateKlantPage extends BasePage { + createComponent: CreateComponent; + + btwNummerInput: Locator; + klantVerderAanvullenButton: Locator; + + raamcontractDropdown: Locator; + klantInput: Locator; + btwInput: Locator; + typeDropdown: Locator; + typeDropdownOptions: Locator; + straatEnNummerInput: Locator; + PostcodeInput: Locator; + stadInput: Locator; + landDropdown: Locator; + landDropdownOptions: Locator; + contactGegevensInput: Locator; + contactEmailInput: Locator; + telefoonNrInput: Locator; + taalDropdown: Locator; + bewarenButton: Locator; + + constructor(page: Page, url: string = "/clients/create") { + super(page, url); + + this.createComponent = new CreateComponent(page); + this.btwNummerInput = page.getByTestId("btw"); + this.klantVerderAanvullenButton = page.getByTestId("btw-continue"); + + this.raamcontractDropdown = page + .locator("div") + .filter({ hasText: /^Type\(s\)$/ }) + .nth(2); + this.klantInput = page.getByTestId("name"); + this.btwInput = page.getByTestId("btw"); + this.typeDropdown = page.locator('.react-select__value-container.react-select__value-container--is-multi > .react-select__input-container').first() + this.typeDropdownOptions = page.locator("#react-select-4-listbox") + this.straatEnNummerInput = page.getByTestId("address"); + this.PostcodeInput = page.getByTestId("postalCode"); + this.stadInput = page.getByTestId("city"); + this.landDropdown = page + .locator( + ".col-lg-3 > .form-group > .css-b62m3t-container > .react-select__control > .react-select__value-container > .react-select__input-container" + ) + .first(); + this.landDropdownOptions = page.locator("#react-select-5-listbox") + + this.contactGegevensInput = page.getByTestId("contact"); + this.contactEmailInput = page.getByTestId("contactEmail"); + this.telefoonNrInput = page + .locator("div") + .filter({ hasText: "Telefoon nr" }) + .nth(4); + this.taalDropdown = page.locator( + ".col-lg-3 > .form-group > .react-select-base > .react-select__control > .react-select__value-container > .react-select__input-container" + ); + this.bewarenButton = page.getByRole("button", { name: "Bewaren" }); + } + + /** + * + * @param btwNummer + * @returns + */ + async btwNummerInvullen(btwNummer: string) { + await this.btwNummerInput.fill(btwNummer); + } + /** + * + * @returns + */ + async ClickKlantVerderAanvullen() { + await this.klantVerderAanvullenButton.click(); + } + /** */ + async klantNaamInvullen(klantNaam: string) { + await this.klantInput.fill(klantNaam); + } + + /** + * + * @param btw + * @returns + */ + async btwInvullen(btw: string) { + await this.btwInput.fill(btw); + + return this; + } + + /** + * + * @param type + * @returns + */ + async typeDropdownSelecteren(type: klantTypes) { + await this.typeDropdown.click(); + await this.typeDropdownOptions.locator('.react-select__option', { hasText: type }).click(); + } + + + /** + * + * @param straatEnNummer + * @returns + */ + async straatEnNummerInvullen(straatEnNummer: string) { + await this.straatEnNummerInput.fill(straatEnNummer); + + } + /** + * + * @param postcode + * @returns + */ + async postcodeInvullen(postcode: string) { + await this.PostcodeInput.fill(postcode); + + return this; + } + /** + * + * @param stad + * @returns + */ + async stadInvullen(stad: string) { + await this.stadInput.fill(stad); + + return this; + } + + /** + * + * @param land + * @returns + */ + async landDropdownSelecteren(land: string) { + await this.landDropdown.click(); + await this.landDropdownOptions.locator('.react-select__option', { hasText: land }).click(); + + return this; + } + + /** + * + * @param contactGegevens + * @returns + */ + async contactGegevensInvullen(contactGegevens: string) { + await this.contactGegevensInput.fill(contactGegevens); + + return this; + } + + /** + * + * @param contactEmail + * @returns + */ + async contactEmailInvullen(contactEmail: string) { + await this.contactEmailInput.fill(contactEmail); + + return this; + } + /** + * + * @param telefoonNr + * @returns + */ + async telefoonNrInvullen(telefoonNr: string) { + await this.telefoonNrInput.fill(telefoonNr); + + return this; + } + /** + * + * @param taal + * @returns + */ + async taalDropdownSelecteren(taal: string) { + await this.taalDropdown.click(); + await this.page.getByText(taal).click(); + + return this; + } + + async clickBewaren() { + await this.bewarenButton.click(); + + return this; + } +} + +/* +await page.getByTestId("add").click(); +await page.getByTestId("btw").click(); +await page.getByTestId("btw").fill("1"); +await page.getByTestId("btw-continue").click(); +await page.getByTestId("name").click(); +await page.getByTestId("name").fill("klant"); +await page + .locator("div") + .filter({ hasText: /^Type\(s\)$/ }) + .nth(2) + .click(); + +await page.getByRole("combobox", { name: "types" }).click(); +await page.getByTestId("address").click(); +await page.getByTestId("address").fill("straat"); +await page.getByTestId("postalCode").click(); +await page.getByTestId("postalCode").fill("postcode"); +await page.getByTestId("city").click(); +await page.getByTestId("city").fill("stad"); +await page + .locator( + ".col-lg-3 > .form-group > .css-b62m3t-container > .react-select__control > .react-select__value-container > .react-select__input-container" + ) + .first() + .click(); +await page.getByText("België").click(); + +await page.getByTestId("contact").click(); +await page.getByTestId("contact").fill("contact"); +await page.getByTestId("contactEmail").click(); +await page.getByTestId("contactEmail").fill("email contact"); +await page.locator("div").filter({ hasText: "Telefoon nr" }).nth(4).click(); +await page.getByTestId("telephone").fill("1"); +await page + .locator( + ".col-lg-3 > .form-group > .react-select-base > .react-select__control > .react-select__value-container > .react-select__input-container" + ) + .click(); +await page.getByText("nl").click(); + +await page + .locator("#rdw-wrapper-9356") + .getByRole("textbox", { name: "rdw-editor" }) + .click();*/ diff --git a/src/pages/Klanten/KlantenPage.ts b/src/pages/Klanten/KlantenPage.ts new file mode 100644 index 0000000..29b47ae --- /dev/null +++ b/src/pages/Klanten/KlantenPage.ts @@ -0,0 +1,73 @@ +import { Locator, Page } from "playwright"; +import { BasePage } from "../BasePage"; +import { CreateComponent } from "../../components/CreateComponent"; + +export class KlantenPage extends BasePage { + createComponent: CreateComponent; + + nieuweKlantButton: Locator + zoekenInput: Locator; + filterdropdown: Locator; + toonInactieveToggle: Locator; + constructor(page: Page, url: string = "/clients") { + super(page, url); + + this.createComponent = new CreateComponent(page); + this.nieuweKlantButton = page.getByTestId("add"); + this.zoekenInput = page.getByRole("textbox", { name: "Zoeken" }); + this.filterdropdown = page.getByTestId(""); + this.toonInactieveToggle = page.locator('div').filter({ hasText: 'Toon inactieve' }).nth(5); + } + + /** + * + */ + async ClickOnNieuweKlant() { + await this.nieuweKlantButton.click(); +} +} + +/* +getByTestId('add') +getByRole('textbox', { name: 'Zoeken' }) +locator('div').filter({ hasText: /^2025$/ }).nth(3) +locator('.react-select__indicator')await page.locator('svg').click(); +await page.getByText('Onderaannemer').click(); +await page.locator('#react-select-2-option-1').click(); +locator('div').filter({ hasText: 'Toon inactieve' }).nth(5) +getByRole('cell', { name: 'BE2' }) +getByRole('button', { name: '' }).first() +getByRole('button', { name: '' }).first() + +//nieuwe klant creation + +await page.getByTestId('add').click(); +await page.getByTestId('btw').click(); +await page.getByTestId('btw').fill('1'); +await page.getByTestId('btw-continue').click(); +await page.getByTestId('name').click(); +await page.getByTestId('name').fill('klant'); +await page.locator('div').filter({ hasText: /^Type\(s\)$/ }).nth(2).click(); + +await page.getByRole('combobox', { name: 'types' }).click(); +await page.getByTestId('address').click(); +await page.getByTestId('address').fill('straat'); +await page.getByTestId('postalCode').click(); +await page.getByTestId('postalCode').fill('postcode'); +await page.getByTestId('city').click(); +await page.getByTestId('city').fill('stad'); +await page.locator('.col-lg-3 > .form-group > .css-b62m3t-container > .react-select__control > .react-select__value-container > .react-select__input-container').first().click(); +await page.getByText('België').click(); + + +await page.getByTestId('contact').click(); +await page.getByTestId('contact').fill('contact'); +await page.getByTestId('contactEmail').click(); +await page.getByTestId('contactEmail').fill('email contact'); +await page.locator('div').filter({ hasText: 'Telefoon nr' }).nth(4).click(); +await page.getByTestId('telephone').fill('1'); +await page.locator('.col-lg-3 > .form-group > .react-select-base > .react-select__control > .react-select__value-container > .react-select__input-container').click(); +await page.getByText('nl').click(); + +await page.locator('#rdw-wrapper-9356').getByRole('textbox', { name: 'rdw-editor' }).click(); +*/ \ No newline at end of file diff --git a/src/pages/projecten/ConsultantsPage.ts b/src/pages/projecten/ConsultantsPage.ts index 38127d4..c24f382 100644 --- a/src/pages/projecten/ConsultantsPage.ts +++ b/src/pages/projecten/ConsultantsPage.ts @@ -1,6 +1,6 @@ import { Locator, Page } from "playwright"; import { BasePage } from "../BasePage"; -import { CreateComponent } from "../../ccomponents/CreateComponent"; +import { CreateComponent } from "../../components/CreateComponent"; export class ConsultantsPage extends BasePage { createComponent: CreateComponent; @@ -12,7 +12,7 @@ export class ConsultantsPage extends BasePage { deleteButton: Locator; constructor(page: Page, url: string = "/consultants") { - super(page, "/consultants"); + super(page, url); this.createComponent = new CreateComponent(page); diff --git a/src/pages/projecten/CreateConsultantsPage.ts b/src/pages/projecten/CreateConsultantsPage.ts index bbc65c5..fdc4d62 100644 --- a/src/pages/projecten/CreateConsultantsPage.ts +++ b/src/pages/projecten/CreateConsultantsPage.ts @@ -1,6 +1,6 @@ import { Locator, Page } from "playwright"; import { BasePage } from "../BasePage"; -import { CreateComponent } from "../../ccomponents/CreateComponent"; +import { CreateComponent } from "../../components/CreateComponent"; export class CreateConsultantsPage extends BasePage { createComponent: CreateComponent; @@ -15,7 +15,7 @@ export class CreateConsultantsPage extends BasePage { readonly saveButton: Locator; constructor(page: Page, url: string = "/consultants") { - super(page, "/consultants"); + super(page, url); this.createComponent = new CreateComponent(page); diff --git a/test-setup/setup.ts b/test-setup/setup.ts index c03359a..ba36903 100644 --- a/test-setup/setup.ts +++ b/test-setup/setup.ts @@ -39,9 +39,11 @@ export async function setupTestEnvironment() { const mongoPort = mongoContainer.getMappedPort(27017); const mongoUrl = `mongodb://${mongoContainer.getHost()}:${mongoPort}/confac`; + console.log("Starting Backend process"); + backendProcess = spawn("npm", ["start"], { cwd: path.join(appPath, "backend"), - stdio: ["inherit", "pipe", "pipe"], + stdio: ["inherit", "pipe", "pipe"], shell: true, env: { ...process.env, @@ -50,12 +52,16 @@ export async function setupTestEnvironment() { }, }); console.log("Backend process started"); + console.log("Starting seed process"); seedProcess = spawn("cd backend/public && node ./faker/index.j", { - shell: true, - stdio: "inherit", + shell: true, + stdio: "inherit", env: process.env, }); + console.log("Seed process finished"); + + console.log("Frontend process started"); frontendProcess = spawn("npm", ["start"], { cwd: path.join(appPath, "frontend"), diff --git a/tests/klanten/klanten.spec.ts b/tests/klanten/klanten.spec.ts new file mode 100644 index 0000000..c123e12 --- /dev/null +++ b/tests/klanten/klanten.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from "@playwright/test"; +import { setupTestEnvironment, teardownTestEnvironment } from "../../test-setup/setup"; + + +import { KlantenPage } from "../../src/pages/Klanten/KlantenPage"; +import { CreateKlantPage } from "../../src/pages/Klanten/CreateKlantPage"; +import { klantTypes } from "../../src/enum/klantTypes"; + + + + +let klantenPage: KlantenPage; +let createKlantPage: CreateKlantPage + +test.beforeAll(async () => { + await setupTestEnvironment(); +}); + +test.afterAll(async () => { + await teardownTestEnvironment(); +}); + +test.beforeEach(async ({ page }) => { + klantenPage = new KlantenPage(page); + createKlantPage = new CreateKlantPage(page); + +}); + +test("klant toevoegen", async ({ page }) => { + await klantenPage.goto(); + + await klantenPage.ClickOnNieuweKlant(); + + await createKlantPage.btwNummerInvullen("123456789"); + await createKlantPage.ClickKlantVerderAanvullen(); + await createKlantPage.klantNaamInvullen("klant naam"); + await createKlantPage.btwInvullen("btw nummer"); + await createKlantPage.typeDropdownSelecteren(klantTypes.Eindklant); + await createKlantPage.straatEnNummerInvullen("straat 1"); + await createKlantPage.postcodeInvullen("1000"); + await createKlantPage.stadInvullen("stad"); + await createKlantPage.landDropdownSelecteren("België"); + await createKlantPage.clickBewaren(); + + +}); + + From 2e6e01dc0cdeb8aeef21cd756d46b74e2625060a Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Wed, 29 Oct 2025 16:56:04 +0100 Subject: [PATCH 5/9] Added klanten tests -search -delete -update --- src/pages/Klanten/CreateKlantPage.ts | 151 +++++++++------------------ src/pages/Klanten/KlantenPage.ts | 122 ++++++++++++---------- tests/klanten/klanten.spec.ts | 82 +++++++++++---- 3 files changed, 179 insertions(+), 176 deletions(-) diff --git a/src/pages/Klanten/CreateKlantPage.ts b/src/pages/Klanten/CreateKlantPage.ts index 92a1e87..e00fa96 100644 --- a/src/pages/Klanten/CreateKlantPage.ts +++ b/src/pages/Klanten/CreateKlantPage.ts @@ -38,8 +38,12 @@ export class CreateKlantPage extends BasePage { .nth(2); this.klantInput = page.getByTestId("name"); this.btwInput = page.getByTestId("btw"); - this.typeDropdown = page.locator('.react-select__value-container.react-select__value-container--is-multi > .react-select__input-container').first() - this.typeDropdownOptions = page.locator("#react-select-4-listbox") + this.typeDropdown = page + .locator( + ".react-select__value-container.react-select__value-container--is-multi > .react-select__input-container" + ) + .first(); + this.typeDropdownOptions = page.locator("#react-select-4-listbox"); this.straatEnNummerInput = page.getByTestId("address"); this.PostcodeInput = page.getByTestId("postalCode"); this.stadInput = page.getByTestId("city"); @@ -48,7 +52,7 @@ export class CreateKlantPage extends BasePage { ".col-lg-3 > .form-group > .css-b62m3t-container > .react-select__control > .react-select__value-container > .react-select__input-container" ) .first(); - this.landDropdownOptions = page.locator("#react-select-5-listbox") + this.landDropdownOptions = page.locator("#react-select-5-listbox"); this.contactGegevensInput = page.getByTestId("contact"); this.contactEmailInput = page.getByTestId("contactEmail"); @@ -63,28 +67,30 @@ export class CreateKlantPage extends BasePage { } /** - * - * @param btwNummer - * @returns + * enter the given btw number (in the initial create page) + * @param btwNummer string to repsrent the btw nummer */ async btwNummerInvullen(btwNummer: string) { await this.btwNummerInput.fill(btwNummer); } /** - * - * @returns + * Click the verder aanvullen button */ async ClickKlantVerderAanvullen() { await this.klantVerderAanvullenButton.click(); } - /** */ + + /** + * enter the given klant name + * @param klantNaam name to enter + */ async klantNaamInvullen(klantNaam: string) { await this.klantInput.fill(klantNaam); } /** - * - * @param btw + * enter the btw number (on the full creation page) + * @param btw string to represent the btw number * @returns */ async btwInvullen(btw: string) { @@ -94,150 +100,91 @@ export class CreateKlantPage extends BasePage { } /** - * - * @param type - * @returns + * select the given type from the type dropdown + * opens the dropdown and selects from the given options + * @param type see @KlantTypes enum */ async typeDropdownSelecteren(type: klantTypes) { await this.typeDropdown.click(); - await this.typeDropdownOptions.locator('.react-select__option', { hasText: type }).click(); + await this.typeDropdownOptions + .locator(".react-select__option", { hasText: type }) + .click(); } - /** - * - * @param straatEnNummer - * @returns + * enter the straat en nummer input + * @param straatEnNummer string to enter */ async straatEnNummerInvullen(straatEnNummer: string) { await this.straatEnNummerInput.fill(straatEnNummer); - } /** - * - * @param postcode - * @returns + * enter the postcode + * @param postcode string to enter */ async postcodeInvullen(postcode: string) { await this.PostcodeInput.fill(postcode); - - return this; } /** * * @param stad - * @returns + + */ + + /** + * enter the stad + * @param stad string to enter */ async stadInvullen(stad: string) { await this.stadInput.fill(stad); - - return this; } /** - * - * @param land - * @returns + * select the given land from the land dropdown + * @param land land to select */ async landDropdownSelecteren(land: string) { await this.landDropdown.click(); - await this.landDropdownOptions.locator('.react-select__option', { hasText: land }).click(); - - return this; + await this.landDropdownOptions + .locator(".react-select__option", { hasText: land }) + .click(); } /** - * - * @param contactGegevens - * @returns + * contact gegevens invullen + * @param contactGegevens string to enter */ async contactGegevensInvullen(contactGegevens: string) { await this.contactGegevensInput.fill(contactGegevens); - - return this; } /** - * - * @param contactEmail - * @returns + * contact email invullen + * @param contactEmail string to enter */ async contactEmailInvullen(contactEmail: string) { await this.contactEmailInput.fill(contactEmail); - - return this; } /** - * - * @param telefoonNr - * @returns + * telefoon nr invullen + * @param telefoonNr string to enter */ async telefoonNrInvullen(telefoonNr: string) { await this.telefoonNrInput.fill(telefoonNr); - - return this; } /** - * - * @param taal - * @returns + * select the given taal from the taal dropdown + * @param taal string to select */ async taalDropdownSelecteren(taal: string) { await this.taalDropdown.click(); await this.page.getByText(taal).click(); - - return this; } + /** + * click the bewaren button. Navigates back to klanten page + */ async clickBewaren() { await this.bewarenButton.click(); - - return this; } } - -/* -await page.getByTestId("add").click(); -await page.getByTestId("btw").click(); -await page.getByTestId("btw").fill("1"); -await page.getByTestId("btw-continue").click(); -await page.getByTestId("name").click(); -await page.getByTestId("name").fill("klant"); -await page - .locator("div") - .filter({ hasText: /^Type\(s\)$/ }) - .nth(2) - .click(); - -await page.getByRole("combobox", { name: "types" }).click(); -await page.getByTestId("address").click(); -await page.getByTestId("address").fill("straat"); -await page.getByTestId("postalCode").click(); -await page.getByTestId("postalCode").fill("postcode"); -await page.getByTestId("city").click(); -await page.getByTestId("city").fill("stad"); -await page - .locator( - ".col-lg-3 > .form-group > .css-b62m3t-container > .react-select__control > .react-select__value-container > .react-select__input-container" - ) - .first() - .click(); -await page.getByText("België").click(); - -await page.getByTestId("contact").click(); -await page.getByTestId("contact").fill("contact"); -await page.getByTestId("contactEmail").click(); -await page.getByTestId("contactEmail").fill("email contact"); -await page.locator("div").filter({ hasText: "Telefoon nr" }).nth(4).click(); -await page.getByTestId("telephone").fill("1"); -await page - .locator( - ".col-lg-3 > .form-group > .react-select-base > .react-select__control > .react-select__value-container > .react-select__input-container" - ) - .click(); -await page.getByText("nl").click(); - -await page - .locator("#rdw-wrapper-9356") - .getByRole("textbox", { name: "rdw-editor" }) - .click();*/ diff --git a/src/pages/Klanten/KlantenPage.ts b/src/pages/Klanten/KlantenPage.ts index 29b47ae..9c8d0bd 100644 --- a/src/pages/Klanten/KlantenPage.ts +++ b/src/pages/Klanten/KlantenPage.ts @@ -5,69 +5,85 @@ import { CreateComponent } from "../../components/CreateComponent"; export class KlantenPage extends BasePage { createComponent: CreateComponent; - nieuweKlantButton: Locator - zoekenInput: Locator; - filterdropdown: Locator; - toonInactieveToggle: Locator; + nieuweKlantButton: Locator; + zoekenInput: Locator; + filterdropdown: Locator; + toonInactieveToggle: Locator; + deleteButton: Locator; + editButton: Locator; + constructor(page: Page, url: string = "/clients") { super(page, url); - + this.createComponent = new CreateComponent(page); this.nieuweKlantButton = page.getByTestId("add"); this.zoekenInput = page.getByRole("textbox", { name: "Zoeken" }); this.filterdropdown = page.getByTestId(""); - this.toonInactieveToggle = page.locator('div').filter({ hasText: 'Toon inactieve' }).nth(5); + this.toonInactieveToggle = page + .locator("div") + .filter({ hasText: "Toon inactieve" }) + .nth(5); + this.deleteButton = page.getByRole("button", { name: "" }); + this.editButton = page.getByRole("button", { name: "" }); } - /** - * - */ - async ClickOnNieuweKlant() { - await this.nieuweKlantButton.click(); -} -} - -/* -getByTestId('add') -getByRole('textbox', { name: 'Zoeken' }) -locator('div').filter({ hasText: /^2025$/ }).nth(3) -locator('.react-select__indicator')await page.locator('svg').click(); -await page.getByText('Onderaannemer').click(); -await page.locator('#react-select-2-option-1').click(); -locator('div').filter({ hasText: 'Toon inactieve' }).nth(5) -getByRole('cell', { name: 'BE2' }) -getByRole('button', { name: '' }).first() -getByRole('button', { name: '' }).first() - -//nieuwe klant creation + /** + * Click the new klant button + */ + async ClickOnNieuweKlant() { + await this.nieuweKlantButton.click(); + } -await page.getByTestId('add').click(); -await page.getByTestId('btw').click(); -await page.getByTestId('btw').fill('1'); -await page.getByTestId('btw-continue').click(); -await page.getByTestId('name').click(); -await page.getByTestId('name').fill('klant'); -await page.locator('div').filter({ hasText: /^Type\(s\)$/ }).nth(2).click(); + /** + * Enter the given klant name in the search input + * @param klantNaam name to be searched + */ + async search(klantNaam: string) { + await this.zoekenInput.fill(klantNaam); + } -await page.getByRole('combobox', { name: 'types' }).click(); -await page.getByTestId('address').click(); -await page.getByTestId('address').fill('straat'); -await page.getByTestId('postalCode').click(); -await page.getByTestId('postalCode').fill('postcode'); -await page.getByTestId('city').click(); -await page.getByTestId('city').fill('stad'); -await page.locator('.col-lg-3 > .form-group > .css-b62m3t-container > .react-select__control > .react-select__value-container > .react-select__input-container').first().click(); -await page.getByText('België').click(); + /** + * check if a klant exists in the klanten list + * @param klantNaam name to validate + * @returns @boolean true for present, false for not present + */ + async klantExists(klantNaam: string): Promise { + const klantCell = this.page.getByRole("cell", { name: klantNaam }); + return (await klantCell.count()) > 0; + } + /** + * delete the klant at the given row or the given row locator + * @param row row number or row locator to delete + */ + async deleteKlant(row: number | Locator = 0) { + if (typeof row === "number") { + await this.deleteButton.nth(row).click(); + return; + } else { + await row.getByRole("button", { name: "" }).click(); + } + } -await page.getByTestId('contact').click(); -await page.getByTestId('contact').fill('contact'); -await page.getByTestId('contactEmail').click(); -await page.getByTestId('contactEmail').fill('email contact'); -await page.locator('div').filter({ hasText: 'Telefoon nr' }).nth(4).click(); -await page.getByTestId('telephone').fill('1'); -await page.locator('.col-lg-3 > .form-group > .react-select-base > .react-select__control > .react-select__value-container > .react-select__input-container').click(); -await page.getByText('nl').click(); + /** + * edit the klant at the given row or the given row locator + * @param row row number or row locator to edit + */ + async editKlant(row: number | Locator = 0) { + if (typeof row === "number") { + await this.editButton.nth(row).click(); + return; + } else { + await row.getByRole("button", { name: "" }).click(); + } + } -await page.locator('#rdw-wrapper-9356').getByRole('textbox', { name: 'rdw-editor' }).click(); -*/ \ No newline at end of file + /** + * get klant row by name + * @param klantNaam name of the klant to find + * @returns Locator for the klant row + */ + async getKlantRowbyname(klantNaam: string): Promise { + return this.page.getByRole("row", { name: klantNaam }); + } +} \ No newline at end of file diff --git a/tests/klanten/klanten.spec.ts b/tests/klanten/klanten.spec.ts index c123e12..b5cde37 100644 --- a/tests/klanten/klanten.spec.ts +++ b/tests/klanten/klanten.spec.ts @@ -1,16 +1,15 @@ import { test, expect } from "@playwright/test"; -import { setupTestEnvironment, teardownTestEnvironment } from "../../test-setup/setup"; - +import { + setupTestEnvironment, + teardownTestEnvironment, +} from "../../test-setup/setup"; import { KlantenPage } from "../../src/pages/Klanten/KlantenPage"; import { CreateKlantPage } from "../../src/pages/Klanten/CreateKlantPage"; import { klantTypes } from "../../src/enum/klantTypes"; - - - let klantenPage: KlantenPage; -let createKlantPage: CreateKlantPage +let createKlantPage: CreateKlantPage; test.beforeAll(async () => { await setupTestEnvironment(); @@ -21,28 +20,69 @@ test.afterAll(async () => { }); test.beforeEach(async ({ page }) => { - klantenPage = new KlantenPage(page); - createKlantPage = new CreateKlantPage(page); - + klantenPage = new KlantenPage(page); + createKlantPage = new CreateKlantPage(page); }); test("klant toevoegen", async ({ page }) => { - await klantenPage.goto(); + await klantenPage.goto(); + + await klantenPage.ClickOnNieuweKlant(); + + await createKlantPage.btwNummerInvullen("123456789"); + await createKlantPage.ClickKlantVerderAanvullen(); + await createKlantPage.klantNaamInvullen("klant naam"); + await createKlantPage.btwInvullen("btw nummer"); + await createKlantPage.typeDropdownSelecteren(klantTypes.Eindklant); + await createKlantPage.straatEnNummerInvullen("straat 1"); + await createKlantPage.postcodeInvullen("1000"); + await createKlantPage.stadInvullen("stad"); + await createKlantPage.landDropdownSelecteren("België"); + + await createKlantPage.clickBewaren(); + + const [response] = await Promise.all([ + page.waitForResponse( + (res) => + res.url().includes("api/clients") && res.request().method() === "POST" + ), + createKlantPage.clickBewaren(), + ]); + expect(response.status()).toBe(200); +}); + +test("klant zoeken", async ({ page }) => { + let klantNaam = "Wyman LLC"; + await klantenPage.goto(); - await klantenPage.ClickOnNieuweKlant(); + await klantenPage.search(klantNaam); - await createKlantPage.btwNummerInvullen("123456789"); - await createKlantPage.ClickKlantVerderAanvullen(); - await createKlantPage.klantNaamInvullen("klant naam"); - await createKlantPage.btwInvullen("btw nummer"); - await createKlantPage.typeDropdownSelecteren(klantTypes.Eindklant); - await createKlantPage.straatEnNummerInvullen("straat 1"); - await createKlantPage.postcodeInvullen("1000"); - await createKlantPage.stadInvullen("stad"); - await createKlantPage.landDropdownSelecteren("België"); - await createKlantPage.clickBewaren(); + expect(await klantenPage.klantExists(klantNaam)).toBeTruthy(); +}); + +test("klant verwijderen", async ({ page }) => { + let klantNaam = "Wyman LLC"; + await klantenPage.goto(); + await klantenPage.getKlantRowbyname(klantNaam).then(async (row) => { + await klantenPage.deleteKlant(row); + }); + expect(await klantenPage.klantExists(klantNaam)).toBeFalsy(); }); +test("klant aanpassen", async ({ page }) => { + let klantNaam = "Wyman LLC"; + let updatedKlantNaam = "aangepaste klant naam"; + await klantenPage.goto(); + + await klantenPage.getKlantRowbyname(klantNaam).then(async (row) => { + await klantenPage.editKlant(row); + }); + await createKlantPage.klantNaamInvullen(updatedKlantNaam); + + await createKlantPage.clickBewaren(); + + expect(await klantenPage.klantExists(updatedKlantNaam)).toBeTruthy(); +}); From 2e8cb71eaed5c16778434f8e3d7cda5fd4685eeb Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Wed, 29 Oct 2025 17:10:40 +0100 Subject: [PATCH 6/9] workflow clean up --- .github/workflows/playwright copy.yml | 102 ------------------ .../workflows/playwright-testcontainer.yml | 40 ------- 2 files changed, 142 deletions(-) delete mode 100644 .github/workflows/playwright copy.yml delete mode 100644 .github/workflows/playwright-testcontainer.yml diff --git a/.github/workflows/playwright copy.yml b/.github/workflows/playwright copy.yml deleted file mode 100644 index 54249b6..0000000 --- a/.github/workflows/playwright copy.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Playwright Tests - -on: - push: - branches: [ "**" ] - pull_request: - branches: [ "**" ] - -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Checkout confac repo - uses: actions/checkout@v4 - with: - repository: itenium-be/confac - path: confac - - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - name: Start MongoDB - run: | - docker volume create mongodata - docker run -d \ - -p 27017:27017 \ - -e MONGO_INITDB_ROOT_USERNAME=admin \ - -e MONGO_INITDB_ROOT_PASSWORD=pwd \ - -v mongodata:/data/db \ - --name confac-mongo \ - mongo:3.6.3 - - - name: Install confac dependencies - run: | - cd confac/backend - npm ci - cd ../frontend - npm ci - - - name: Copy sample.env to .env - run: | - cd confac/backend - cp .env.sample .env - - - name: Start confac backend and frontend - run: | - cd confac/backend - nohup npm start > backend.log 2>&1 & - cd ../frontend - nohup npm start > frontend.log 2>&1 & - shell: bash - - - name: inject dummy data - run: | - cd confac/backend - node ./public/faker/index.js - shell: bash - continue-on-error: true - - - name: Install test dependencies - run: npm ci - - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - - name: Run Playwright tests - run: npx playwright test - - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: - playwright-report/ - retention-days: 30 - - - name: Upload backend log - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - name: backend-log - path: confac/backend/backend.log - retention-days: 30 - - - name: Upload frontend log - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - name: frontend-log - path: confac/frontend/frontend.log - retention-days: 30 - - - name: Cleanup containers - if: always() - run: | - docker stop confac-mongo || true - docker rm confac-mongo || true - docker volume rm mongodata || true diff --git a/.github/workflows/playwright-testcontainer.yml b/.github/workflows/playwright-testcontainer.yml deleted file mode 100644 index 33e5694..0000000 --- a/.github/workflows/playwright-testcontainer.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: E2E Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout E2E tests - uses: actions/checkout@v4 - - - name: Checkout Confac App - uses: actions/checkout@v4 - with: - repository: your-org/confac - path: confac - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '16.10.0' - - - name: Install dependencies (E2E) - run: npm ci - - - name: Install dependencies (Backend) - run: npm ci - working-directory: confac/backend - - - name: Install dependencies (Frontend) - run: npm ci - working-directory: confac/frontend - - - name: Install Playwright browsers - run: npx playwright install --with-deps - - - name: Run E2E tests - run: npx playwright test - env: - CONFAC_APP_PATH: ${{ github.workspace }}/confac \ No newline at end of file From cae01119b60ebccf36f2d34d1182629be7d059a9 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Wed, 29 Oct 2025 17:13:26 +0100 Subject: [PATCH 7/9] removed example test --- tests/example.spec.ts | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 tests/example.spec.ts diff --git a/tests/example.spec.ts b/tests/example.spec.ts deleted file mode 100644 index 75df5f3..0000000 --- a/tests/example.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { LoginPage } from "../src/pages/LoginPage"; - - -let loginPage: LoginPage; - -test.beforeEach(async ({ page }) => { - loginPage = new LoginPage(page); -}); - -test("hello world", async ({ page }) => { - console.log("hello world"); - expect(true).toBeTruthy(); - - await loginPage.goto(); -}); From 865e6beb8e376c0b8ecd071b4b0d3928620e601a Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Wed, 29 Oct 2025 17:17:02 +0100 Subject: [PATCH 8/9] no message --- src/pages/Klanten/CreateKlantPage.ts | 8 +++----- tests/projecten/consultanten/consultanten.spec.ts | 11 ++--------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/pages/Klanten/CreateKlantPage.ts b/src/pages/Klanten/CreateKlantPage.ts index e00fa96..5b62030 100644 --- a/src/pages/Klanten/CreateKlantPage.ts +++ b/src/pages/Klanten/CreateKlantPage.ts @@ -118,6 +118,7 @@ export class CreateKlantPage extends BasePage { async straatEnNummerInvullen(straatEnNummer: string) { await this.straatEnNummerInput.fill(straatEnNummer); } + /** * enter the postcode * @param postcode string to enter @@ -125,11 +126,6 @@ export class CreateKlantPage extends BasePage { async postcodeInvullen(postcode: string) { await this.PostcodeInput.fill(postcode); } - /** - * - * @param stad - - */ /** * enter the stad @@ -165,6 +161,7 @@ export class CreateKlantPage extends BasePage { async contactEmailInvullen(contactEmail: string) { await this.contactEmailInput.fill(contactEmail); } + /** * telefoon nr invullen * @param telefoonNr string to enter @@ -172,6 +169,7 @@ export class CreateKlantPage extends BasePage { async telefoonNrInvullen(telefoonNr: string) { await this.telefoonNrInput.fill(telefoonNr); } + /** * select the given taal from the taal dropdown * @param taal string to select diff --git a/tests/projecten/consultanten/consultanten.spec.ts b/tests/projecten/consultanten/consultanten.spec.ts index 1825370..08a4fc1 100644 --- a/tests/projecten/consultanten/consultanten.spec.ts +++ b/tests/projecten/consultanten/consultanten.spec.ts @@ -3,9 +3,6 @@ import { ConsultantsPage } from "../../../src/pages/projecten/ConsultantsPage"; import { CreateConsultantsPage } from "../../../src/pages/projecten/CreateConsultantsPage"; import { setupTestEnvironment, teardownTestEnvironment } from "../../../test-setup/setup"; -let container; - - let consultantsPage: ConsultantsPage; let createConsultantsPage: CreateConsultantsPage; @@ -42,8 +39,6 @@ test("consultant zoeken", async ({ page }) => { await consultantsPage.search(search); expect(await consultantsPage.consultantExists(search)).toBeTruthy; - - }); test("consultant verwijderen", async ({ page }) => { @@ -55,8 +50,8 @@ test("consultant verwijderen", async ({ page }) => { await consultantsPage.deleteConsultant(rowToDelete); expect(await consultantsPage.consultantExists(name)).toBeFalsy(); - }); + test("consultant aanpassen", async ({ page }) => { let rowToUpdate = 5; let newName = "Aangepaste voornaam"; @@ -69,6 +64,4 @@ test("consultant aanpassen", async ({ page }) => { await createConsultantsPage.setFirstName(newName); await createConsultantsPage.clickSave(); expect(await consultantsPage.consultantExists(newName)).toBeTruthy - -}); - +}); \ No newline at end of file From d7e9706123e0e71051b8d731d0bd854f68b5bac1 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Thu, 30 Oct 2025 10:57:57 +0100 Subject: [PATCH 9/9] more robust klanten tests updated auth to do a already logged in check added random data helper --- playwright.config.ts | 2 +- src/utils/helpers/AlphaNumericHelper.ts | 95 +++++++++++++++++++++++++ tests/auth/auth.setup.ts | 38 ++++++---- tests/klanten/klanten.spec.ts | 20 +++--- 4 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 src/utils/helpers/AlphaNumericHelper.ts diff --git a/playwright.config.ts b/playwright.config.ts index 44b006b..0bffec7 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", - + storageState:'playwright/.auth/user.json', screenshot: "only-on-failure" }, diff --git a/src/utils/helpers/AlphaNumericHelper.ts b/src/utils/helpers/AlphaNumericHelper.ts new file mode 100644 index 0000000..8f433ed --- /dev/null +++ b/src/utils/helpers/AlphaNumericHelper.ts @@ -0,0 +1,95 @@ +export class AlphaNumericHelper { + /** + * Get random alphabetic string of given length + * @param length Length of string, defaults to 4 + * @returns Random alphabetic string + */ + static randomAlpha(length = 4): string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + let result = ""; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; + } + + /** + * Get random numeric string of given length + * @param length Length of string, defaults to 4 + * @returns Random numeric string + */ + static randomNumeric(length = 4): string { + const digits = "0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += digits.charAt(Math.floor(Math.random() * digits.length)); + } + return result; + } + + /** + * Get random alphanumeric string of given length + * @param length Length of string, defaults to 4 + * @returns Random alphanumeric string + */ + static randomAlphanumeric(length = 4): string { + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; + } + + /** + * Get random full name + * @returns Random first and last name + */ + static randomName(): string { + const firstNames = ["Lucas", "Emma", "Louis", "Lotte", "Noah"]; + const lastNames = ["Peeters", "Janssens", "Maes", "Wouters", "Claes"]; + const first = firstNames[Math.floor(Math.random() * firstNames.length)]; + const last = lastNames[Math.floor(Math.random() * lastNames.length)]; + return `${first} ${last}`; + } + + /** + * Get random street address + * @returns Random street with number + */ + static randomStraat(): string { + const streets = [ + "Rue de la Loi", + "Kerkstraat", + "Langestraat", + "Koninginnelaan", + "Vrijheidslaan", + ]; + const street = streets[Math.floor(Math.random() * streets.length)]; + const number = Math.floor(Math.random() * 200) + 1; + return `${street} ${number}`; + } + + /** + * Get random phone number + * @returns Random phone number string + */ + static randomPhoneNumber(): string { + let number = "04"; + for (let i = 0; i < 8; i++) { + number += Math.floor(Math.random() * 10).toString(); + } + return number; + } + + /** + * Get random BTW number + * @returns Random 10-digit numeric string + */ + static randomBtw(): string { + const digits = this.randomNumeric(9); + const suffix = "B01"; + return `BE${digits}${suffix}`; + } +} diff --git a/tests/auth/auth.setup.ts b/tests/auth/auth.setup.ts index 9abf426..6215145 100644 --- a/tests/auth/auth.setup.ts +++ b/tests/auth/auth.setup.ts @@ -1,10 +1,13 @@ -import test, { test as setup, expect } from '@playwright/test'; -import path from 'path'; +import test, { test as setup, expect, request } from "@playwright/test"; +import path from "path"; -import { setupTestEnvironment, teardownTestEnvironment } from '../../test-setup/setup'; -import { LoginPage } from '../../src/pages/LoginPage'; +import { + setupTestEnvironment, + teardownTestEnvironment, +} from "../../test-setup/setup"; +import { LoginPage } from "../../src/pages/LoginPage"; -const authFile = path.join(__dirname, '../../playwright/.auth/user.json'); +const authFile = path.join(__dirname, "../../playwright/.auth/user.json"); let login: LoginPage; test.beforeAll(async () => { @@ -15,15 +18,20 @@ test.afterAll(async () => { await teardownTestEnvironment(); }); -setup('authenticate', async ({ page }) => { - login = new LoginPage(page); - - await login.goto(); - await login.enterName('e2e-test-user'); - await login.submitForm(); +setup("authenticate", async ({ page }) => { + const api = await request.newContext({ storageState: authFile }); + const response = await api.get(process.env.BASE_URL + "/api/config"); - await expect(page).toHaveTitle("Maandelijkse facturatie - confac") - - await page.context().storageState({ path: authFile }); + if (response.status() === 200) { + return + } + login = new LoginPage(page); -}) \ No newline at end of file + await login.goto(); + await login.enterName("e2e-test-user"); + await login.submitForm(); + + await expect(page).toHaveTitle("Maandelijkse facturatie - confac"); + + await page.context().storageState({ path: authFile }); +}); diff --git a/tests/klanten/klanten.spec.ts b/tests/klanten/klanten.spec.ts index b5cde37..bf0c32e 100644 --- a/tests/klanten/klanten.spec.ts +++ b/tests/klanten/klanten.spec.ts @@ -7,6 +7,9 @@ import { import { KlantenPage } from "../../src/pages/Klanten/KlantenPage"; import { CreateKlantPage } from "../../src/pages/Klanten/CreateKlantPage"; import { klantTypes } from "../../src/enum/klantTypes"; +import { AlphaNumericHelper } from "../../src/utils/helpers/AlphaNumericHelper" + + let klantenPage: KlantenPage; let createKlantPage: CreateKlantPage; @@ -29,18 +32,16 @@ test("klant toevoegen", async ({ page }) => { await klantenPage.ClickOnNieuweKlant(); - await createKlantPage.btwNummerInvullen("123456789"); + await createKlantPage.btwNummerInvullen(AlphaNumericHelper.randomBtw()); await createKlantPage.ClickKlantVerderAanvullen(); - await createKlantPage.klantNaamInvullen("klant naam"); - await createKlantPage.btwInvullen("btw nummer"); + await createKlantPage.klantNaamInvullen("test" + AlphaNumericHelper.randomName()); + //await createKlantPage.btwInvullen("btw nummer"); await createKlantPage.typeDropdownSelecteren(klantTypes.Eindklant); - await createKlantPage.straatEnNummerInvullen("straat 1"); - await createKlantPage.postcodeInvullen("1000"); + await createKlantPage.straatEnNummerInvullen(AlphaNumericHelper.randomStraat() + AlphaNumericHelper.randomNumeric(1)); + await createKlantPage.postcodeInvullen(AlphaNumericHelper.randomNumeric(4)); await createKlantPage.stadInvullen("stad"); await createKlantPage.landDropdownSelecteren("België"); - await createKlantPage.clickBewaren(); - const [response] = await Promise.all([ page.waitForResponse( (res) => @@ -72,10 +73,11 @@ test("klant verwijderen", async ({ page }) => { }); test("klant aanpassen", async ({ page }) => { - let klantNaam = "Wyman LLC"; + let klantNaam = "test"; let updatedKlantNaam = "aangepaste klant naam"; await klantenPage.goto(); + await klantenPage.search(klantNaam) await klantenPage.getKlantRowbyname(klantNaam).then(async (row) => { await klantenPage.editKlant(row); }); @@ -86,3 +88,5 @@ test("klant aanpassen", async ({ page }) => { expect(await klantenPage.klantExists(updatedKlantNaam)).toBeTruthy(); }); + +