import { expect, test, type Page } from '@playwright/test'; import { PDFDocument } from 'pdf-lib'; import { createEncryptedPdf, createPdf, createPreRotatedPdf, dropFiles, expectPdfDownload, getPageContentText, getPageText, pdfDropzone, trackPageErrors } from './helpers'; const downloadPaginated = async (page: Page, fileName: RegExp) => { const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: 'Paginieren & herunterladen' }).click(); return expectPdfDownload(await downloadPromise, fileName); }; test('pagination stamps every page with an Aktenzeichen and the configured start number', async ({ page }) => { const pageErrors = trackPageErrors(page); await page.goto('/tools/pagination'); await dropFiles(page, pdfDropzone(page), [ { name: 'akte.pdf', mimeType: 'application/pdf', buffer: await createPdf(3, 'Akte') } ]); await expect(page.getByText('Seite 1 von 3')).toBeVisible(); await page.locator('#pagination-start').fill('26'); await page.locator('#pagination-text').fill('123 Ja 60/26'); await expect(page.getByTestId('pagination-example')).toContainText( 'Beispiel: 123 Ja 60/26, Bl. 26' ); await page.getByRole('button', { name: 'Nächste Seite' }).click(); await expect(page.getByText('Seite 2 von 3')).toBeVisible(); await expect(page.getByTestId('pagination-preview')).toContainText('123 Ja 60/26, Bl. 27'); const bytes = await downloadPaginated(page, /^akte_paginiert\.pdf$/); const output = await PDFDocument.load(bytes); expect(output.getPageCount()).toBe(3); expect(getPageText(output, 0)).toContain('123 Ja 60/26, Bl. 26'); expect(getPageText(output, 1)).toContain('123 Ja 60/26, Bl. 27'); expect(getPageText(output, 2)).toContain('123 Ja 60/26, Bl. 28'); expect(pageErrors).toEqual([]); }); test('pagination can keep the additional text on the first page only', async ({ page }) => { const pageErrors = trackPageErrors(page); await page.goto('/tools/pagination'); await dropFiles(page, pdfDropzone(page), [ { name: 'akte.pdf', mimeType: 'application/pdf', buffer: await createPdf(3, 'Akte') } ]); await page.locator('#pagination-start').fill('7'); await page.locator('#pagination-text').fill('AZ 7/26'); await page.locator('#pagination-format').selectOption('s'); await page.getByLabel('Nur auf der ersten Seite').check(); await expect(page.getByTestId('pagination-example')).toContainText('AZ 7/26, S. 7'); await page.getByRole('button', { name: 'Nächste Seite' }).click(); await expect(page.getByTestId('pagination-preview')).toHaveText('S. 8'); const bytes = await downloadPaginated(page, /^akte_paginiert\.pdf$/); const output = await PDFDocument.load(bytes); expect(output.getPageCount()).toBe(3); expect(getPageText(output, 0)).toContain('AZ 7/26, S. 7'); expect(getPageText(output, 1)).toContain('S. 8'); expect(getPageText(output, 1)).not.toContain('AZ 7/26'); expect(getPageText(output, 2)).toContain('S. 9'); expect(pageErrors).toEqual([]); }); test('pagination supports number-only labels and jumps to a page', async ({ page }) => { const pageErrors = trackPageErrors(page); await page.goto('/tools/pagination'); await dropFiles(page, pdfDropzone(page), [ { name: 'nummern.pdf', mimeType: 'application/pdf', buffer: await createPdf(11, 'Nummern') } ]); await page.locator('#pagination-start').fill('42'); await page.locator('#pagination-format').selectOption('nummer'); await expect(page.getByTestId('pagination-example')).toHaveText('Beispiel: 42'); await page.getByLabel('Springe zu Seite').fill('11'); await expect(page.getByText('Seite 11 von 11')).toBeVisible(); await expect(page.getByTestId('pagination-preview')).toHaveText('52'); const bytes = await downloadPaginated(page, /^nummern_paginiert\.pdf$/); const output = await PDFDocument.load(bytes); expect(output.getPageCount()).toBe(11); expect(getPageText(output, 0)).toContain('42'); expect(getPageText(output, 10)).toContain('52'); expect(pageErrors).toEqual([]); }); test('pagination keeps the page number upright on rotated scans', async ({ page }) => { const pageErrors = trackPageErrors(page); await page.goto('/tools/pagination'); await dropFiles(page, pdfDropzone(page), [ { name: 'gedreht.pdf', mimeType: 'application/pdf', buffer: await createPreRotatedPdf(2, 'Gedreht') } ]); const bytes = await downloadPaginated(page, /^gedreht_paginiert\.pdf$/); const output = await PDFDocument.load(bytes); expect(output.getPageCount()).toBe(2); // Page 1 is stored with /Rotate 90: the text is counter-rotated so it reads // horizontally on screen instead of sideways. const rotatedContent = getPageContentText(output, 0); expect(getPageText(output, 0)).toContain('Bl. 1'); const rotatedMatrix = /([-\d.]+) ([-\d.]+) ([-\d.]+) ([-\d.]+) ([-\d.]+) ([-\d.]+) Tm\n<426C2E2031> Tj/.exec( rotatedContent ); expect(rotatedMatrix).not.toBeNull(); const [a, b, c, d, x, y] = rotatedMatrix!.slice(1).map(Number); // 90° counter-clockwise in user space cancels the viewer's page rotation. expect(Math.abs(a)).toBeLessThan(0.001); expect(b).toBeCloseTo(1, 3); expect(c).toBeCloseTo(-1, 3); expect(Math.abs(d)).toBeLessThan(0.001); // Visual bottom center of the 594×420 display: 28 pt margin, centered. expect(x).toBeGreaterThan(384); expect(x).toBeLessThan(396); expect(y).toBeGreaterThan(280); expect(y).toBeLessThan(292); const plainContent = getPageContentText(output, 1); expect(getPageText(output, 1)).toContain('Bl. 2'); expect(plainContent).toMatch(/1 0 0 1 [-\d.]+ [-\d.]+ Tm\n<426C2E2032> Tj/); expect(pageErrors).toEqual([]); }); test('pagination explains how to handle a password-protected PDF', async ({ page }) => { const pageErrors = trackPageErrors(page); await page.goto('/tools/pagination'); await dropFiles(page, pdfDropzone(page), [ { name: 'geschuetzt.pdf', mimeType: 'application/pdf', buffer: await createEncryptedPdf(1, 'Geschützt', 'test-passwort') } ]); await expect(page.getByRole('alert')).toContainText('passwortgeschützt'); await expect(page.getByRole('alert')).toContainText('Passwort entfernen'); expect(pageErrors).toEqual([]); }); test('merge hands the merged PDF over to pagination', async ({ page }) => { const pageErrors = trackPageErrors(page); await page.goto('/tools/merge'); await dropFiles(page, pdfDropzone(page), [ { name: 'eins.pdf', mimeType: 'application/pdf', buffer: await createPdf(1, 'Eins') }, { name: 'zwei.pdf', mimeType: 'application/pdf', buffer: await createPdf(2, 'Zwei') } ]); await page.getByRole('button', { name: 'Zusammenfügen & paginieren' }).click(); await expect(page).toHaveURL(/\/tools\/pagination$/); await expect(page.getByText('Seite 1 von 3')).toBeVisible({ timeout: 15_000 }); const bytes = await downloadPaginated(page, /^zusammengefuegt_paginiert\.pdf$/); const output = await PDFDocument.load(bytes); expect(output.getPageCount()).toBe(3); expect(getPageText(output, 0)).toContain('Bl. 1'); expect(getPageText(output, 1)).toContain('Bl. 2'); expect(getPageText(output, 2)).toContain('Bl. 3'); expect(pageErrors).toEqual([]); });