import { test, expect } from '@playwright/test'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { adminPath, apiList, apiPost, ensureDataSource, ensureMailshot, cleanupByNames } from './helpers.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const renewalsTemplate = fs.readFileSync(path.resolve(__dirname, '../../../formats/renewals_v4.html'), 'utf8'); const dsName = process.env.E2E_RENEWAL_DATASOURCE || 'renewal_accounts_with_contacts'; const mailshotPurpose = process.env.E2E_RENEWAL_MAILSHOT_PURPOSE || 'Renewals (pending accounts and contacts)'; const allowCreate = process.env.E2E_RENEWAL_ALLOW_CREATE === '1'; const runEnabled = process.env.E2E_RENEWAL_ENABLE === '1'; test.describe('renewal dataset: download pdf e2e', () => { test.skip(!runEnabled, 'Enable with E2E_RENEWAL_ENABLE=1'); const clickAndExpectDownload = async (page, buttonName, expectedFilenamePart) => { page.once('dialog', async (dialog) => { await dialog.accept(); }); const nextMatchingDownload = async () => { const deadline = Date.now() + 240000; while (Date.now() < deadline) { const remaining = Math.max(1, deadline - Date.now()); const download = await page.waitForEvent('download', { timeout: remaining }).catch(() => null); if (!download) { break; } if (download.suggestedFilename().includes(expectedFilenamePart)) { return download; } } return null; }; const downloadPromise = nextMatchingDownload(); await page.getByRole('button', { name: buttonName }).click(); let downloaded; try { downloaded = await downloadPromise; } catch { const critical = page.getByText('There has been a critical error on this website.'); if (await critical.count()) { throw new Error(`Download action "${buttonName}" reached a WordPress critical error page.`); } const failBanner = page.locator('strong', { hasText: 'PDF generation failed.' }); if (await failBanner.count()) { const msg = (await page.locator('.wrap').innerText()).replace(/\s+/g, ' ').trim(); throw new Error(`Download action "${buttonName}" failed in UI: ${msg}`); } throw new Error(`Download action "${buttonName}" timed out waiting for file download.`); } if (!downloaded) { throw new Error(`Download action "${buttonName}" did not produce expected file "${expectedFilenamePart}".`); } expect(downloaded.suggestedFilename()).toContain(expectedFilenamePart); const outPath = await downloaded.path(); expect(outPath).toBeTruthy(); const outSize = fs.statSync(outPath).size; expect(outSize).toBeGreaterThan(1000); }; const resolveMailshot = async (request) => { const uniq = `${Date.now()}_${Math.floor(Math.random() * 100000)}`; const purpose = `e2e_renewal_download_${uniq}`; const dsCreatedName = `e2e_renewal_ds_${uniq}`; let mailshotId = ''; let effectiveDsName = dsName; let createdMailshot = false; const mailshotList = await apiList(request, 'feca_mailshots_mailshots_api'); expect(mailshotList.ok).toBeTruthy(); const existing = (mailshotList.items || []).find( (row) => String(row.Purpose || '') === mailshotPurpose ); if (existing) { mailshotId = String(existing.id || ''); effectiveDsName = String(existing.DataSource || '').trim(); if (effectiveDsName === '') { throw new Error(`Mailshot "${mailshotPurpose}" has no DataSource.`); } } else { const dsList = await apiList(request, 'feca_mailshots_data_sources_api'); expect(dsList.ok).toBeTruthy(); const dsExists = (dsList.items || []).some((row) => String(row.name || '') === dsName); if (!dsExists) { if (!allowCreate) { throw new Error(`Required data source not found: ${dsName}`); } await ensureDataSource(request, dsCreatedName, dsName); effectiveDsName = dsCreatedName; } if (!allowCreate) { throw new Error(`Required mailshot not found: ${mailshotPurpose}`); } const saved = await ensureMailshot(request, { Purpose: purpose, DataSource: effectiveDsName, CC: '', BCC: '', Subject: 'Renewal for {{ account_name }}', Message: '', PDFAttachment: renewalsTemplate, AttachmentNames: '[]', PDFFilenameDerivedFrom: 'account_name', RecipientEmailField: '', ReplyTo: '' }); mailshotId = String(saved.id || ''); createdMailshot = true; } expect(mailshotId).not.toBe(''); const dsListFinal = await apiList(request, 'feca_mailshots_data_sources_api'); expect(dsListFinal.ok).toBeTruthy(); const effectiveDsExists = (dsListFinal.items || []).some((row) => String(row.name || '') === effectiveDsName); expect(effectiveDsExists).toBeTruthy(); const render = await apiPost(request, 'feca_mailshots_test_api', 'render_test', { mailshot_id: mailshotId, recipient_index: '0' }); expect(render.ok).toBeTruthy(); expect(String(render?.rendered?.pdf_attachment || '')).toContain('Membership Renewal'); return { mailshotId, cleanup: async () => { await cleanupByNames(request, { dataSourceNames: [dsCreatedName], mailshotPurposes: createdMailshot ? [purpose] : [] }); } }; }; test('downloads merged PDF for renewal_accounts_with_contacts', async ({ page, request }) => { const resolved = await resolveMailshot(request); try { await page.goto(`${adminPath('feca-mailshots-download-pdf')}&mailshot_id=${encodeURIComponent(resolved.mailshotId)}`); await expect(page.getByRole('heading', { name: 'Download PDF' })).toBeVisible(); await page.locator('#dp_mailshot_id').selectOption(resolved.mailshotId); await clickAndExpectDownload(page, 'Download Merged PDF', '_merged.pdf'); } finally { try { await resolved.cleanup(); } catch { // best effort cleanup } } }); test('downloads ZIP of PDFs for renewal_accounts_with_contacts', async ({ page, request }) => { const resolved = await resolveMailshot(request); try { await page.goto(`${adminPath('feca-mailshots-download-pdf')}&mailshot_id=${encodeURIComponent(resolved.mailshotId)}`); await expect(page.getByRole('heading', { name: 'Download PDF' })).toBeVisible(); await page.locator('#dp_mailshot_id').selectOption(resolved.mailshotId); await clickAndExpectDownload(page, 'Download ZIP of PDFs', '_pdfs.zip'); } finally { try { await resolved.cleanup(); } catch { // best effort cleanup } } }); });