All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Bumped APP_VERSION to 0.8.125 and updated the changelog to reflect recent changes. - Added new tests for the dashboard API to ensure proper HTTP 200 responses when inner lists are mocked. - Enhanced the ExerciseListBulkToolbar component with a data-testid for improved testing capabilities. - Refactored the TrainingPlanningPage by extracting utility functions to trainingPlanningPageHelpers for better code organization.
5.7 KiB
5.7 KiB
Instructions
- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.
Test info
- Name: dev-smoke-test.spec.js >> 12. Trainingsplanung: Seite lädt mit Überschrift
- Location: tests\dev-smoke-test.spec.js:275:1
Error details
Error: page.goto: net::ERR_CONNECTION_REFUSED at http://127.0.0.1:3098/
Call log:
- navigating to "http://127.0.0.1:3098/", waiting until "load"
Test source
1 | const { test, expect } = require('@playwright/test');
2 |
3 | const TEST_EMAIL = process.env.TEST_EMAIL || 'lars@stommer.com';
4 | const TEST_PASSWORD = process.env.TEST_PASSWORD || '12345678';
5 |
6 | /** Primärer Submit auf der Login-Seite (nicht den Tab "Login" vs. "Registrieren"). */
7 | async function submitLoginForm(page) {
8 | await page.getByRole('button', { name: 'Anmelden' }).click();
9 | }
10 |
11 | async function login(page) {
> 12 | await page.goto('/');
| ^ Error: page.goto: net::ERR_CONNECTION_REFUSED at http://127.0.0.1:3098/
13 | await page.waitForLoadState('networkidle');
14 |
15 | // Warte bis Login-Seite geladen ist
16 | await page.waitForSelector('input[type="email"]', { timeout: 10000 });
17 |
18 | await page.fill('input[type="email"]', TEST_EMAIL);
19 | await page.fill('input[type="password"]', TEST_PASSWORD);
20 | await submitLoginForm(page);
21 | // Wait until auth is complete: URL leaves /login and Dashboard is rendered
22 | await page.waitForURL((url) => !url.toString().includes('/login'), { timeout: 15000 });
23 | await page.waitForLoadState('networkidle');
24 | }
25 |
26 | test('1. Login funktioniert', async ({ page }) => {
27 | await page.goto('/');
28 | await page.waitForSelector('input[type="email"]', { timeout: 10000 });
29 | await page.fill('input[type="email"]', TEST_EMAIL);
30 | await page.fill('input[type="password"]', TEST_PASSWORD);
31 | await submitLoginForm(page);
32 | await page.waitForLoadState('networkidle');
33 |
34 | // Nach Login soll der Tab "Login" (Moduswahl) verschwinden — nicht der Submit "Anmelden"
35 | const loginButton = page.locator('button:has-text("Login")');
36 | await expect(loginButton).toHaveCount(0, { timeout: 10000 });
37 |
38 | await page.screenshot({ path: 'screenshots/01-nach-login.png' });
39 | console.log('✓ Login erfolgreich');
40 | });
41 |
42 | test('2. Dashboard lädt ohne Fehler', async ({ page }) => {
43 | await login(page);
44 |
45 | // Warte bis Spinner verschwunden
46 | await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
47 |
48 | // Dashboard: h1 „Dashboard“ + Begrüßungstext (nicht mehr „Willkommen bei Shinkan“ als Überschrift)
49 | const main = page.locator('.app-main');
50 | await expect(main.getByRole('heading', { level: 1, name: 'Dashboard' })).toBeVisible({
51 | timeout: 5000,
52 | });
53 | await expect(main.getByText(/Shinkan unterstützt dich/i)).toBeVisible({ timeout: 5000 });
54 |
55 | await page.screenshot({ path: 'screenshots/02-dashboard.png' });
56 | console.log('✓ Dashboard OK');
57 | });
58 |
59 | test('3. Navigation zu Übungen', async ({ page }) => {
60 | await login(page);
61 |
62 | await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 });
63 |
64 | // Bei Viewport ≥1024px ist .bottom-nav versteckt — Mobile garantieren wie in playwright.config.js
65 | await page.setViewportSize({ width: 390, height: 844 });
66 |
67 | // Bottom-Nav: Navigation und URL gemeinsam abwarten (vermeidet race mit networkidle)
68 | const exercisesLink = page.locator('.bottom-nav').getByRole('link', { name: /Übungen/i });
69 | await Promise.all([
70 | page.waitForURL(
71 | (u) => {
72 | const path = u.pathname.replace(/\/$/, '') || '/'
73 | return path === '/exercises'
74 | },
75 | { timeout: 15000 },
76 | ),
77 | exercisesLink.click(),
78 | ]);
79 | await page.waitForLoadState('networkidle');
80 |
81 | // Wie Test 4 (Vereine): eine eindeutige h1 — nicht h1,h2-Kombi (Strict Mode + mehrere Treffer)
82 | const main = page.locator('.app-main');
83 | await expect(main.getByRole('heading', { level: 1, name: /Übungen/i })).toBeVisible({
84 | timeout: 10000,
85 | });
86 |
87 | await page.screenshot({ path: 'screenshots/03-uebungen.png' });
88 | console.log('✓ Übungen-Seite erreichbar');
89 | });
90 |
91 | test('4. Navigation zu Vereine', async ({ page }) => {
92 | await login(page);
93 | await page.setViewportSize({ width: 390, height: 844 });
94 |
95 | await page.locator('.bottom-nav a[href="/clubs"]').click();
96 | await page.waitForLoadState('networkidle');
97 |
98 | // ClubsPage: <h1>Vereinsverwaltung</h1> + Tab <h2>Vereine</h2> → ein kombinierter
99 | // Selektor löst 2 Treffer aus (Playwright strict mode). URL + primäre Überschrift reichen.
100 | await expect(page).toHaveURL(/\/clubs\/?$/, { timeout: 5000 });
101 | await expect(page.getByRole('heading', { level: 1, name: /Vereinsverwaltung/i })).toBeVisible({
102 | timeout: 5000,
103 | });
104 |
105 | await page.screenshot({ path: 'screenshots/04-vereine.png' });
106 | console.log('✓ Vereine-Seite erreichbar');
107 | });
108 |
109 | test('5. Desktop-Sidebar sichtbar (Desktop)', async ({ page }) => {
110 | // Desktop-Viewport
111 | await page.setViewportSize({ width: 1280, height: 800 });
112 |