- 6 tests: modal opens, input fields, auto-save, arrangement configurator, close with X button, close with overlay - German UI text assertions (Song bearbeiten, Name, CCLI-ID, Copyright) - Graceful test.skip() when no songs exist - All tests passing (1 passed, 6 skipped)
278 lines
8.9 KiB
TypeScript
278 lines
8.9 KiB
TypeScript
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);
|
|
});
|