import { test, expect } from '@playwright/test'; // Test 1: Song-Datenbank page renders with heading test('song-datenbank page renders with correct heading', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Verify we're on songs page await expect(page).toHaveURL(/.*songs/); // Verify heading is visible await expect(page.getByRole('heading', { name: 'Song-Datenbank' })).toBeVisible(); // Verify description text is visible await expect(page.getByText('Verwalte alle Songs, Übersetzungen und Arrangements.')).toBeVisible(); }); // Test 2: Song list shows table structure or empty state test('song list shows table structure or empty state', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist or empty state is shown const songTable = page.locator('table'); const emptyState = page.getByText(/Noch keine Songs vorhanden|Keine Songs gefunden/); const hasTable = await songTable.isVisible().catch(() => false); const isEmpty = await emptyState.isVisible().catch(() => false); // Either table exists OR empty state exists (but not both) expect(hasTable || isEmpty).toBe(true); }); // Test 3: Song row shows structural elements (name, CCLI, dates) test('song row shows name, CCLI ID, and date columns', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist const songTable = page.locator('table'); const hasTable = await songTable.isVisible().catch(() => false); if (!hasTable) { // Skip test if no songs exist test.skip(); } // Get first song row const firstRow = page.locator('tbody tr').first(); await expect(firstRow).toBeVisible(); // Verify row has cells for title, CCLI, created date, updated date, last used date const cells = firstRow.locator('td'); const cellCount = await cells.count(); // Should have at least 7 cells: title, CCLI, created, updated, last_used, translation, actions expect(cellCount).toBeGreaterThanOrEqual(7); // Verify title cell has text content const titleCell = cells.nth(0); const titleText = await titleCell.textContent(); expect(titleText).toBeTruthy(); expect(titleText?.length).toBeGreaterThan(0); // Verify CCLI cell exists (may be empty or have ID) const ccliCell = cells.nth(1); await expect(ccliCell).toBeVisible(); }); // Test 4: Search input filters songs test('search input filters songs', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist const songTable = page.locator('table'); const hasTable = await songTable.isVisible().catch(() => false); if (!hasTable) { // Skip test if no songs exist test.skip(); } // Get initial song count const initialRows = page.locator('tbody tr'); const initialCount = await initialRows.count(); if (initialCount === 0) { test.skip(); } // Type in search input const searchInput = page.getByTestId('song-list-search-input'); await expect(searchInput).toBeVisible(); // Type a search query that likely won't match anything await searchInput.fill('xyznonexistentquery123'); // Wait for search to debounce and results to update await page.waitForTimeout(600); await page.waitForLoadState('networkidle'); // Verify empty state or reduced results const emptyState = page.getByText('Keine Songs gefunden'); const emptyStateVisible = await emptyState.isVisible().catch(() => false); // Either empty state is shown OR no rows exist const finalRows = page.locator('tbody tr'); const finalCount = await finalRows.count(); expect(emptyStateVisible || finalCount === 0).toBe(true); // Clear search await searchInput.fill(''); await page.waitForTimeout(600); await page.waitForLoadState('networkidle'); }); // Test 5: Pagination works (if enough songs exist) test('pagination controls work when multiple pages exist', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if pagination exists const paginationNav = page.locator('nav').filter({ has: page.getByTestId('song-list-pagination-prev') }); const hasPagination = await paginationNav.isVisible().catch(() => false); if (!hasPagination) { // Skip test if pagination doesn't exist (not enough songs) test.skip(); } // Verify prev/next buttons exist const prevButton = page.getByTestId('song-list-pagination-prev'); const nextButton = page.getByTestId('song-list-pagination-next'); await expect(prevButton).toBeVisible(); await expect(nextButton).toBeVisible(); // Verify page indicator text exists const pageIndicator = page.locator('p').filter({ hasText: /Seite \d+ von \d+/ }); await expect(pageIndicator).toBeVisible(); }); // Test 6: Delete button triggers confirmation dialog (cancel → song still visible) test('delete button triggers confirmation dialog and cancel keeps song', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist const songTable = page.locator('table'); const hasTable = await songTable.isVisible().catch(() => false); if (!hasTable) { // Skip test if no songs exist test.skip(); } // Get first song row const firstRow = page.locator('tbody tr').first(); const hasDeleteButton = await firstRow.getByTestId('song-list-delete-button').isVisible().catch(() => false); if (!hasDeleteButton) { // Skip test if no delete button visible test.skip(); } // Get song title before delete attempt const titleBefore = await firstRow.locator('td').first().textContent(); // Click delete button const deleteButton = firstRow.getByTestId('song-list-delete-button'); await deleteButton.click(); // Wait for confirmation dialog to appear await page.waitForTimeout(200); // Verify confirmation dialog is visible const confirmDialog = page.locator('div').filter({ hasText: /Song löschen\?/ }); const dialogVisible = await confirmDialog.isVisible().catch(() => false); if (!dialogVisible) { test.skip(); } // Click cancel button const cancelButton = page.getByTestId('song-list-delete-cancel-button'); await expect(cancelButton).toBeVisible(); await cancelButton.click(); // Wait for dialog to close await page.waitForTimeout(200); // Verify dialog is gone const dialogGone = await confirmDialog.isVisible().catch(() => false); expect(dialogGone).toBe(false); // Verify song is still visible in table const songStillVisible = await firstRow.isVisible().catch(() => false); expect(songStillVisible).toBe(true); // Verify title is unchanged const titleAfter = await firstRow.locator('td').first().textContent(); expect(titleAfter).toBe(titleBefore); }); // Test 7: Edit button opens SongEditModal test('edit button opens song edit modal', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist const songTable = page.locator('table'); const hasTable = await songTable.isVisible().catch(() => false); if (!hasTable) { // Skip test if no songs exist test.skip(); } // Get first song row const firstRow = page.locator('tbody tr').first(); const hasEditButton = await firstRow.getByTestId('song-list-edit-button').isVisible().catch(() => false); if (!hasEditButton) { // Skip test if no edit button visible test.skip(); } // Click edit button const editButton = firstRow.getByTestId('song-list-edit-button'); await editButton.click(); // Wait for modal to appear await page.waitForTimeout(300); // Verify modal is visible (check for modal container with testid) const editModal = page.getByTestId('song-list-edit-modal'); const modalVisible = await editModal.isVisible().catch(() => false); // Modal should be visible or at least the modal backdrop should appear expect(modalVisible).toBe(true); }); // Test 8: Download button triggers download (assert non-error response) test('download button triggers download action', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist const songTable = page.locator('table'); const hasTable = await songTable.isVisible().catch(() => false); if (!hasTable) { // Skip test if no songs exist test.skip(); } // Get first song row const firstRow = page.locator('tbody tr').first(); const hasDownloadButton = await firstRow.getByTestId('song-list-download-button').isVisible().catch(() => false); if (!hasDownloadButton) { // Skip test if no download button visible test.skip(); } // Listen for download event const downloadPromise = page.waitForEvent('download').catch(() => null); // Click download button const downloadButton = firstRow.getByTestId('song-list-download-button'); await downloadButton.click(); // Wait a bit for download to start or for any response await page.waitForTimeout(500); // Verify no error toast appeared const errorToast = page.locator('div').filter({ hasText: /Fehler|Error/ }); const hasError = await errorToast.isVisible().catch(() => false); // Download may or may not trigger (depends on implementation) // But we verify no error occurred expect(hasError).toBe(false); }); // Test 9: Translate button navigates to translate page test('translate button navigates to translate page', async ({ page }) => { await page.goto('/songs'); await page.waitForLoadState('networkidle'); // Check if songs exist const songTable = page.locator('table'); const hasTable = await songTable.isVisible().catch(() => false); if (!hasTable) { // Skip test if no songs exist test.skip(); } // Get first song row const firstRow = page.locator('tbody tr').first(); const hasTranslateLink = await firstRow.getByTestId('song-list-translate-link').isVisible().catch(() => false); if (!hasTranslateLink) { // Skip test if no translate link visible test.skip(); } // Get the translate link href const translateLink = firstRow.getByTestId('song-list-translate-link'); const href = await translateLink.getAttribute('href'); // Verify href matches translate pattern expect(href).toMatch(/\/songs\/\d+\/translate/); // Click translate link await translateLink.click(); // Wait for navigation await page.waitForLoadState('networkidle'); // Verify we navigated to translate page await expect(page).toHaveURL(/\/songs\/\d+\/translate/); });