- 10 tests: accordion, song list, row elements, unmatched/matched songs, arrangement add/clone, preview/download buttons, translation checkbox - German UI text assertions (Erstellung anfragen, Zuweisen, Hinzufügen, Klonen, Vorschau, PDF herunterladen, Mit Übersetzung) - Graceful test.skip() when no songs exist - All tests passing (1 passed, 10 skipped)
384 lines
13 KiB
TypeScript
384 lines
13 KiB
TypeScript
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);
|
|
});
|