import { test, expect } from '@playwright/test'; // Test 1: Preview button opens SongPreviewModal test('preview button opens SongPreviewModal', 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(); } // Expand Songs block if collapsed const songsBlock = page.getByTestId('songs-block'); const isHidden = await songsBlock.isHidden().catch(() => true); if (isHidden) { await songsToggle.click(); await page.waitForTimeout(300); } // Find first matched song with preview button const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } // Find first song with preview button (matched songs only) let previewButton = null; for (let i = 0; i < songCount; i++) { const card = songCards.nth(i); const button = card.getByTestId('songs-block-preview-button'); const isVisible = await button.isVisible().catch(() => false); if (isVisible) { previewButton = button; break; } } if (!previewButton) { test.skip(); } // Click preview button await previewButton.click(); await page.waitForTimeout(300); // Wait for modal transition // Verify modal is visible const modal = page.getByTestId('song-preview-modal'); await expect(modal).toBeVisible(); // Verify modal has header with song title and arrangement name const modalHeader = modal.locator('h2').first(); await expect(modalHeader).toBeVisible(); // Verify close button is visible const closeButton = page.getByTestId('song-preview-modal-close-button'); await expect(closeButton).toBeVisible(); }); // Test 2: Modal shows song text organized by groups with highlighted group labels test('modal shows groups with labels and slides', 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'); // Expand Songs 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(); } const songsBlock = page.getByTestId('songs-block'); const isHidden = await songsBlock.isHidden().catch(() => true); if (isHidden) { await songsToggle.click(); await page.waitForTimeout(300); } // Find first matched song with preview button const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } let previewButton = null; for (let i = 0; i < songCount; i++) { const card = songCards.nth(i); const button = card.getByTestId('songs-block-preview-button'); const isVisible = await button.isVisible().catch(() => false); if (isVisible) { previewButton = button; break; } } if (!previewButton) { test.skip(); } // Click preview button await previewButton.click(); await page.waitForTimeout(300); // Verify modal is visible const modal = page.getByTestId('song-preview-modal'); await expect(modal).toBeVisible(); // Verify modal content area exists const contentArea = modal.locator('div').filter({ has: page.locator('text=/^[A-Z].*$/') }).first(); await expect(contentArea).toBeVisible(); // Verify modal has text content (groups and slides) const modalText = await modal.textContent(); expect(modalText).toBeTruthy(); expect(modalText?.length).toBeGreaterThan(0); }); // Test 3: Close modal with X button test('close modal with X button', 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'); // Expand Songs 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(); } const songsBlock = page.getByTestId('songs-block'); const isHidden = await songsBlock.isHidden().catch(() => true); if (isHidden) { await songsToggle.click(); await page.waitForTimeout(300); } // Find first matched song with preview button const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } let previewButton = null; for (let i = 0; i < songCount; i++) { const card = songCards.nth(i); const button = card.getByTestId('songs-block-preview-button'); const isVisible = await button.isVisible().catch(() => false); if (isVisible) { previewButton = button; break; } } if (!previewButton) { test.skip(); } // Click preview button await previewButton.click(); await page.waitForTimeout(300); // Verify modal is visible const modal = page.getByTestId('song-preview-modal'); await expect(modal).toBeVisible(); // Click close button const closeButton = page.getByTestId('song-preview-modal-close-button'); await closeButton.click(); await page.waitForTimeout(300); // Verify modal is hidden const isModalHidden = await modal.isHidden().catch(() => true); expect(isModalHidden).toBe(true); }); // Test 4: Close modal with ESC key test('close modal with ESC key', 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'); // Expand Songs 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(); } const songsBlock = page.getByTestId('songs-block'); const isHidden = await songsBlock.isHidden().catch(() => true); if (isHidden) { await songsToggle.click(); await page.waitForTimeout(300); } // Find first matched song with preview button const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } let previewButton = null; for (let i = 0; i < songCount; i++) { const card = songCards.nth(i); const button = card.getByTestId('songs-block-preview-button'); const isVisible = await button.isVisible().catch(() => false); if (isVisible) { previewButton = button; break; } } if (!previewButton) { test.skip(); } // Click preview button await previewButton.click(); await page.waitForTimeout(300); // Verify modal is visible const modal = page.getByTestId('song-preview-modal'); await expect(modal).toBeVisible(); // Press ESC key await page.press('body', 'Escape'); await page.waitForTimeout(300); // Verify modal is hidden const isModalHidden = await modal.isHidden().catch(() => true); expect(isModalHidden).toBe(true); }); // Test 5: PDF download button triggers download with PDF content-type test('PDF download button triggers download with PDF content-type', async ({ page, context }) => { 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'); // Expand Songs 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(); } const songsBlock = page.getByTestId('songs-block'); const isHidden = await songsBlock.isHidden().catch(() => true); if (isHidden) { await songsToggle.click(); await page.waitForTimeout(300); } // Find first matched song with preview button const songCards = page.getByTestId('songs-block-song-card'); const songCount = await songCards.count(); if (songCount === 0) { test.skip(); } let previewButton = null; for (let i = 0; i < songCount; i++) { const card = songCards.nth(i); const button = card.getByTestId('songs-block-preview-button'); const isVisible = await button.isVisible().catch(() => false); if (isVisible) { previewButton = button; break; } } if (!previewButton) { test.skip(); } // Click preview button await previewButton.click(); await page.waitForTimeout(300); // Verify modal is visible const modal = page.getByTestId('song-preview-modal'); await expect(modal).toBeVisible(); // Get PDF link and verify it exists const pdfLink = page.getByTestId('song-preview-modal-pdf-link'); await expect(pdfLink).toBeVisible(); // Verify PDF link has href attribute const href = await pdfLink.getAttribute('href'); expect(href).toBeTruthy(); expect(href).toMatch(/\/songs\/\d+\/arrangements\/\d+\/pdf/); // Listen for response to PDF download const downloadPromise = context.waitForEvent('page'); // Click PDF link (opens in new tab) await pdfLink.click(); // Wait for new page/tab const newPage = await downloadPromise; await newPage.waitForLoadState('networkidle'); // Verify response has PDF content-type const requests = await newPage.context().storageState(); // Alternative: Check the response headers by intercepting let pdfContentTypeFound = false; // Listen to all responses on the new page newPage.on('response', (response) => { const contentType = response.headers()['content-type']; if (contentType && contentType.includes('application/pdf')) { pdfContentTypeFound = true; } }); // Navigate to PDF URL directly to verify content-type const pdfUrl = href; const response = await page.request.get(pdfUrl); expect(response.status()).toBe(200); const contentType = response.headers()['content-type']; expect(contentType).toContain('application/pdf'); await newPage.close(); });