Skip to content

Commit 02065b5

Browse files
phashManuel Rödigclaude
authored
fix(ci): nightly-E2E gruen + Dependabot + Action-Versionen bumpen (#13)
Die nightly-E2E (e2e.yml) war seit Erstellung bei JEDEM Lauf rot (17 passed / 12 failed). Zwei Ursachen behoben: - Onboarding-Welcome-Modal-Overlay fing Klicks ab (Reset-All, Slider, Masken, Logout) -> 30s-Timeouts in editor/login/screenshots.spec. Fix: beforeEach setzt `lumen.onboarding.v1=dismissed` + clearCookies, analog zu admin/feedback.spec. - Marketplace-Seed lud ein PNG hoch, Backend verlangt fuer oeffentliche Preset-Previews aber JPEG (presets.py) + prueft Magic-Bytes (images.py) -> 400/415. Fix: uploadTinyImage nutzt jetzt ein valides 1x1-JPEG (image/jpeg, ff d8 ff ...). Zusaetzlich: - screenshots.spec ist ein Doku-Generator, kein CI-Test -> hinter LUMEN_SCREENSHOTS=1 gegated (skippt im nightly). - playwright.config: html-Reporter ergaenzt, damit der Artifact-Upload (frontend/playwright-report/) tatsaechlich Inhalt hat. - .github/dependabot.yml: npm (frontend), pip (backend), github-actions -- woechentlich, minor+patch gruppiert. - Action-Versionen gebumpt (Node-20-Deprecation): checkout v5, setup-node v6, setup-python v6, pnpm/action-setup v6, upload-artifact v5. Co-authored-by: Manuel Rödig <m.roedig@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dbed6f1 commit 02065b5

9 files changed

Lines changed: 197 additions & 24 deletions

File tree

.github/dependabot.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Dependabot: automatische Dependency-Update-PRs fuer alle drei Ecosystems.
2+
# Security-Alerts muessen zusaetzlich im Repo-Setting aktiviert werden
3+
# (Settings -> Code security -> Dependabot alerts) — die config hier deckt
4+
# nur die Version-Updates ab.
5+
#
6+
# Gruppierung: minor+patch werden pro Ecosystem in EINEN PR gebuendelt
7+
# (weniger Rauschen), major-Bumps kommen einzeln (potenziell breaking).
8+
version: 2
9+
updates:
10+
# Frontend (React/Vite/TS) — pnpm-Lockfile in frontend/
11+
- package-ecosystem: npm
12+
directory: /frontend
13+
schedule:
14+
interval: weekly
15+
day: monday
16+
time: "06:00"
17+
timezone: Europe/Berlin
18+
open-pull-requests-limit: 5
19+
commit-message:
20+
prefix: "chore(deps)"
21+
prefix-development: "chore(deps-dev)"
22+
groups:
23+
frontend-minor-patch:
24+
update-types:
25+
- minor
26+
- patch
27+
28+
# Backend (FastAPI/SQLAlchemy) — requirements.txt + requirements-dev.txt
29+
- package-ecosystem: pip
30+
directory: /backend
31+
schedule:
32+
interval: weekly
33+
day: monday
34+
time: "06:00"
35+
timezone: Europe/Berlin
36+
open-pull-requests-limit: 5
37+
commit-message:
38+
prefix: "chore(deps)"
39+
prefix-development: "chore(deps-dev)"
40+
groups:
41+
backend-minor-patch:
42+
update-types:
43+
- minor
44+
- patch
45+
46+
# GitHub Actions in .github/workflows/
47+
- package-ecosystem: github-actions
48+
directory: /
49+
schedule:
50+
interval: weekly
51+
day: monday
52+
time: "06:00"
53+
timezone: Europe/Berlin
54+
open-pull-requests-limit: 5
55+
commit-message:
56+
prefix: "ci(deps)"
57+
groups:
58+
actions-all:
59+
update-types:
60+
- minor
61+
- patch

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ jobs:
2626
run:
2727
working-directory: frontend
2828
steps:
29-
- uses: actions/checkout@v4
29+
- uses: actions/checkout@v5
3030

3131
- name: Setup pnpm
32-
uses: pnpm/action-setup@v4
32+
uses: pnpm/action-setup@v6
3333
with:
3434
version: 9
3535

3636
- name: Setup Node
37-
uses: actions/setup-node@v4
37+
uses: actions/setup-node@v6
3838
with:
3939
node-version: 22
4040
cache: pnpm
@@ -65,10 +65,10 @@ jobs:
6565
run:
6666
working-directory: backend
6767
steps:
68-
- uses: actions/checkout@v4
68+
- uses: actions/checkout@v5
6969

7070
- name: Setup Python
71-
uses: actions/setup-python@v5
71+
uses: actions/setup-python@v6
7272
with:
7373
python-version: "3.12"
7474

.github/workflows/e2e.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
name: Playwright E2E
2020
runs-on: ubuntu-24.04
2121
steps:
22-
- uses: actions/checkout@v4
22+
- uses: actions/checkout@v5
2323

2424
- name: Dev-Stack hochfahren (PG + Keycloak + MinIO)
2525
run: docker compose -f deployment/docker-compose.dev.yml up -d
@@ -35,7 +35,7 @@ jobs:
3535
echo "Keycloak nicht ready"; docker compose -f deployment/docker-compose.dev.yml logs keycloak; exit 1
3636
3737
- name: Setup Python
38-
uses: actions/setup-python@v5
38+
uses: actions/setup-python@v6
3939
with:
4040
python-version: "3.12"
4141

@@ -72,12 +72,12 @@ jobs:
7272
echo "Backend nicht ready"; cat backend.log; exit 1
7373
7474
- name: Setup pnpm
75-
uses: pnpm/action-setup@v4
75+
uses: pnpm/action-setup@v6
7676
with:
7777
version: 9
7878

7979
- name: Setup Node
80-
uses: actions/setup-node@v4
80+
uses: actions/setup-node@v6
8181
with:
8282
node-version: 22
8383
cache: pnpm
@@ -116,7 +116,7 @@ jobs:
116116

117117
- name: Playwright-Report als Artifact
118118
if: always()
119-
uses: actions/upload-artifact@v4
119+
uses: actions/upload-artifact@v5
120120
with:
121121
name: playwright-report
122122
path: frontend/playwright-report/

frontend/e2e/api-helper.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,69 @@ async function api<T>(token: string, path: string, init: FetchInit = {}): Promis
3131
return (await res.json()) as T;
3232
}
3333

34-
/** Minimales 1x1 PNG (stat. Daten) — gross genug, dass HEAD eine Size meldet. */
35-
const TINY_PNG_BYTES = new Uint8Array([
36-
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
37-
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
38-
0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00,
39-
0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0xfa, 0xcf, 0xc0, 0xc0,
40-
0xc0, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x95, 0x90, 0xf4, 0x97, 0x00,
41-
0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
34+
// Minimales valides 1x1 JPEG. MUSS ein JPEG sein: oeffentliche Preset-
35+
// Previews verlangen content_type image/jpeg (presets.py), und confirm_upload
36+
// prueft die echten Magic-Bytes (\xff\xd8\xff) gegen den deklarierten Typ
37+
// (images.py). Ein PNG wuerde hier mit 400 bzw. 415 abgelehnt.
38+
const TINY_JPEG_BYTES = new Uint8Array([
39+
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
40+
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43,
41+
0x00, 0x0a, 0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b,
42+
0x0a, 0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
43+
0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21, 0x26,
44+
0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30, 0x41, 0x31,
45+
0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44, 0x49, 0x43, 0x3c,
46+
0x48, 0x37, 0x3d, 0x3e, 0x3b, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x0a, 0x0b,
47+
0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22, 0x28,
48+
0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
49+
0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
50+
0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
51+
0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
52+
0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01, 0x03,
53+
0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00,
54+
0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
55+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
56+
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00,
57+
0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00,
58+
0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
59+
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
60+
0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24,
61+
0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
62+
0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
63+
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56,
64+
0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
65+
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86,
66+
0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
67+
0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3,
68+
0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
69+
0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
70+
0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
71+
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00,
72+
0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
73+
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
74+
0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00,
75+
0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00,
76+
0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
77+
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08,
78+
0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
79+
0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18,
80+
0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
81+
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55,
82+
0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
83+
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84,
84+
0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
85+
0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
86+
0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4,
87+
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
88+
0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
89+
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
90+
0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x4a,
91+
0x28, 0xa2, 0x80, 0x3f, 0xff, 0xd9,
4292
]);
4393

4494
export async function uploadTinyImage(
4595
token: string,
46-
filename = "marketplace-preview.png",
96+
filename = "marketplace-preview.jpg",
4797
): Promise<{ id: string }> {
4898
// Snake-Case-Body: dev backend kann beide Formen, alte Backends nur snake.
4999
const init = await api<{ id?: string; upload_url?: string; uploadUrl?: string }>(
@@ -53,8 +103,8 @@ export async function uploadTinyImage(
53103
method: "POST",
54104
body: JSON.stringify({
55105
filename,
56-
content_type: "image/png",
57-
size_bytes: TINY_PNG_BYTES.byteLength,
106+
content_type: "image/jpeg",
107+
size_bytes: TINY_JPEG_BYTES.byteLength,
58108
}),
59109
},
60110
);
@@ -66,8 +116,8 @@ export async function uploadTinyImage(
66116
// PUT direkt gegen den Pre-Signed URL (MinIO/Garage).
67117
const put = await fetch(uploadUrl, {
68118
method: "PUT",
69-
headers: { "Content-Type": "image/png" },
70-
body: TINY_PNG_BYTES,
119+
headers: { "Content-Type": "image/jpeg" },
120+
body: TINY_JPEG_BYTES,
71121
});
72122
if (!put.ok) {
73123
throw new Error(`Pre-Signed-PUT fehlgeschlagen: ${put.status}`);

frontend/e2e/editor.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ const SAMPLE_DIR = join(HERE, "..", "..", "tests-fixtures", "test-samples");
1010
const JPG_PATH = join(SAMPLE_DIR, "gradient.jpg");
1111
const PNG_PATH = join(SAMPLE_DIR, "gradient.png");
1212

13+
// Onboarding auf "dismissed" vorsetzen: ein frischer Test-User triggert sonst
14+
// das Welcome-Modal beim ersten /editor-Render, dessen Overlay alle Klicks
15+
// (Reset-All, Slider, Masken-Toggles) abfaengt -> 30s-Timeouts. Cookies leeren
16+
// gegen KC-Session-Leak zwischen Tests. Gleiches Muster wie admin/feedback.spec.
17+
test.beforeEach(async ({ page, context }) => {
18+
await context.clearCookies();
19+
await page.addInitScript(() => {
20+
window.localStorage.setItem(
21+
"lumen.onboarding.v1",
22+
JSON.stringify({ status: "dismissed" }),
23+
);
24+
});
25+
});
26+
1327
test.describe("Editor", () => {
1428
test("JPG laden, Bypass-Toggle, Reset-All", async ({ page }) => {
1529
const user = await loginAsNewUser(page);

frontend/e2e/login.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ import { test, expect } from "@playwright/test";
22

33
import { cleanupUser, loginAsNewUser } from "./auth-helper";
44

5+
// Cookies leeren (KC-Session-Leak zwischen Tests) + Onboarding "dismissed":
6+
// nach dem Login landet man im Editor, dessen Welcome-Modal sonst den
7+
// Logout-Button-Klick abfaengt -> 30s-Timeout. Gleiches Muster wie admin.spec.
8+
test.beforeEach(async ({ page, context }) => {
9+
await context.clearCookies();
10+
await page.addInitScript(() => {
11+
window.localStorage.setItem(
12+
"lumen.onboarding.v1",
13+
JSON.stringify({ status: "dismissed" }),
14+
);
15+
});
16+
});
17+
518
test.describe("Login-Flow gegen Keycloak", () => {
619
test("ohne Login: /editor redirected auf /login", async ({ page }) => {
720
await page.goto("/editor");

frontend/e2e/marketplace.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,19 @@ import { apiTokenFor, cleanupUser, loginAsNewUser } from "./auth-helper";
1515
* den Component-Tests (vitest) abgedeckt und wuerde im E2E-Setup
1616
* mehrere Sekunden plus Bilddatei-Auswahl kosten.
1717
*/
18+
// Onboarding "dismissed" vorsetzen + Cookies leeren: sonst blockiert das
19+
// Welcome-Modal den Logout-Klick (Creator->Consumer-Wechsel) und KC-Sessions
20+
// leaken zwischen Tests. Gleiches Muster wie admin/feedback.spec.
21+
test.beforeEach(async ({ page, context }) => {
22+
await context.clearCookies();
23+
await page.addInitScript(() => {
24+
window.localStorage.setItem(
25+
"lumen.onboarding.v1",
26+
JSON.stringify({ status: "dismissed" }),
27+
);
28+
});
29+
});
30+
1831
test.describe("Marketplace", () => {
1932
test("Empty-State sichtbar wenn keine Presets veroeffentlicht", async ({
2033
page,

frontend/e2e/screenshots.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,26 @@ const SHOTS = join(HERE, "..", "..", "docs", "screenshots", "phase5");
1717

1818
test.use({ viewport: { width: 1440, height: 900 } });
1919

20+
// Doku-Screenshots sind kein CI-Test (siehe Header): nur explizit via
21+
// LUMEN_SCREENSHOTS=1 ausfuehren. Im nightly-Lauf wird der Test geskippt,
22+
// damit die fragile Doku-Sequenz (Preset speichern/anwenden, Masken) die
23+
// Suite nicht rot faerbt.
24+
const RUN_SCREENSHOTS = Boolean(process.env.LUMEN_SCREENSHOTS);
25+
26+
// Onboarding "dismissed" vorsetzen, damit das Welcome-Modal beim explizit
27+
// gestarteten Lauf nicht die Editor-Klicks blockiert.
28+
test.beforeEach(async ({ page, context }) => {
29+
await context.clearCookies();
30+
await page.addInitScript(() => {
31+
window.localStorage.setItem(
32+
"lumen.onboarding.v1",
33+
JSON.stringify({ status: "dismissed" }),
34+
);
35+
});
36+
});
37+
2038
test("UI-Screenshots: Editor-Hauptzustaende fuer Phase-5-Doku", async ({ page }) => {
39+
test.skip(!RUN_SCREENSHOTS, "Doku-Screenshots nur via LUMEN_SCREENSHOTS=1");
2140
const user = await loginAsNewUser(page);
2241
try {
2342
await page.goto("/editor");

frontend/playwright.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ export default defineConfig({
1313
fullyParallel: false,
1414
retries: process.env.CI ? 2 : 0,
1515
workers: 1,
16-
reporter: [["list"]],
16+
// list = lesbare Konsolen-Ausgabe; html = durchsuchbarer Report mit
17+
// eingebetteten Traces/Screenshots, der im nightly-Workflow als Artifact
18+
// hochgeladen wird (sonst existiert kein playwright-report/-Verzeichnis).
19+
reporter: [["list"], ["html", { open: "never" }]],
1720
use: {
1821
baseURL: BASE_URL,
1922
trace: "retain-on-failure",

0 commit comments

Comments
 (0)