Compare commits

10 Commits

Author SHA1 Message Date
ISA
c03802e97f chore: Jenkinsfile 2025-09-05 12:14:38 +02:00
ISA
1485c0c92c fix: allow scripts in woodpecker 2025-09-05 11:56:46 +02:00
ISA
44ecbfa417 fix: woodpecker allow scripts 2025-09-05 11:55:10 +02:00
ISA
927a807c4d fix: woodpecker npm run server:sim 2025-09-05 11:54:00 +02:00
ISA
29a79ce0a9 fix: woodpecker 2025-09-05 11:30:39 +02:00
ISA
2166744c63 fix: woodpecker rimraf not found 2025-09-05 11:27:30 +02:00
ISA
81239f41ae fix: woodpecker compiler error 2025-09-05 11:19:00 +02:00
ISA
584593ba71 fix: .woodpecker.yml 2025-09-05 11:12:11 +02:00
ISA
4b83ff01cf test: playwright mit npm run dev erfolgreich 2025-09-05 10:54:52 +02:00
ISA
8c88aa843c chore: move playwright components folder to tests 2025-09-05 10:25:31 +02:00
36 changed files with 150 additions and 101 deletions

View File

@@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_EXPORT_STATIC=false
NEXT_PUBLIC_USE_CGI=false NEXT_PUBLIC_USE_CGI=false
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.845 NEXT_PUBLIC_APP_VERSION=1.6.855
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)

View File

@@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL
NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_EXPORT_STATIC=true
NEXT_PUBLIC_USE_CGI=true NEXT_PUBLIC_USE_CGI=true
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.845 NEXT_PUBLIC_APP_VERSION=1.6.855
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -15,6 +15,7 @@ steps:
commands: commands:
- pwd - pwd
- node -v && npm -v - node -v && npm -v
# Skip lifecycle scripts in CI to avoid running husky's prepare step
- npm ci - npm ci
# Zeig mir, ob die Datei wirklich im Checkout liegt: # Zeig mir, ob die Datei wirklich im Checkout liegt:
- echo "=== git ls-files ===" - echo "=== git ls-files ==="
@@ -33,20 +34,22 @@ steps:
PORT: "3000" PORT: "3000"
commands: commands:
- node -v && npm -v - node -v && npm -v
- npm ci # Skip lifecycle scripts in CI to avoid running husky's prepare step (husky is a devDep)
- env npm_config_production=false npm ci
- npm run build - npm run build
# Start local static simulator in background # Start local static simulator in background
- node scripts/local-cpl-sim.mjs & - npm run server:sim &
# Wait until simulator responds on port 3000 (no curl dependency) # Wait until simulator responds on port 3000 (no curl dependency)
- | - node -e "const http=require('http');let n=120;function ping(){http.get('http://localhost:3000',res=>{console.log('Server is up');process.exit(0)}).on('error',()=>{if(n--<=0){console.error('Server did not start');process.exit(1)}setTimeout(ping,1000)});}ping();"
node -e "const http=require('http');let n=120;function ping(){http.get('http://localhost:3000',res=>{console.log('Server is up');process.exit(0)}).on('error',()=>{if(n--<=0){console.error('Server did not start');process.exit(1)}setTimeout(ping,1000)});}ping();"
- npx playwright test --project=chromium - npx playwright test --project=chromium
- name: notify-success - name: notify-success
image: alpine/curl:latest image: alpine/curl:latest
when: when:
status: success status: success
commands: commands:
- curl -d "Tests erfolgreich in woodpecker" https://ntfy.sh/OEOr8DNB0aT2mXWg231PeEEKwvuzt86qgM8ezQmgfcX9ZIlZ35 - curl -d "Tests erfolgreich in woodpecker" https://ntfy.sh/OEOr8DNB0aT2mXWg231PeEEKwvuzt86qgM8ezQmgfcX9ZIlZ35
- name: notify-failure - name: notify-failure
image: alpine/curl:latest image: alpine/curl:latest
when: when:

View File

@@ -1,3 +1,58 @@
## [1.6.855] 2025-09-05
- fix: allow scripts in woodpecker
---
## [1.6.854] 2025-09-05
- fix: woodpecker allow scripts
---
## [1.6.853] 2025-09-05
- fix: woodpecker npm run server:sim
---
## [1.6.852] 2025-09-05
- fix: woodpecker
---
## [1.6.851] 2025-09-05
- fix: woodpecker rimraf not found
---
## [1.6.850] 2025-09-05
- fix: woodpecker compiler error
---
## [1.6.849] 2025-09-05
- fix: .woodpecker.yml
---
## [1.6.848] 2025-09-05
- test: playwright mit npm run dev erfolgreich
---
## [1.6.847] 2025-09-05
- chore: move playwright components folder to tests
---
## [1.6.846] 2025-09-05
- feat(kue705FO): scrolling für lange Modulnamen (48 Zeichen) + Version-Gate/Env-Override
- Unterstützt bis zu 48 Zeichen im Modulnamen; bei Überlänge automatische Laufschrift
- Marquee via react-fast-marquee (SSR-sicher per next/dynamic)
- Overflow-Erkennung + Tooltip mit vollem Namen
- Version-Gate: aktiviert ab V4.30
---
## [1.6.845] 2025-09-05 ## [1.6.845] 2025-09-05
- feat: prepare KÜ 8 for scrolling text - feat: prepare KÜ 8 for scrolling text

50
Jenkinsfile vendored
View File

@@ -6,13 +6,51 @@ pipeline {
stage('Versions') { stage('Versions') {
steps { sh 'node -v && npm -v' } steps { sh 'node -v && npm -v' }
} }
stage('Install deps') {
steps { sh 'npm ci' } stage('Verify mocks') {
}
stage('Playwright tests') {
steps { steps {
sh 'npx playwright install' // Browser-Binärdateien laden sh '''
sh 'npx playwright test' set -euxo pipefail
npm ci
echo "=== git ls-files ==="
git ls-files | grep -i "^mocks/device-cgi-simulator/SERVICE/systemMockData.js" || true
echo "=== ls -la ==="
ls -la mocks/device-cgi-simulator/SERVICE || true
echo "=== file exists? ==="
test -f mocks/device-cgi-simulator/SERVICE/systemMockData.js && echo "FOUND" || (echo "MISSING" && exit 1)
'''
}
}
stage('Build & E2E (chromium)') {
environment {
CI = 'true'
NODE_ENV = 'production'
NEXT_TELEMETRY_DISABLED = '1'
PORT = '3000'
}
steps {
sh '''
set -euxo pipefail
# Install devDependencies as well (rimraf, cross-env, etc.)
env npm_config_production=false npm ci
# Build Next.js
npm run build
# Start local static simulator in background
npm run server:sim &
# Ensure Playwright browsers and OS deps are installed (best-effort)
npx playwright install-deps || true
npx playwright install
# Wait until simulator responds on port 3000 (no curl dependency)
node -e "const http=require('http');let n=120;function ping(){http.get('http://localhost:3000',res=>{console.log('Server is up');process.exit(0)}).on('error',()=>{if(n--<=0){console.error('Server did not start');process.exit(1)}setTimeout(ping,1000)});}ping();"
# Run tests (chromium only to match Woodpecker)
npx playwright test --project=chromium
'''
} }
} }
} }

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.845", "version": "1.6.855",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.845", "version": "1.6.855",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.1.0", "@fontsource/roboto": "^5.1.0",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",

View File

@@ -1,13 +1,13 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.845", "version": "1.6.855",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",
"clean": "rimraf .next out", "clean": "rimraf .next out",
"build": "npm run clean && cross-env EXPORT_STATIC=true next build", "build": "npm run clean && cross-env EXPORT_STATIC=true next build",
"postbuild": "node -e \"const fs=require('fs');const path=require('path');fs.mkdirSync('out',{recursive:true});fs.copyFileSync('LICENSE_ICONIFY.txt', path.join('out','LICENSE_ICONIFY.txt'))\"", "postbuild": "node -e \"const fs=require('fs');const path=require('path');fs.mkdirSync('out',{recursive:true});fs.copyFileSync('LICENSE_ICONIFY.txt', path.join('out','LICENSE_ICONIFY.txt'))\"",
"serve:sim": "node ./scripts/local-cpl-sim.mjs", "server:sim": "node ./scripts/local-cpl-sim.mjs",
"start": "next start", "start": "next start",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"check": "npm run lint && npm run typecheck", "check": "npm run lint && npm run typecheck",

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runAnalogInputsTest } from "../tests/pages/analogInputs/analogInputsTest";
test("analog inputs page", async ({ page }) => {
await runAnalogInputsTest(page);
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runDashboardTest } from "../tests/pages/dashboard/dashboardTest";
test("dashboard page", async ({ page }) => {
await runDashboardTest(page);
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runDigitalInputsTest } from "../tests/pages/digitalInputs/digitalInputsTest";
test("digital inputs page", async ({ page }) => {
await runDigitalInputsTest(page);
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runDigitalOutputsTest } from "../tests/pages/digitalOutputs/digitalOutputsTest";
test("digital outputs page", async ({ page }) => {
await runDigitalOutputsTest(page);
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runSettingsPageTest } from "../tests/pages/settingsPage/settingsPageTest";
test("einstellungen page", async ({ page }) => {
await runSettingsPageTest(page);
});

View File

@@ -1,5 +0,0 @@
import { test } from "@playwright/test";
test("index page", async ({ page }) => {
await page.goto("/");
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runCableMonitoringTest } from "../tests/pages/kabelueberwachung/kabelueberwachungTest";
test("kabelueberwachung page", async ({ page }) => {
await runCableMonitoringTest(page);
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runMeldungenTest } from "../tests/pages/meldungen/meldungenTest";
test("meldungen page", async ({ page }) => {
await runMeldungenTest(page);
});

View File

@@ -1,6 +0,0 @@
import { test } from "@playwright/test";
import { runSystemTest } from "../tests/pages/system/systemTest";
test("system page", async ({ page }) => {
await runSystemTest(page);
});

View File

@@ -1,5 +0,0 @@
import { test } from "@playwright/test";
test("zutrittskontrolle page", async ({ page }) => {
await page.goto("/zutrittskontrolle");
});

View File

@@ -1,9 +1,9 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { expect } from "@playwright/test"; import { expect } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
// Kombinierte Helper-Funktion: injiziert CSS (nur einmal), hebt hervor und prüft Sichtbarkeit // Kombinierte Helper-Funktion: injiziert CSS (nur einmal), hebt hervor und prüft Sichtbarkeit

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runDashboardTest(page: Page) { export async function runDashboardTest(page: Page) {
await page.goto("/dashboard"); await page.goto("/dashboard");

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runDigitalInputsTest(page: Page) { export async function runDigitalInputsTest(page: Page) {
await page.goto("/digitalInputs"); await page.goto("/digitalInputs");

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runDigitalOutputsTest(page: Page) { export async function runDigitalOutputsTest(page: Page) {
await page.goto("/digitalOutputs"); await page.goto("/digitalOutputs");

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runCableMonitoringTest(page: Page) { export async function runCableMonitoringTest(page: Page) {
await page.goto("/kabelueberwachung"); await page.goto("/kabelueberwachung");
@@ -94,7 +94,9 @@ export async function runCableMonitoringTest(page: Page) {
); );
await page.waitForTimeout(400); await page.waitForTimeout(400);
await highlightAndExpectVisible(page, page.getByText("Kabel 1")); // Use a unique locator to avoid strict mode violation (two elements contain text "Kabel 1").
// The card exposes a title attribute "Kabel 1", so prefer getByTitle for a single match.
await highlightAndExpectVisible(page, page.getByTitle("Kabel 1").first());
await page.waitForTimeout(400); await page.waitForTimeout(400);
await highlightAndExpectVisible( await highlightAndExpectVisible(
@@ -197,7 +199,11 @@ export async function runCableMonitoringTest(page: Page) {
); );
await page.waitForTimeout(400); await page.waitForTimeout(400);
await highlightAndExpectVisible(page, page.getByText("Kabel 8")); // For cable 8 the UI shows a long name and exposes it via title attribute; target it explicitly
await highlightAndExpectVisible(
page,
page.getByTitle("Kabel_8 in Salzgitter bei Hannover").first()
);
await page.waitForTimeout(400); await page.waitForTimeout(400);
await page.waitForTimeout(400); await page.waitForTimeout(400);

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runMeldungenTest(page: Page) { export async function runMeldungenTest(page: Page) {
await page.goto("/meldungen"); await page.goto("/meldungen");

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runSettingsPageTest(page: Page) { export async function runSettingsPageTest(page: Page) {
await page.goto("/einstellungen"); await page.goto("/einstellungen");

View File

@@ -1,8 +1,8 @@
import type { Page } from "@playwright/test"; import type { Page } from "@playwright/test";
import { highlightAndExpectVisible } from "@playwright/utils/highlight"; import { highlightAndExpectVisible } from "@playwright/utils/highlight";
import { navTest } from "@/playwright/components/navigation/navTest"; import { navTest } from "@/playwright/tests/components/navigation/navTest";
import { headerTest } from "@/playwright/components/header/headerTest"; import { headerTest } from "@/playwright/tests/components/header/headerTest";
import { footerTest } from "@/playwright/components/footer/footerTest"; import { footerTest } from "@/playwright/tests/components/footer/footerTest";
export async function runSystemTest(page: Page) { export async function runSystemTest(page: Page) {
await page.goto("/system"); await page.goto("/system");

View File

@@ -55,5 +55,10 @@
"types/global.d.ts" "types/global.d.ts"
], ],
"exclude": ["node_modules", ".next", "out"] "exclude": [
"node_modules",
".next",
"out",
"public"
]
} }