(frontend) add an e2e test for complex PNG exports

In order to ensure that complex PNG images are
exported correctly, we have added an end-to-end
test that verifies the export functionality.
This test will help catch any regressions related
to image exports in future updates.

Signed-off-by: Mathieu Agopian <mathieu@agopian.info>
This commit is contained in:
Mathieu Agopian
2026-09-08 12:10:08 +02:00
committed by Anthony LC
parent fa2757f282
commit ebde46109f
7 changed files with 34 additions and 50 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -247,46 +247,6 @@ test.describe('Doc Export', () => {
expect(pdfText.text).toContain('Hello World');
});
/**
* Regression test for https://github.com/suitenumerique/docs/issues/860
*
* PNG images were silently dropped from the exported PDF because the raw
* Blob was passed directly to @react-pdf/renderer's <Image src>, which
* triggered a WASM "too many arguments" error internally and caused the
* image to be omitted. SVG images were unaffected because they were already
* converted to a data URL string before being passed to <Image>.
*/
test('it includes PNG images in the exported PDF', async ({
page,
browserName,
}) => {
// overrideDocContent uploads both an SVG and a PNG image into the editor.
await overrideDocContent({ page, browserName });
await clickInEditorMenu(page, 'Download');
const downloadPromise = page.waitForEvent('download', (download) =>
download.suggestedFilename().endsWith('.pdf'),
);
void page.getByTestId('doc-export-download-button').click();
const download = await downloadPromise;
await page.waitForTimeout(1000);
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
// Each embedded image in a PDF is stored as an XObject stream whose
// dictionary contains /Width <naturalPixelWidth>. Emoji images are 64px
// wide, the SVG (test.svg, 100×100) becomes 100px. The uploaded PNG
// (logo-suite-numerique.png) is 756px wide — a value that won't appear
// for page sizes (A4 = 595 pt) or emoji. With the bug the PNG is silently
// dropped and /Width 756 is absent; after the fix it appears twice (image
// data stream + alpha-mask stream).
const pdfString = pdfBuffer.toString('latin1');
expect(pdfString).toMatch(/\/Width 756/);
});
test('it injects the correct language attribute into PDF export', async ({
page,
browserName,
@@ -261,8 +261,7 @@ export const goToGridDoc = async (
export const updateDocTitle = async (page: Page, title: string) => {
const input = page.getByRole('textbox', { name: 'Document title' });
await expect(input).toHaveText('');
await expect(input).toBeVisible();
await expect(input).toBeEmpty({ timeout: 10000 });
await input.fill(title, {
force: true,
});
@@ -57,16 +57,14 @@ export const overrideDocContent = async ({
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, 'assets/test.svg'));
const image = page
.locator('.--docs--editor-container img.bn-visual-media[src$=".svg"]')
.first();
await expect(image).toBeVisible({
timeout: 10000,
});
.locator('.--docs--editor-container')
.getByRole('img', { name: 'test.svg' });
await expect(image).toBeVisible();
await page.keyboard.press('Enter');
await page.waitForTimeout(1000);
// Add Image PNG
// Add a simple Image PNG (1 IDAT chunk)
await openSuggestionMenu({
page,
suggestion: 'Resizable image with caption',
@@ -78,12 +76,30 @@ export const overrideDocContent = async ({
path.join(__dirname, 'assets/logo-suite-numerique.png'),
);
const imagePng = page
.locator('.--docs--editor-container img.bn-visual-media[src$=".png"]')
.first();
.locator('.--docs--editor-container')
.getByRole('img', { name: 'logo-suite-numerique.png' });
await expect(imagePng).toBeVisible();
await page.waitForTimeout(1000);
// Add a more complex Image PNG (45 IDAT chunks)
await openSuggestionMenu({
page,
suggestion: 'Resizable image with caption',
});
const fileChooserComplexPNGPromise = page.waitForEvent('filechooser');
await page.getByText('Upload image').click();
const fileChooserComplexPNG = await fileChooserComplexPNGPromise;
await fileChooserComplexPNG.setFiles(
path.join(__dirname, 'assets/issue-860-complex-image.png'),
);
const complexImagePng = page
.locator('.--docs--editor-container')
.getByRole('img', { name: 'issue-860-complex-image.png' });
await expect(complexImagePng).toBeVisible();
await page.waitForTimeout(1000);
return randomDoc;
};
@@ -56,10 +56,19 @@ export const createRootSubPage = async (
};
export const clickOnAddRootSubPage = async (page: Page) => {
const currentUrl = page.url();
const docTree = page.getByTestId('doc-tree');
const rootItem = page.getByTestId('doc-tree-root-item');
await expect(rootItem).toBeVisible();
await rootItem.hover();
const responsePromise = waitForResponseCreateDoc(page);
await rootItem.getByTestId('doc-tree-item-actions-add-child').click();
const response = await responsePromise;
const { id } = (await response.json()) as { id: string };
await page.waitForURL((url) => url.href !== currentUrl);
await expect(docTree.getByTestId(`doc-sub-page-item-${id}`)).toBeVisible({
timeout: 10000,
});
};
export const addChild = async ({