From 0798cd845fc91fe1583490901b2849cb3c4f68b4 Mon Sep 17 00:00:00 2001 From: Richard R Date: Wed, 20 May 2026 03:44:09 -0600 Subject: [PATCH] refactor(export): extract download trigger helper and add retry logic to full download Move download logic into reusable downloadViaTrigger function. Add retries and button enabled checks to downloadFullAudiobook for improved robustness against transient UI or backend delays. --- tests/export.spec.ts | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/tests/export.spec.ts b/tests/export.spec.ts index d89a445..8d9e048 100644 --- a/tests/export.spec.ts +++ b/tests/export.spec.ts @@ -77,12 +77,14 @@ type DownloadedAudiobook = { cleanup: () => Promise; }; -async function downloadFullAudiobook(page: Page, timeoutMs = 60_000): Promise { - const fullDownloadButton = page.getByRole('button', { name: /Full Download/i }); - await expect(fullDownloadButton).toBeVisible({ timeout: timeoutMs }); +async function downloadViaTrigger( + page: Page, + trigger: () => Promise, + timeoutMs = 60_000, +): Promise { const [download] = await Promise.all([ page.waitForEvent('download', { timeout: timeoutMs }), - fullDownloadButton.click(), + trigger(), ]); const failure = await download.failure(); expect(failure).toBeNull(); @@ -119,6 +121,27 @@ async function downloadFullAudiobook(page: Page, timeoutMs = 60_000): Promise { + const fullDownloadButton = page.getByRole('button', { name: /Full Download/i }); + await expect(fullDownloadButton).toBeVisible({ timeout: timeoutMs }); + await expect(fullDownloadButton).toBeEnabled({ timeout: timeoutMs }); + + let lastError: unknown; + for (let attempt = 1; attempt <= 3; attempt += 1) { + try { + return await downloadViaTrigger(page, () => fullDownloadButton.click(), timeoutMs); + } catch (error) { + lastError = error; + if (attempt === 3) throw error; + await page.waitForTimeout(250 * attempt); + await expect(fullDownloadButton).toBeVisible({ timeout: timeoutMs }); + await expect(fullDownloadButton).toBeEnabled({ timeout: timeoutMs }); + } + } + + throw lastError instanceof Error ? lastError : new Error('Full download failed'); +} + async function getAudioDurationSeconds(filePath: string) { const { stdout } = await execFileAsync('ffprobe', [ '-v', @@ -359,6 +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); await setupTest(page, testInfo); await uploadAndDisplay(page, 'sample.pdf'); @@ -378,10 +402,9 @@ test('exports a single MP3 audiobook PDF page via chapters menu', async ({ page // Readiness gate: chapter row visibility can lead backend storage consistency by a small window. await waitForBackendDownloadReady(page, bookId, { minChapters: 1 }); - // Download via frontend button + // Download via full-download button once at least one chapter is ready. await withDownloadedFullAudiobook(page, async ({ filePath }) => { const durationSeconds = await getAudioDurationSeconds(filePath); - // For EPUB we just assert a sane non-trivial duration; at least one 10s mocked chapter. expect(durationSeconds).toBeGreaterThan(9); expect(durationSeconds).toBeLessThan(300); });