From 1bc40a39b707215b5ae2d6f00a2b5eb78d545969 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Mon, 27 Oct 2025 16:58:29 +0100 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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(); }); + + From 942eaeb486bd50ce5a4d4e03edb875df70e62252 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Thu, 30 Oct 2025 15:26:06 +0100 Subject: [PATCH 10/11] Added Date helper Added random string helper Added APIs for klant, consultant, user & project --- .env | 1 + src/pages/projecten/CreateProjectenPage.ts | 96 +++++++++++ src/utils/API/ConsultantApi .ts | 78 +++++++++ src/utils/API/KlantApi.ts | 120 ++++++++++++++ src/utils/API/PrjectAPI.ts | 182 +++++++++++++++++++++ src/utils/API/UserAPI.ts | 76 +++++++++ src/utils/helpers/DateHelper.ts | 75 +++++++++ 7 files changed, 628 insertions(+) create mode 100644 src/pages/projecten/CreateProjectenPage.ts create mode 100644 src/utils/API/ConsultantApi .ts create mode 100644 src/utils/API/KlantApi.ts create mode 100644 src/utils/API/PrjectAPI.ts create mode 100644 src/utils/API/UserAPI.ts create mode 100644 src/utils/helpers/DateHelper.ts diff --git a/.env b/.env index 07aa051..078cff5 100644 --- a/.env +++ b/.env @@ -1,4 +1,5 @@ BASE_URL=http://localhost:3000 +BASE_URL_API=http://localhost:9000 CONFAC_APP_PATH= ../confac #TESTCONTAINERS_RYUK_DISABLED=true diff --git a/src/pages/projecten/CreateProjectenPage.ts b/src/pages/projecten/CreateProjectenPage.ts new file mode 100644 index 0000000..164e41c --- /dev/null +++ b/src/pages/projecten/CreateProjectenPage.ts @@ -0,0 +1,96 @@ +import { Locator, Page } from "@playwright/test"; +import { BasePage } from "../BasePage"; +import { DateHelper } from "../../utils/helpers/DateHelper"; + +export class CreateProjectenPage extends BasePage { + readonly accountManagerDropdown: Locator; + readonly consultantDropdown: Locator; + readonly eindKlantDropdown: Locator; + readonly startDatum: Locator; + readonly bewarenButton: Locator; + + constructor(page: Page, url: string = "/projects/create") { + super(page, url); + + this.accountManagerDropdown = page.getByText( + "Account ManagerMaak een keuze" + ); + this.consultantDropdown = page.getByText("ConsultantMaak een keuze"); + this.eindKlantDropdown = page.getByText("EindklantMaak een keuze"); + this.startDatum = page.getByRole("textbox", { name: "Start datum" }); + + this.bewarenButton = page.getByRole("button", {name : "Bewaren"}); + } + + /** + * Select the given account manager, based on text or number + * @param accountManager String or Number, string matches text. Number matches position (starting from 0). De + */ + async selectAccountManager(accountManager: string | number = 0) { + await this.accountManagerDropdown.click(); + + if (typeof accountManager === "number") { + await this.page + .locator(".react-select__option") + .nth(accountManager) + .click(); + } else { + await this.page + .locator(".react-select__option", { + hasText: accountManager, + }) + .click(); + } + } + + /** + * Select the given consult, based on text or number + * @param consultant String or n + */ + async selectconsultant(consultant: string | number = 0) { + await this.consultantDropdown.click(); + + if (typeof consultant === "number") { + await this.page.locator(".react-select__option").nth(consultant).click(); + } else { + await this.page + .locator(".react-select__option", { + hasText: consultant, + }) + .click(); + } + } + + /** + * Select the given eindklant + * @param eindKlant String or Number, string matches text. Number matches position (starting from 0) + */ + async selectEindKlant(eindKlant: string | number = 0) { + await this.eindKlantDropdown.click(); + + if (typeof eindKlant === "number") { + await this.page.locator(".react-select__option").nth(eindKlant).click(); + } else { + await this.page + .locator(".react-select__option", { + hasText: eindKlant, + }) + .click(); + } + } + + /** + * Set the start datum + * @param date + */ + async setStartDatum(date: string = DateHelper.todayDMY()) { + await this.startDatum.fill(date); + } + + /** + * Click the bewaren button + */ + async clickBewaren() { + await this.bewarenButton.click(); + } +} diff --git a/src/utils/API/ConsultantApi .ts b/src/utils/API/ConsultantApi .ts new file mode 100644 index 0000000..d5a3f21 --- /dev/null +++ b/src/utils/API/ConsultantApi .ts @@ -0,0 +1,78 @@ +import { APIRequestContext } from '@playwright/test'; + +export type ConsultantData = { + _id?: string; + name?: string; + firstName?: string; + slug?: string; + type?: string; + email?: string; + telephone?: string; + active?: boolean; + audit?: Record; + accountingCode?: string; +}; + +export class ConsultantApi { + private static endpoint = `${process.env.BASE_URL_API}/api/consultants`; + + /** + * Get all consultants + */ + static async getAll(request: APIRequestContext): Promise { + const response = await request.get(ConsultantApi.endpoint); + if (!response.ok()) throw new Error(`Failed to fetch consultants: ${response.status()} ${await response.text()}`); + + const contentType = response.headers()['content-type'] || ''; + if (!contentType.includes('application/json')) { + const text = await response.text(); + throw new Error(`Expected JSON but got ${contentType}\nResponse:\n${text}`); + } + + return await response.json(); + } + + /** + * Create a consultant + */ + static async create(request: APIRequestContext, overrides: Partial = {}) { + const defaultPayload: ConsultantData = { + _id: '', + name: 'Default Consultant', + firstName: 'Default First', + slug: '', + type: 'consultant', + email: 'default@example.com', + telephone: '0000000000', + active: true, + audit: {}, + accountingCode: 'DEFAULT', + ...overrides, + }; + + const response = await request.post(ConsultantApi.endpoint, { data: defaultPayload }); + if (!response.ok()) + throw new Error(`Failed to create consultant: ${response.status()} ${await response.text()}`); + + return await response.json(); + } + + /** + * Safe create — only create if not already exists + */ + static async safeCreateConsultant( + request: APIRequestContext, + overrides: Partial = {} + ): Promise { + const consultants = await ConsultantApi.getAll(request); + const existing = consultants.find(c => c.name === (overrides.name || 'Default Consultant')); + + if (existing) { + console.log(`Consultant '${existing.name}' already exists, skipping creation.`); + return existing; + } + + console.log(`Creating new consultant '${overrides.name ?? 'Default Consultant'}'`); + return await ConsultantApi.create(request, overrides); + } +} diff --git a/src/utils/API/KlantApi.ts b/src/utils/API/KlantApi.ts new file mode 100644 index 0000000..86159ff --- /dev/null +++ b/src/utils/API/KlantApi.ts @@ -0,0 +1,120 @@ +import { APIRequestContext } from '@playwright/test'; +import { AlphaNumericHelper } from '../helpers/AlphaNumericHelper'; + +const klantName = "Test API klant" +const klantbtw = AlphaNumericHelper.randomBtw(); + +export type KlantData = { + _id?: string; + slug?: string; + active?: boolean; + name?: string; + types?: string[]; + address?: string; + city?: string; + postalCode?: string; + country?: string; + telephone?: string; + btw?: string; + invoiceFileName?: string; + hoursInDay?: number; + defaultInvoiceLines?: any[]; + attachments?: any[]; + notes?: string; + comments?: any[]; + defaultInvoiceDateStrategy?: string; + defaultChangingOrderNr?: boolean; + email?: { + to?: string; + cc?: string; + bcc?: string; + subject?: string; + body?: string; + attachments?: any[]; + combineAttachments?: boolean; + }; + language?: string; + frameworkAgreement?: { status?: string; notes?: string }; + audit?: Record; +}; + +export class KlantApi { + private static endpoint = `${process.env.BASE_URL_API}/api/clients`; + + /** + * Get all clients + */ + static async getAll(request: APIRequestContext): Promise { + const response = await request.get(KlantApi.endpoint); + if (!response.ok()) throw new Error(`Failed to fetch clients: ${response.status()} ${await response.text()}`); + + const contentType = response.headers()['content-type'] || ''; + if (!contentType.includes('application/json')) { + const text = await response.text(); + throw new Error(`Expected JSON but got ${contentType}\nResponse:\n${text}`); + } + + return await response.json(); + } + + /** + * Create a client + */ + static async create(request: APIRequestContext, overrides: Partial = {}) { + const defaultPayload: KlantData = { + _id: '', + slug: '', + active: true, + name: klantName, + types: ['endCustomer'], + address: '', + city: '', + postalCode: '', + country: '', + telephone: '', + btw: klantbtw, + invoiceFileName: '', + hoursInDay: 8, + defaultInvoiceLines: [], + attachments: [], + notes: '', + comments: [], + defaultInvoiceDateStrategy: 'prev-month-last-day', + defaultChangingOrderNr: false, + email: { + to: '', + cc: '', + bcc: '', + subject: '', + body: '', + attachments: [], + combineAttachments: false, + }, + language: 'en', + frameworkAgreement: { status: 'NoContract', notes: '' }, + audit: {}, + ...overrides, + }; + + const response = await request.post(KlantApi.endpoint, { data: defaultPayload }); + if (!response.ok()) throw new Error(`Failed to create client: ${response.status()} ${await response.text()}`); + + return await response.json(); + } + + /** + * Safe create — only create if not already exists + */ + static async safeCreateKlant(request: APIRequestContext, overrides: Partial = {}): Promise { + const clients = await KlantApi.getAll(request); + const existing = clients.find(c => c.name === (overrides.name || klantName)); + + if (existing) { + console.log(`Client '${existing.name}' already exists, skipping creation.`); + return existing; + } + + console.log(`Creating new client '${overrides.name ?? 'Test API klant'}'`); + return await KlantApi.create(request, overrides); + } +} \ No newline at end of file diff --git a/src/utils/API/PrjectAPI.ts b/src/utils/API/PrjectAPI.ts new file mode 100644 index 0000000..f67bf3f --- /dev/null +++ b/src/utils/API/PrjectAPI.ts @@ -0,0 +1,182 @@ +import { APIRequestContext } from "@playwright/test"; +import { UserApi } from "./UserAPI"; +import { ConsultantApi } from "./ConsultantApi "; +import { KlantApi } from "./KlantApi"; + +export type ProjectData = { + _id?: string; + accountManager?: string; + consultantId?: string; + startDate?: string; + client?: { + clientId?: string; + defaultInvoiceLines?: Array<{ + desc?: string; + price?: number; + amount?: number; + tax?: number; + type?: string; + sort?: number; + }>; + advancedInvoicing?: boolean; + }; + projectMonthConfig?: { + changingOrderNr?: boolean; + proforma?: string; + timesheetCheck?: boolean; + inboundInvoice?: boolean; + }; + contract?: { + status?: string; + notes?: string; + }; + audit?: Record; + forEndCustomer?: boolean; + endCustomer?: string | null; + comments?: any[]; +}; + +export class ProjectApi { + private static endpoint = `${process.env.BASE_URL_API}/api/projects`; + + /** + * Get all projects + */ + static async getAll(request: APIRequestContext): Promise { + const response = await request.get(ProjectApi.endpoint); + + if (!response.ok()) { + throw new Error( + `Failed to fetch projects: ${response.status()} ${await response.text()}` + ); + } + + const contentType = response.headers()["content-type"] || ""; + if (!contentType.includes("application/json")) { + const text = await response.text(); + throw new Error( + `Expected JSON but got ${contentType}\nResponse:\n${text}` + ); + } + + return await response.json(); + } + + /** + * Create a project + */ + static async create( + request: APIRequestContext, + { + accountManagerName, + consultantName, + clientName, + ...overrides + }: { + accountManagerName: string; + consultantName: string; + clientName: string; + } & Partial + ): Promise { + // --- Lookups --- + const users = await UserApi.getAll(request); + const consultants = await ConsultantApi.getAll(request); + const clients = await KlantApi.getAll(request); + + const accountManager = users.find((u) => u.name === accountManagerName); + const consultant = consultants.find((c) => c.name === consultantName); + const klant = clients.find((k) => k.name === clientName); + + if (!accountManager) + throw new Error(`Account manager '${accountManagerName}' not found`); + if (!consultant) + throw new Error(`Consultant '${consultantName}' not found`); + if (!klant) throw new Error(`Client '${clientName}' not found`); + + // --- Payload --- + const defaultPayload: ProjectData = { + _id: "", + accountManager: accountManager._id, + consultantId: consultant._id, + startDate: new Date().toISOString().split("T")[0] + "T00:00:00.000Z", + client: { + clientId: klant._id, + defaultInvoiceLines: [ + { desc: "", price: 0, amount: 0, tax: 21, type: "daily", sort: 0 }, + ], + advancedInvoicing: false, + }, + projectMonthConfig: { + changingOrderNr: false, + proforma: "no", + timesheetCheck: true, + inboundInvoice: false, + }, + contract: { status: "NoContract", notes: "" }, + audit: {}, + forEndCustomer: false, + endCustomer: null, + comments: [], + ...overrides, + }; + + // --- API Call --- + const response = await request.post(ProjectApi.endpoint, { + data: defaultPayload, + }); + if (!response.ok()) + throw new Error( + `Failed to create project: ${response.status()} ${await response.text()}` + ); + + return await response.json(); + } + + /** + * Safe create — skip creation if project already exists (same consultant + client) + */ + static async safeCreate( + request: APIRequestContext, + params: { + accountManagerName: string; + consultantName: string; + clientName: string; + } & Partial + ): Promise { + const { accountManagerName, consultantName, clientName } = params; + + // Lookup dependencies first + const users = await UserApi.getAll(request); + const consultants = await ConsultantApi.getAll(request); + const clients = await KlantApi.getAll(request); + + const accountManager = users.find((u) => u.name === accountManagerName); + const consultant = consultants.find((c) => c.name === consultantName); + const klant = clients.find((k) => k.name === clientName); + + if (!accountManager || !consultant || !klant) { + throw new Error( + `Cannot safely create project: one of the required records is missing.\n` + + `AccountManager: ${!!accountManager}, Consultant: ${!!consultant}, Client: ${!!klant}` + ); + } + + const projects = await ProjectApi.getAll(request); + const existing = projects.find( + (p) => + p.accountManager === accountManager._id && + p.consultantId === consultant._id && + p.client?.clientId === klant._id + ); + + if (existing) { + console.log( + `Project already exists for ${consultant.name} → ${klant.name}` + ); + return existing; + } + + console.log(`Creating new project for ${consultant.name} → ${klant.name}`); + return await ProjectApi.create(request, params); + } +} diff --git a/src/utils/API/UserAPI.ts b/src/utils/API/UserAPI.ts new file mode 100644 index 0000000..f5a725b --- /dev/null +++ b/src/utils/API/UserAPI.ts @@ -0,0 +1,76 @@ +import { APIRequestContext } from '@playwright/test'; + +export type UserData = { + _id?: string; + name?: string; + firstName?: string; + alias?: string; + email?: string; + active?: boolean; + roles?: string[]; + audit?: Record; +}; + +export class UserApi { + /** Base URL for user API */ + private static endpoint = `${process.env.BASE_URL_API}/api/user`; + + /** + * Fetch all users + */ + static async getAll(request: APIRequestContext): Promise { + const response = await request.get(UserApi.endpoint); + if (!response.ok()) { + throw new Error(`Failed to fetch users: ${response.status()} ${await response.text()}`); + } + return await response.json(); + } + + /** + * Create a user + */ + static async createUser(request: APIRequestContext, overrides: Partial = {}) { + const defaultPayload: UserData = { + _id: '', + name: 'e2e-test-user', + firstName: ' ', + alias: '', + email: '', + active: true, + roles: ['1'], + audit: {}, + ...overrides, + }; + + const response = await request.post(UserApi.endpoint, { data: defaultPayload }); + if (!response.ok()) { + throw new Error(`Failed to create user: ${response.status()} ${await response.text()}`); + } + return await response.json(); + } + + /** + * Safe create: create user if not already present + */ + static async safeCreateUser( + request: APIRequestContext, + overrides: Partial = {} + ): Promise { + const users = await UserApi.getAll(request); + + // You can change the matching condition if needed + const existing = users.find( + u => + (overrides.email && u.email === overrides.email) || + (!overrides.email && u.name === (overrides.name || 'e2e-test-user')) + ); + + if (existing) { + console.log(`User '${existing.name}' already exists, skipping creation.`); + return existing; + } + + console.log(`User not found, creating new user: ${overrides.name ?? 'e2e-test-user'}`); + return await UserApi.createUser(request, overrides); + } +} diff --git a/src/utils/helpers/DateHelper.ts b/src/utils/helpers/DateHelper.ts new file mode 100644 index 0000000..f361699 --- /dev/null +++ b/src/utils/helpers/DateHelper.ts @@ -0,0 +1,75 @@ +// utils/DateHelper.ts + +export class DateHelper { + /** + * Get today's date as a Date object + */ + static today(): Date { + return new Date(); + } + + /** + * Get today's date as ISO string (UTC) + */ + static todayISO(): string { + return new Date().toISOString(); + } + + /** + * return todays date as dd mm yyyy + * @returns + */ + static todayDMY(): string { + return this.formatDMY(this.today()) + } + + /** + * Get a date N days from today + * @param days Number of days to add (can be negative) + */ + static relativeDate(days: number = 0): Date { + const date = new Date(); + date.setDate(date.getDate() + days); + return date; + } + + /** + * Format a Date object to YYYY-MM-DD + * @param date Date to format + */ + static formatYMD(date: Date): string { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); // months are 0-based + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + /** + * Get a date N days from today in YYYY-MM-DD format + * @param days Number of days to add + */ + static relativeYMD(days: number = 0): string { + const date = this.relativeDate(days); + return this.formatYMD(date); + } + + /** + * Get a date in DD/MM/YYYY format (common for European UI) + * @param date Date to format + */ + static formatDMY(date: Date): string { + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const year = date.getFullYear(); + return `${day}/${month}/${year}`; + } + + /** + * Get a date N days from today in DD/MM/YYYY format + * @param days Number of days to add + */ + static relativeDMY(days: number = 0): string { + const date = this.relativeDate(days); + return this.formatDMY(date); + } +} From 31dfe6cfe8df88775d8eeb12b670593debb59969 Mon Sep 17 00:00:00 2001 From: remco Verbruggen Date: Thu, 30 Oct 2025 16:39:19 +0100 Subject: [PATCH 11/11] Added project tests -create -search -update updated getKlantRowbyName to use regex --- src/pages/Klanten/KlantenPage.ts | 4 +- src/pages/projecten/CreateProjectenPage.ts | 26 ++++-- src/pages/projecten/ProjectenPage.ts | 101 +++++++++++++++++++++ tests/klanten/klanten.spec.ts | 6 +- tests/projecten/projecten.spec.ts | 96 ++++++++++++++++++++ 5 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 tests/projecten/projecten.spec.ts diff --git a/src/pages/Klanten/KlantenPage.ts b/src/pages/Klanten/KlantenPage.ts index 9c8d0bd..bb062a7 100644 --- a/src/pages/Klanten/KlantenPage.ts +++ b/src/pages/Klanten/KlantenPage.ts @@ -83,7 +83,7 @@ export class KlantenPage extends BasePage { * @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 }); + async getKlantRowbyName(klantNaam: string): Promise { + return this.page.getByRole("row", { name: new RegExp(klantNaam) }); } } \ No newline at end of file diff --git a/src/pages/projecten/CreateProjectenPage.ts b/src/pages/projecten/CreateProjectenPage.ts index 164e41c..ef7de72 100644 --- a/src/pages/projecten/CreateProjectenPage.ts +++ b/src/pages/projecten/CreateProjectenPage.ts @@ -12,14 +12,19 @@ export class CreateProjectenPage extends BasePage { constructor(page: Page, url: string = "/projects/create") { super(page, url); - this.accountManagerDropdown = page.getByText( - "Account ManagerMaak een keuze" - ); - this.consultantDropdown = page.getByText("ConsultantMaak een keuze"); - this.eindKlantDropdown = page.getByText("EindklantMaak een keuze"); + this.accountManagerDropdown = page + .locator('div.form-group:has-text("Account Manager")') + .locator("div.react-select__control"); + this.consultantDropdown = page + .locator('div.form-group:has-text("Consultant")') + .locator("div.react-select__control"); + this.eindKlantDropdown = page + .locator('div.form-group:has-text("Eindklant")') + .locator("div.react-select__control"); + this.startDatum = page.getByRole("textbox", { name: "Start datum" }); - this.bewarenButton = page.getByRole("button", {name : "Bewaren"}); + this.bewarenButton = page.getByRole("button", { name: "Bewaren" }); } /** @@ -69,13 +74,13 @@ export class CreateProjectenPage extends BasePage { await this.eindKlantDropdown.click(); if (typeof eindKlant === "number") { - await this.page.locator(".react-select__option").nth(eindKlant).click(); + await this.page.locator(".react-select__option").nth(eindKlant).click({force: true }); } else { await this.page .locator(".react-select__option", { hasText: eindKlant, }) - .click(); + .click({force: true }); } } @@ -91,6 +96,9 @@ export class CreateProjectenPage extends BasePage { * Click the bewaren button */ async clickBewaren() { - await this.bewarenButton.click(); + await Promise.all([ + this.page.waitForURL("**/projects"), // update pattern to […] path you expect + this.bewarenButton.click(), + ]); } } diff --git a/src/pages/projecten/ProjectenPage.ts b/src/pages/projecten/ProjectenPage.ts index e69de29..78f1227 100644 --- a/src/pages/projecten/ProjectenPage.ts +++ b/src/pages/projecten/ProjectenPage.ts @@ -0,0 +1,101 @@ +import { Locator, Page } from "@playwright/test"; +import { BasePage } from "../BasePage"; +import { CreateComponent } from "../../components/CreateComponent"; + +export class ProjectenPage extends BasePage { + CreateComponent: CreateComponent; + + readonly nieuwProjectButton: Locator; + readonly zoekenInput: Locator; + readonly inactieveToggle: Locator; + readonly editButton: Locator; + + constructor(page: Page, url: string = "/projects") { + super(page, url); + + this.CreateComponent = new CreateComponent(page); + + this.nieuwProjectButton = page.getByTestId("add"); + this.zoekenInput = page.getByRole("textbox", { name: "Zoeken" }); + this.inactieveToggle = page.locator("span").nth(3); + this.editButton = page.getByRole("button", { name: "" }); + } + + /** + * Click the nieuw project button (on the page) + * redirects to @CreateProjectenPage + */ + async clickNieuwProject() { + await this.nieuwProjectButton.click(); + } + + /** + * + * @param project + */ + async enterZoeken(project: string) { + await this.zoekenInput.fill(project); + } + + /** + * Edit the project at the given row or the given row locator + * @param row row number or row locator to edit + */ + async editProject(row: number | Locator = 0) { + if (typeof row === "number") { + await this.editButton.nth(row).click({force: true}); + return; + } else { + await row.getByRole("button", { name: "" }).click({force: true}); + } + + await this.page.evaluate(() => Promise.resolve()); // flush microtasks - page UI hangs without this for some reason + } + + /** + * Toggle showing inactive projecten + */ + async toggleInactiveConsultants(): Promise { + await this.inactieveToggle.click(); + } + + /** + * Get project row by name + * @param name - Name of the project to find + * @returns Locator for the project row + */ + async getProjectRow(name: string): Promise { + return this.page.getByRole("row", { name: new RegExp(name) }); + } + + /** + * Get project name by row index + * @param rowIndex row index to get the project name from + * @returns Promise with the project name + */ + async getProjectNameByRow(rowIndex: number): Promise { + const rows = this.page.locator("table tbody tr"); + const nameLink = rows.nth(rowIndex).locator("td a").first(); + const name = (await nameLink.textContent()) ?? ""; + return name.trim(); + } + + /** + * Check if project exists in the list + * @param name - Name of the project to check + * @returns Promise indicating if project exists + */ + async projectExists(name: string): Promise { + const row = await this.getProjectRow(name); + return await row.isVisible(); + } + + /** + * Get project row by name + * @param name - Name of the project to find + * @returns Locator for the project row + */ + async getProjectRowbyName(name: string): Promise { + return this.page.getByRole("row", { name: new RegExp(name) }); + } +} diff --git a/tests/klanten/klanten.spec.ts b/tests/klanten/klanten.spec.ts index bf0c32e..638a823 100644 --- a/tests/klanten/klanten.spec.ts +++ b/tests/klanten/klanten.spec.ts @@ -35,7 +35,7 @@ test("klant toevoegen", async ({ page }) => { await createKlantPage.btwNummerInvullen(AlphaNumericHelper.randomBtw()); await createKlantPage.ClickKlantVerderAanvullen(); await createKlantPage.klantNaamInvullen("test" + AlphaNumericHelper.randomName()); - //await createKlantPage.btwInvullen("btw nummer"); + //await createKlantPage.btwInvullen("btw nummer"); --skipping as it is already pre filled here await createKlantPage.typeDropdownSelecteren(klantTypes.Eindklant); await createKlantPage.straatEnNummerInvullen(AlphaNumericHelper.randomStraat() + AlphaNumericHelper.randomNumeric(1)); await createKlantPage.postcodeInvullen(AlphaNumericHelper.randomNumeric(4)); @@ -65,7 +65,7 @@ test("klant verwijderen", async ({ page }) => { let klantNaam = "Wyman LLC"; await klantenPage.goto(); - await klantenPage.getKlantRowbyname(klantNaam).then(async (row) => { + await klantenPage.getKlantRowbyName(klantNaam).then(async (row) => { await klantenPage.deleteKlant(row); }); @@ -78,7 +78,7 @@ test("klant aanpassen", async ({ page }) => { await klantenPage.goto(); await klantenPage.search(klantNaam) - await klantenPage.getKlantRowbyname(klantNaam).then(async (row) => { + await klantenPage.getKlantRowbyName(klantNaam).then(async (row) => { await klantenPage.editKlant(row); }); diff --git a/tests/projecten/projecten.spec.ts b/tests/projecten/projecten.spec.ts new file mode 100644 index 0000000..d493749 --- /dev/null +++ b/tests/projecten/projecten.spec.ts @@ -0,0 +1,96 @@ +import { test, expect } from "@playwright/test"; +import { + setupTestEnvironment, + teardownTestEnvironment, +} from "../../test-setup/setup"; +import { ProjectenPage } from "../../src/pages/projecten/ProjectenPage"; +import { CreateProjectenPage } from "../../src/pages/projecten/CreateProjectenPage"; +import { KlantApi } from "../../src/utils/API/KlantApi"; +import { ConsultantApi } from "../../src/utils/API/ConsultantApi "; +import { UserApi } from "../../src/utils/API/UserAPI"; +import { AlphaNumericHelper } from "../../src/utils/helpers/AlphaNumericHelper"; +import { ProjectApi } from "../../src/utils/API/PrjectAPI"; + +let projectenPage: ProjectenPage; +let createProjectenPage: CreateProjectenPage; + +test.beforeAll(async () => { + await setupTestEnvironment(); +}); + +test.afterAll(async () => { + await teardownTestEnvironment(); +}); + +test.beforeEach(async ({ page }) => { + projectenPage = new ProjectenPage(page); + createProjectenPage = new CreateProjectenPage(page); +}); + +test("project toevoegen", async ({ page, request }) => { + let klant = "projectKlant" + AlphaNumericHelper.randomAlphanumeric(4); + let consultant = "ProjectConsultant"; + let user = "e2e-test-user"; + + await KlantApi.safeCreateKlant(request, { name: klant }); + await ConsultantApi.safeCreateConsultant(request, { name: consultant }); + await UserApi.safeCreateUser(request); + + await createProjectenPage.goto(); + + await createProjectenPage.selectAccountManager("e2e-test-user"); + await createProjectenPage.selectconsultant(consultant); + await createProjectenPage.selectEindKlant(klant); + await createProjectenPage.setStartDatum(); + + await createProjectenPage.clickBewaren(); +}); + +test("Project zoeken", async ({ page, request }) => { + let klant = "projectKlant" + AlphaNumericHelper.randomAlphanumeric(4); + let consultant = "ProjectConsultant"; + let user = "e2e-test-user"; + + await KlantApi.safeCreateKlant(request, { name: klant }); + await ConsultantApi.safeCreateConsultant(request, { name: consultant }); + await UserApi.safeCreateUser(request); + await ProjectApi.create(request, { + accountManagerName: user, + consultantName: consultant, + clientName: klant, + }); + + await projectenPage.goto(); + await projectenPage.enterZoeken(klant); + + await projectenPage.projectExists(klant); +}); + +test("Project aanpassen", async ({ page, request }) => { + let klant = "projectKlant" + AlphaNumericHelper.randomAlphanumeric(4); + let klant2 = "projectKlantedit" + AlphaNumericHelper.randomAlphanumeric(4); + let consultant = "ProjectConsultant"; + let user = "e2e-test-user"; + + await KlantApi.safeCreateKlant(request, { name: klant }); + await KlantApi.safeCreateKlant(request, { name: klant2 }); + + await ConsultantApi.safeCreateConsultant(request, { name: consultant }); + await UserApi.safeCreateUser(request); + await ProjectApi.create(request, { + accountManagerName: user, + consultantName: consultant, + clientName: klant, + }); + + await projectenPage.goto(); + await projectenPage.enterZoeken(klant); + await projectenPage.getProjectRowbyName(klant).then(async (row) => { + await projectenPage.editProject(row); + }); + + await createProjectenPage.selectEindKlant(klant2); + await createProjectenPage.clickBewaren(); + + await projectenPage.projectExists(klant2); +});