import { test, expect } from '@playwright/test'; // Test 1: Songs block accordion can be expanded and collapsed test('songs block accordion can be expanded and collapsed', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); // Find the Songs block toggle button (4th block) const blockToggles = page.getByTestId('service-edit-block-toggle'); const songsToggle = blockToggles.filter({ has: page.locator('text=Songs') }).first(); const toggleExists = await songsToggle.isVisible().catch(() => false); if (!toggleExists) { test.skip(); } await expect(songsToggle).toBeVisible(); // Get the Songs block content container const songsBlock = page.getByTestId('songs-block'); // Verify block is initially visible (expanded by default) await expect(songsBlock).toBeVisible(); // Click toggle to collapse await songsToggle.click(); await page.waitForTimeout(300); // Wait for transition // Verify block is hidden const isHidden = await songsBlock.isHidden().catch(() => true); expect(isHidden).toBe(true); // Click toggle again to expand await songsToggle.click(); await page.waitForTimeout(300); // Wait for transition // Verify block is visible again await expect(songsBlock).toBeVisible(); }); // Test 2: Song list shows songs in correct order or empty state test('song list shows songs or empty state', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const songsBlock = page.getByTestId('songs-block'); await expect(songsBlock).toBeVisible(); // Check for song cards const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { // No songs - verify empty state message await expect(songsBlock).toContainText('Fuer diesen Service sind aktuell keine Songs vorhanden.'); return; } // Songs exist - verify first card is rendered with order label const firstSongCard = songCards.first(); await expect(firstSongCard).toBeVisible(); await expect(firstSongCard.locator('text=/Song \\d+/')).toBeVisible(); }); // Test 3: Each song row shows name, CCLI ID, and status badge test('song row shows name, CCLI ID, and status badge', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } const firstSongCard = songCards.first(); // Verify CCLI label exists await expect(firstSongCard.locator('text=/CCLI:/')).toBeVisible(); // Verify translation indicator exists await expect(firstSongCard.locator('text=/Hat Uebersetzung:/')).toBeVisible(); // Verify status badge exists (either "Zugeordnet" or "Nicht zugeordnet") const statusBadge = firstSongCard.locator('text=/zugeordnet/i').first(); await expect(statusBadge).toBeVisible(); }); // Test 4: Unmatched songs show "Erstellung anfragen" button and manual assign select test('unmatched songs show request creation button and manual assign', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } // Look for unmatched song card (has "Nicht zugeordnet" badge) const unmatchedCard = songCards.filter({ has: page.locator('text=Nicht zugeordnet') }).first(); const hasUnmatched = await unmatchedCard.isVisible().catch(() => false); if (!hasUnmatched) { test.skip(); } // Verify "Erstellung anfragen" button const requestButton = page.getByTestId('songs-block-request-button').first(); await expect(requestButton).toBeVisible(); await expect(requestButton).toContainText('Erstellung anfragen'); // Verify search input const searchInput = page.getByTestId('songs-block-search-input').first(); await expect(searchInput).toBeVisible(); // Verify song select dropdown const songSelect = page.getByTestId('songs-block-song-select').first(); await expect(songSelect).toBeVisible(); // Verify "Zuordnen" (assign) button const assignButton = page.getByTestId('songs-block-assign-button').first(); await expect(assignButton).toBeVisible(); await expect(assignButton).toContainText('Zuordnen'); }); // Test 5: Matched songs show arrangement dropdown with options test('matched songs show arrangement dropdown with options', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); // Check for arrangement configurator (only present for matched songs) const arrangementConfigurator = page.getByTestId('arrangement-configurator').first(); const hasMatched = await arrangementConfigurator.isVisible().catch(() => false); if (!hasMatched) { test.skip(); } // Verify arrangement select dropdown exists const arrangementSelect = page.getByTestId('arrangement-select').first(); await expect(arrangementSelect).toBeVisible(); // Verify add button const addButton = page.getByTestId('arrangement-add-button').first(); await expect(addButton).toBeVisible(); await expect(addButton).toContainText('Hinzufügen'); // Verify clone button const cloneButton = page.getByTestId('arrangement-clone-button').first(); await expect(cloneButton).toBeVisible(); await expect(cloneButton).toContainText('Klonen'); }); // Test 6: Arrangement "Hinzufügen" (Add) button opens name prompt test('arrangement add button opens name prompt', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const addButton = page.getByTestId('arrangement-add-button').first(); const hasMatched = await addButton.isVisible().catch(() => false); if (!hasMatched) { test.skip(); } // Set up dialog handler before clicking (window.prompt is synchronous) let promptShown = false; page.once('dialog', async (dialog) => { promptShown = true; expect(dialog.type()).toBe('prompt'); expect(dialog.message()).toContain('Name des neuen Arrangements'); await dialog.dismiss(); // Cancel without creating }); await addButton.click(); await page.waitForTimeout(500); expect(promptShown).toBe(true); }); // Test 7: Arrangement "Klonen" (Clone) button opens name prompt test('arrangement clone button opens name prompt', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const cloneButton = page.getByTestId('arrangement-clone-button').first(); const hasMatched = await cloneButton.isVisible().catch(() => false); if (!hasMatched) { test.skip(); } // Verify arrangement select has options (clone requires a selected arrangement) const arrangementSelect = page.getByTestId('arrangement-select').first(); const optionCount = await arrangementSelect.locator('option').count(); if (optionCount === 0) { test.skip(); } // Set up dialog handler before clicking let promptShown = false; page.once('dialog', async (dialog) => { promptShown = true; expect(dialog.type()).toBe('prompt'); expect(dialog.message()).toContain('Name des neuen Arrangements'); await dialog.dismiss(); // Cancel without creating }); await cloneButton.click(); await page.waitForTimeout(500); expect(promptShown).toBe(true); }); // Test 8: Preview button opens song preview (placeholder toast for now) test('preview button is present for matched songs', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const previewButton = page.getByTestId('songs-block-preview-button').first(); const hasMatched = await previewButton.isVisible().catch(() => false); if (!hasMatched) { test.skip(); } // Verify preview button exists with correct text await expect(previewButton).toBeVisible(); await expect(previewButton).toContainText('Vorschau'); }); // Test 9: Download (PDF) button is present for songs with selected arrangement test('download button is present for matched songs', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const downloadButton = page.getByTestId('songs-block-download-button').first(); const hasMatched = await downloadButton.isVisible().catch(() => false); if (!hasMatched) { test.skip(); } // Verify download button exists with correct text await expect(downloadButton).toBeVisible(); await expect(downloadButton).toContainText('PDF herunterladen'); }); // Test 10: Translation checkbox toggles (if song has translation) test('translation checkbox toggles if song has translation', async ({ page }) => { await page.goto('/services'); await page.waitForLoadState('networkidle'); const editButton = page.getByTestId('service-list-edit-button').first(); const hasEditableService = await editButton.isVisible().catch(() => false); if (!hasEditableService) { test.skip(); } await editButton.click(); await page.waitForLoadState('networkidle'); const translationCheckbox = page.getByTestId('songs-block-translation-checkbox').first(); const hasTranslation = await translationCheckbox.isVisible().catch(() => false); if (!hasTranslation) { test.skip(); } // Get current checked state const initialState = await translationCheckbox.isChecked(); // Toggle the checkbox await translationCheckbox.click(); await page.waitForTimeout(300); // Verify state changed const toggledState = await translationCheckbox.isChecked(); expect(toggledState).not.toBe(initialState); // Toggle back to restore original state await translationCheckbox.click(); await page.waitForTimeout(300); // Verify restored to original state const restoredState = await translationCheckbox.isChecked(); expect(restoredState).toBe(initialState); });