import { test, expect } from '@playwright/test'; // Test 1: Edit button opens modal 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 const editModal = page.getByTestId('song-edit-modal'); await expect(editModal).toBeVisible(); // Verify modal title await expect(page.getByText('Song bearbeiten')).toBeVisible(); }); // Test 2: Modal shows input fields (name, CCLI ID, copyright) test('modal shows song metadata input fields', 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) { test.skip(); } // Get first song row and click edit const firstRow = page.locator('tbody tr').first(); const hasEditButton = await firstRow.getByTestId('song-list-edit-button').isVisible().catch(() => false); if (!hasEditButton) { test.skip(); } const editButton = firstRow.getByTestId('song-list-edit-button'); await editButton.click(); // Wait for modal to appear await page.waitForTimeout(300); // Verify modal is visible const editModal = page.getByTestId('song-edit-modal'); await expect(editModal).toBeVisible(); // Verify input fields are visible const titleInput = page.getByTestId('song-edit-modal-title-input'); const ccliInput = page.getByTestId('song-edit-modal-ccli-input'); const copyrightTextarea = page.getByTestId('song-edit-modal-copyright-textarea'); await expect(titleInput).toBeVisible(); await expect(ccliInput).toBeVisible(); await expect(copyrightTextarea).toBeVisible(); // Verify labels are visible await expect(page.getByText('Titel')).toBeVisible(); await expect(page.getByText('CCLI-ID')).toBeVisible(); await expect(page.getByText('Copyright-Text')).toBeVisible(); }); // Test 3: Fields are auto-saved on change (debounced) — verify no explicit save button test('modal fields auto-save on change without explicit save button', 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) { test.skip(); } // Get first song row and click edit const firstRow = page.locator('tbody tr').first(); const hasEditButton = await firstRow.getByTestId('song-list-edit-button').isVisible().catch(() => false); if (!hasEditButton) { test.skip(); } const editButton = firstRow.getByTestId('song-list-edit-button'); await editButton.click(); // Wait for modal to appear await page.waitForTimeout(300); // Verify modal is visible const editModal = page.getByTestId('song-edit-modal'); await expect(editModal).toBeVisible(); // Verify there is NO explicit save button const saveButton = page.locator('button:has-text("Speichern")'); const saveButtonExists = await saveButton.isVisible().catch(() => false); expect(saveButtonExists).toBe(false); // Verify auto-save indicator exists (shows "Speichert…" or "Gespeichert") const titleInput = page.getByTestId('song-edit-modal-title-input'); const currentValue = await titleInput.inputValue(); // Type a character to trigger auto-save await titleInput.fill(currentValue + 'X'); // Wait for debounce (500ms) + save request await page.waitForTimeout(700); // Verify save indicator appears (either "Speichert…" or "Gespeichert") const savingIndicator = page.getByText(/Speichert…|Gespeichert/); const indicatorVisible = await savingIndicator.isVisible().catch(() => false); expect(indicatorVisible).toBe(true); // Restore original value (remove the 'X') await titleInput.fill(currentValue); await page.waitForTimeout(700); }); // Test 4: Arrangement configurator is embedded in modal test('arrangement configurator is embedded in 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) { test.skip(); } // Get first song row and click edit const firstRow = page.locator('tbody tr').first(); const hasEditButton = await firstRow.getByTestId('song-list-edit-button').isVisible().catch(() => false); if (!hasEditButton) { test.skip(); } const editButton = firstRow.getByTestId('song-list-edit-button'); await editButton.click(); // Wait for modal to appear await page.waitForTimeout(300); // Verify modal is visible const editModal = page.getByTestId('song-edit-modal'); await expect(editModal).toBeVisible(); // Verify arrangement configurator section is visible await expect(page.getByText('Arrangements')).toBeVisible(); // Verify arrangement configurator component is rendered const arrangementConfigurator = page.getByTestId('arrangement-configurator'); const configuratorVisible = await arrangementConfigurator.isVisible().catch(() => false); expect(configuratorVisible).toBe(true); }); // Test 5: Close modal with X button test('close modal with X button', 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) { test.skip(); } // Get first song row and click edit const firstRow = page.locator('tbody tr').first(); const hasEditButton = await firstRow.getByTestId('song-list-edit-button').isVisible().catch(() => false); if (!hasEditButton) { test.skip(); } const editButton = firstRow.getByTestId('song-list-edit-button'); await editButton.click(); // Wait for modal to appear await page.waitForTimeout(300); // Verify modal is visible const editModal = page.getByTestId('song-edit-modal'); await expect(editModal).toBeVisible(); // Click close button const closeButton = page.getByTestId('song-edit-modal-close-button'); await closeButton.click(); // Wait for modal to close await page.waitForTimeout(300); // Verify modal is gone const modalGone = await editModal.isVisible().catch(() => false); expect(modalGone).toBe(false); }); // Test 6: Close modal with overlay click test('close modal with overlay click', 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) { test.skip(); } // Get first song row and click edit const firstRow = page.locator('tbody tr').first(); const hasEditButton = await firstRow.getByTestId('song-list-edit-button').isVisible().catch(() => false); if (!hasEditButton) { test.skip(); } const editButton = firstRow.getByTestId('song-list-edit-button'); await editButton.click(); // Wait for modal to appear await page.waitForTimeout(300); // Verify modal is visible const editModal = page.getByTestId('song-edit-modal'); await expect(editModal).toBeVisible(); // Click on the overlay (outside the modal) const overlay = page.locator('div').filter({ has: editModal, }).first(); // Get the overlay's bounding box and click outside the modal const modalBox = await editModal.boundingBox(); if (modalBox) { // Click on the left side of the overlay (outside modal) await page.click('div[class*="fixed"][class*="inset-0"]', { position: { x: 10, y: 10 }, }); } // Wait for modal to close await page.waitForTimeout(300); // Verify modal is gone const modalGone = await editModal.isVisible().catch(() => false); expect(modalGone).toBe(false); });