diff --git a/src/app/(app)/pdf/[id]/page.tsx b/src/app/(app)/pdf/[id]/page.tsx index 631af46..30a6a6f 100644 --- a/src/app/(app)/pdf/[id]/page.tsx +++ b/src/app/(app)/pdf/[id]/page.tsx @@ -443,7 +443,7 @@ export default function PDFViewerPage() { ) : null} {isLoading || !isParseReady || !isPdfViewerReady ? ( -
+
{renderPdfStatusLoader()}
) : null} diff --git a/tests/accessibility.spec.ts b/tests/accessibility.spec.ts index f3e4504..21337f2 100644 --- a/tests/accessibility.spec.ts +++ b/tests/accessibility.spec.ts @@ -53,6 +53,7 @@ test.describe('Accessibility smoke', () => { }); test('TTS controls expose aria labels and are keyboard focusable', async ({ page }) => { + test.setTimeout(120_000); await uploadAndDisplay(page, 'sample.pdf'); // TTS bar present diff --git a/tests/export.spec.ts b/tests/export.spec.ts index 00e6153..ce64437 100644 --- a/tests/export.spec.ts +++ b/tests/export.spec.ts @@ -271,7 +271,7 @@ async function resetAudiobookIfPresent(page: Page, bookId?: string) { } test('exports full MP3 audiobook for PDF using mocked 10s TTS sample', async ({ page }, testInfo) => { - test.setTimeout(60_000); + test.setTimeout(120_000); // Ensure TTS is mocked and app is ready await setupTest(page, testInfo); @@ -382,7 +382,7 @@ test('exports partial MP3 audiobook for EPUB using mocked 10s TTS sample', async }); test('exports a single MP3 audiobook PDF page via chapters menu', async ({ page }, testInfo) => { - test.setTimeout(60_000); + test.setTimeout(120_000); await setupTest(page, testInfo); await uploadAndDisplay(page, 'sample.pdf'); @@ -413,6 +413,7 @@ test('exports a single MP3 audiobook PDF page via chapters menu', async ({ page }); test('resets all MP3 audiobook PDF pages', async ({ page }, testInfo) => { + test.setTimeout(120_000); await setupTest(page, testInfo); await uploadAndDisplay(page, 'sample.pdf'); @@ -451,7 +452,7 @@ test('resets all MP3 audiobook PDF pages', async ({ page }, testInfo) => { }); test('regenerates a single MP3 audiobook PDF page and exports full audiobook', async ({ page }, testInfo) => { - test.setTimeout(60_000); + test.setTimeout(120_000); await setupTest(page, testInfo); await uploadAndDisplay(page, 'sample.pdf'); @@ -525,7 +526,7 @@ test('regenerates a single MP3 audiobook PDF page and exports full audiobook', a }); test('resumes audiobook when a chapter is missing and full download succeeds (PDF)', async ({ page }, testInfo) => { - test.setTimeout(60_000); + test.setTimeout(120_000); await setupTest(page, testInfo); await uploadAndDisplay(page, 'sample.pdf'); diff --git a/tests/helpers.ts b/tests/helpers.ts index c5b6da5..500b3dc 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -24,17 +24,23 @@ function sha256HexOfFile(filePath: string) { async function waitForPdfViewerReady(page: Page, timeout = 60000) { await expect(page).toHaveURL(/\/pdf\/[A-Za-z0-9._%-]+$/, { timeout: Math.min(timeout, 20000) }); + const loader = page.getByTestId('pdf-status-loader').first(); + const parseFailedBanner = page.getByText('PDF parsing failed. Retry to continue.').first(); await expect .poll(async () => { + const parseFailed = await parseFailedBanner.isVisible().catch(() => false); + if (parseFailed) return 'failed'; + const documentVisible = await page.locator('.react-pdf__Document').first().isVisible().catch(() => false); - if (documentVisible) return true; const pageVisible = await page.locator('.react-pdf__Page').first().isVisible().catch(() => false); - if (pageVisible) return true; const canvasVisible = await page.locator('.react-pdf__Page canvas').first().isVisible().catch(() => false); - if (canvasVisible) return true; - return false; + + const hasRenderedPdf = documentVisible || pageVisible || canvasVisible; + const loaderVisible = await loader.isVisible().catch(() => false); + if (hasRenderedPdf && !loaderVisible) return 'ready'; + return 'loading'; }, { timeout }) - .toBe(true); + .toBe('ready'); } /** @@ -396,7 +402,7 @@ export async function expectViewerForFile(page: Page, fileName: string) { if (lower.endsWith('.pdf') || lower.endsWith('.docx')) { // DOCX converts to PDF, so viewer expectations are PDF await expect(page).toHaveURL(/\/pdf\/[A-Za-z0-9._%-]+$/); - await expect(page.locator('.react-pdf__Document')).toBeVisible({ timeout: 15000 }); + await waitForPdfViewerReady(page, 60000); return; } if (lower.endsWith('.epub')) { diff --git a/tests/landing-routing.spec.ts b/tests/landing-routing.spec.ts index f9b4bb4..ca9347c 100644 --- a/tests/landing-routing.spec.ts +++ b/tests/landing-routing.spec.ts @@ -37,6 +37,7 @@ test.describe('Landing and app routing', () => { }); test('documents back link returns to /app', async ({ page }, testInfo) => { + test.setTimeout(120_000); await setupTest(page, testInfo); await uploadAndDisplay(page, 'sample.pdf'); diff --git a/tests/navigation.spec.ts b/tests/navigation.spec.ts index 173a0c1..b642b20 100644 --- a/tests/navigation.spec.ts +++ b/tests/navigation.spec.ts @@ -37,6 +37,7 @@ test.describe('Document link navigation by type', () => { }); test('navigates to /pdf, /epub, /html and renders correct viewers', async ({ page }) => { + test.setTimeout(120_000); // Upload documents await uploadFiles(page, 'sample.pdf', 'sample.epub', 'sample.txt'); @@ -65,10 +66,9 @@ test.describe('PDF view modes and Navigator', () => { }); test('switches Single/Dual/Scroll modes and uses Navigator to change page', async ({ page }) => { - test.setTimeout(60_000); + test.setTimeout(120_000); // Open PDF viewer await uploadAndDisplay(page, 'sample.pdf'); - await expect(page.locator('.react-pdf__Document')).toBeVisible({ timeout: 15000 }); // Open document settings (page-level settings) await page.getByRole('button', { name: 'Open settings' }).click(); diff --git a/tests/play.spec.ts b/tests/play.spec.ts index e9a6b3a..aeaaf68 100644 --- a/tests/play.spec.ts +++ b/tests/play.spec.ts @@ -45,6 +45,7 @@ test.describe('Play/Pause Tests', () => { test.describe.configure({ mode: 'serial', timeout: 60000 }); test('plays and pauses TTS for a PDF document', async ({ page }) => { + test.setTimeout(120_000); // Play TTS for the PDF document await playTTSAndWaitForASecond(page, 'sample.pdf'); @@ -61,6 +62,7 @@ test.describe('Play/Pause Tests', () => { }); test('plays and pauses TTS for an DOCX document', async ({ page }) => { + test.setTimeout(120_000); // Play TTS for the DOCX document await playTTSAndWaitForASecond(page, 'sample.docx'); @@ -77,6 +79,7 @@ test.describe('Play/Pause Tests', () => { }); test('switches to a single voice and resumes playing', async ({ page }) => { + test.setTimeout(120_000); // Start playback await playTTSAndWaitForASecond(page, 'sample.pdf'); @@ -96,6 +99,7 @@ test.describe('Play/Pause Tests', () => { }); test('keeps selected single voice instead of resetting to first option', async ({ page }) => { + test.setTimeout(120_000); await playTTSAndWaitForASecond(page, 'sample.pdf'); await openVoicesMenu(page); @@ -122,6 +126,7 @@ test.describe('Play/Pause Tests', () => { }); if (!process.env.CI) test('selects multiple Kokoro voices and resumes playing', async ({ page }) => { + test.setTimeout(120_000); // Start playback await playTTSAndWaitForASecond(page, 'sample.pdf'); @@ -142,6 +147,7 @@ test.describe('Play/Pause Tests', () => { }); test('changing TTS native speed toggles processing and returns to playing', async ({ page }) => { + test.setTimeout(120_000); await playTTSAndWaitForASecond(page, 'sample.pdf'); await changeNativeSpeedAndAssert(page, 1.5); await expectMediaState(page, 'playing'); diff --git a/tests/upload.spec.ts b/tests/upload.spec.ts index c43c7aa..7c66b73 100644 --- a/tests/upload.spec.ts +++ b/tests/upload.spec.ts @@ -88,10 +88,10 @@ test.describe('Document Upload Tests', () => { }); test('displays a PDF document', async ({ page }) => { + test.setTimeout(120_000); await uploadAndDisplay(page, 'sample.pdf'); await expectViewerForFile(page, 'sample.pdf'); // Additional content checks specific to the sample PDF - await expect(page.locator('.react-pdf__Page')).toBeVisible(); await expect(page.getByRole('heading', { level: 1, name: 'sample.pdf' })).toBeVisible(); await expect(page.getByRole('button', { name: /1\s*\/\s*2/ })).toBeVisible(); }); @@ -105,10 +105,10 @@ test.describe('Document Upload Tests', () => { }); test('displays a DOCX document as PDF after conversion', async ({ page }) => { + test.setTimeout(120_000); await uploadAndDisplay(page, 'sample.docx'); await expectViewerForFile(page, 'sample.docx'); // DOCX converts to PDF // Keep specific content checks - await expect(page.locator('.react-pdf__Page')).toBeVisible(); await expect(page.getByText('Demonstration of DOCX')).toBeVisible(); }); @@ -119,6 +119,7 @@ test.describe('Document Upload Tests', () => { }); test('uploads PDF/EPUB/TXT and opens correct viewer for each', async ({ page }) => { + test.setTimeout(120_000); // Upload multiple files await uploadFiles(page, 'sample.pdf', 'sample.epub', 'sample.txt');