195 lines
8.6 KiB
TypeScript
195 lines
8.6 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import { unzipSync } from 'fflate';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { PDFDocument } from 'pdf-lib';
|
|
import {
|
|
createEncryptedPdf,
|
|
createPdf,
|
|
createStampPng,
|
|
dropFiles,
|
|
expectPdfDownload,
|
|
getPageContentStreamCount,
|
|
getPageContentText,
|
|
pdfDropzone,
|
|
trackPageErrors
|
|
} from './helpers';
|
|
|
|
test('stamp presets fill the editable text and EILT is applied to every page', async ({ page }) => {
|
|
const pageErrors = trackPageErrors(page);
|
|
await page.goto('/tools/stamp');
|
|
await dropFiles(page, pdfDropzone(page), [
|
|
{ name: 'akte.pdf', mimeType: 'application/pdf', buffer: await createPdf(3, 'Akte') }
|
|
]);
|
|
|
|
const stampInput = page.locator('#stamp-text');
|
|
for (const preset of ['BEGLAUBIGT', 'AUSFERTIGUNG', 'KOPIE', 'EILT', 'ENTWURF', 'ERLEDIGT']) {
|
|
await page.getByRole('button', { name: preset, exact: true }).click();
|
|
await expect(stampInput).toHaveValue(preset);
|
|
}
|
|
await page.getByRole('button', { name: 'EILT', exact: true }).click();
|
|
await page.locator('#stamp-rotation').fill('20');
|
|
await expect(page.getByTestId('stamp-preview')).toContainText('EILT');
|
|
await expect(page.getByTestId('stamp-preview')).not.toHaveCSS('transform', 'none');
|
|
|
|
await page.getByRole('button', { name: 'Stempel anwenden', exact: true }).click();
|
|
await expect(page.getByText('Fertig ✓')).toBeVisible({ timeout: 20_000 });
|
|
const downloadPromise = page.waitForEvent('download');
|
|
await page.getByRole('button', { name: 'akte.pdf herunterladen' }).click();
|
|
const outputBytes = await expectPdfDownload(await downloadPromise, /^akte_stempel\.pdf$/);
|
|
const output = await PDFDocument.load(outputBytes);
|
|
expect(output.getPageCount()).toBe(3);
|
|
for (let pageIndex = 0; pageIndex < output.getPageCount(); pageIndex += 1) {
|
|
expect(getPageContentStreamCount(output, pageIndex)).toBeGreaterThan(1);
|
|
}
|
|
expect(pageErrors).toEqual([]);
|
|
});
|
|
|
|
test('stamp custom text adds a German date and only changes selected pages', async ({ page }) => {
|
|
const pageErrors = trackPageErrors(page);
|
|
const inputBytes = await createPdf(3, 'Auswahl');
|
|
const input = await PDFDocument.load(inputBytes);
|
|
const originalStreamCounts = input
|
|
.getPages()
|
|
.map((_, pageIndex) => getPageContentStreamCount(input, pageIndex));
|
|
|
|
await page.goto('/tools/stamp');
|
|
await dropFiles(page, pdfDropzone(page), [
|
|
{ name: 'auswahl.pdf', mimeType: 'application/pdf', buffer: inputBytes }
|
|
]);
|
|
await page.locator('#stamp-text').fill('nur hier');
|
|
await page.getByRole('checkbox', { name: 'Datum anhängen' }).check();
|
|
await expect(page.getByTestId('stamp-preview')).toHaveText(/NUR HIER \d{2}\.\d{2}\.\d{4}/);
|
|
await page.getByRole('checkbox', { name: 'Auf alle Seiten anwenden' }).uncheck();
|
|
await page.getByRole('button', { name: /Seite 2/ }).click();
|
|
|
|
await page.getByRole('button', { name: 'Stempel anwenden', exact: true }).click();
|
|
await expect(page.getByText('Fertig ✓')).toBeVisible({ timeout: 20_000 });
|
|
const downloadPromise = page.waitForEvent('download');
|
|
await page.getByRole('button', { name: 'auswahl.pdf herunterladen' }).click();
|
|
const outputBytes = await expectPdfDownload(await downloadPromise, /^auswahl_stempel\.pdf$/);
|
|
const output = await PDFDocument.load(outputBytes);
|
|
expect(output.getPageCount()).toBe(3);
|
|
expect(getPageContentStreamCount(output, 0)).toBe(originalStreamCounts[0]);
|
|
expect(getPageContentStreamCount(output, 1)).toBeGreaterThan(originalStreamCounts[1]);
|
|
expect(getPageContentStreamCount(output, 2)).toBe(originalStreamCounts[2]);
|
|
expect(pageErrors).toEqual([]);
|
|
});
|
|
|
|
test('stamp uses an image instead of text and makes the border optional', async ({ page }) => {
|
|
const pageErrors = trackPageErrors(page);
|
|
await page.goto('/tools/stamp');
|
|
await dropFiles(page, pdfDropzone(page), [
|
|
{ name: 'bildakte.pdf', mimeType: 'application/pdf', buffer: await createPdf(2, 'Bild') }
|
|
]);
|
|
await page.locator('#stamp-image').setInputFiles({
|
|
name: 'stempel.png',
|
|
mimeType: 'image/png',
|
|
buffer: Buffer.from(await createStampPng(page), 'base64')
|
|
});
|
|
|
|
await expect(page.getByText('stempel.png')).toBeVisible();
|
|
await expect(page.getByTestId('stamp-preview').locator('img')).toBeVisible();
|
|
await expect(page.locator('#stamp-text')).toHaveCount(0);
|
|
await expect(page.getByRole('checkbox', { name: 'Rahmen um das Bild anzeigen' })).toBeVisible();
|
|
await expect(
|
|
page.getByRole('checkbox', { name: 'Rahmen um das Bild anzeigen' })
|
|
).not.toBeChecked();
|
|
|
|
await page.getByRole('button', { name: 'Stempel anwenden', exact: true }).click();
|
|
await expect(page.getByText('Fertig ✓')).toBeVisible({ timeout: 20_000 });
|
|
const downloadPromise = page.waitForEvent('download');
|
|
await page.getByRole('button', { name: 'bildakte.pdf herunterladen' }).click();
|
|
const outputBytes = await expectPdfDownload(await downloadPromise, /^bildakte_stempel\.pdf$/);
|
|
const output = await PDFDocument.load(outputBytes);
|
|
expect(output.getPageCount()).toBe(2);
|
|
// Image without frame: the image XObject is drawn, no stroked rectangle is present.
|
|
for (const pageIndex of [0, 1]) {
|
|
const content = getPageContentText(output, pageIndex);
|
|
expect(content).toContain(' Do\n');
|
|
expect(content).not.toContain(' RG');
|
|
}
|
|
|
|
await page.getByRole('checkbox', { name: 'Rahmen um das Bild anzeigen' }).check();
|
|
await expect(page.getByTestId('stamp-preview')).toHaveCSS('border-style', 'solid');
|
|
|
|
// Re-add the file so the changed option applies to a fresh pending row.
|
|
await page.getByRole('button', { name: 'bildakte.pdf entfernen' }).click();
|
|
await dropFiles(page, pdfDropzone(page), [
|
|
{ name: 'bildakte.pdf', mimeType: 'application/pdf', buffer: await createPdf(2, 'Bild') }
|
|
]);
|
|
await expect(page.getByTestId('stamp-preview').locator('img')).toBeVisible();
|
|
|
|
await page.getByRole('button', { name: 'Stempel anwenden', exact: true }).click();
|
|
await expect(page.getByText('Fertig ✓')).toBeVisible({ timeout: 20_000 });
|
|
const borderedDownloadPromise = page.waitForEvent('download');
|
|
await page.getByRole('button', { name: 'bildakte.pdf herunterladen' }).click();
|
|
const borderedBytes = await expectPdfDownload(
|
|
await borderedDownloadPromise,
|
|
/^bildakte_stempel\.pdf$/
|
|
);
|
|
const bordered = await PDFDocument.load(borderedBytes);
|
|
// The optional frame adds a stroked rectangle around the drawn image.
|
|
for (const pageIndex of [0, 1]) {
|
|
const content = getPageContentText(bordered, pageIndex);
|
|
expect(content).toContain(' Do\n');
|
|
expect(content).toContain(' RG');
|
|
expect(content).toMatch(/\bh\nS\n/);
|
|
}
|
|
expect(pageErrors).toEqual([]);
|
|
});
|
|
|
|
test('stamp batch applies to every file and bundles results in one ZIP', async ({ page }) => {
|
|
const pageErrors = trackPageErrors(page);
|
|
await page.goto('/tools/stamp');
|
|
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') },
|
|
{
|
|
name: 'kaputt.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: Buffer.from('kein gueltiges PDF')
|
|
},
|
|
{
|
|
name: 'geschuetzt.pdf',
|
|
mimeType: 'application/pdf',
|
|
buffer: await createEncryptedPdf(1, 'Geschützt', 'test-passwort')
|
|
}
|
|
]);
|
|
|
|
await expect(page.getByText('Dateien (4)')).toBeVisible();
|
|
// With several files the stamp is applied to every page and no preview is loaded.
|
|
await expect(page.getByRole('checkbox', { name: 'Auf alle Seiten anwenden' })).toHaveCount(0);
|
|
await expect(page.getByRole('heading', { name: 'Vorschau' })).toHaveCount(0);
|
|
|
|
await page.getByRole('button', { name: 'Stempel anwenden', exact: true }).click();
|
|
await expect(page.getByText('Fertig ✓')).toHaveCount(2, { timeout: 30_000 });
|
|
await expect(page.getByText('Fehler')).toHaveCount(2);
|
|
await expect(page.getByText('Dieses PDF ist passwortgeschützt')).toBeVisible();
|
|
|
|
const downloadPromise = page.waitForEvent('download');
|
|
await page.getByRole('button', { name: 'Alle herunterladen' }).click();
|
|
const download = await downloadPromise;
|
|
expect(download.suggestedFilename()).toMatch(/^stempel_\d{4}-\d{2}-\d{2}\.zip$/);
|
|
const zipPath = await download.path();
|
|
expect(zipPath).not.toBeNull();
|
|
const entries = Object.keys(unzipSync(new Uint8Array(await readFile(zipPath!))));
|
|
expect(entries.sort()).toEqual(['eins_stempel.pdf', 'zwei_stempel.pdf']);
|
|
expect(pageErrors).toEqual([]);
|
|
});
|
|
|
|
test('stamp explains how to handle a password-protected PDF', async ({ page }) => {
|
|
const pageErrors = trackPageErrors(page);
|
|
await page.goto('/tools/stamp');
|
|
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([]);
|
|
});
|