import { INITIAL_INVOICE_DATA } from "@/app/constants"; import { INVOICE_PDF_TRANSLATIONS } from "@/app/(app)/pdf-i18n-translations/pdf-translations"; import fs from "node:fs"; import path from "node:path"; import { uploadLogoFile } from "../stripe-invoice-template/utils"; // IMPORTANT: we use custom extended test fixture that provides a temporary download directory for each test import { test, expect } from "../utils/extended-playwright-test"; import { renderPdfOnCanvas, renderMultiPagePdfOnCanvas, } from "../utils/render-pdf-on-canvas"; test.describe("Default Invoice Template", () => { test.beforeEach(async ({ page }) => { // we set the system time to a fixed date, so that the invoice number and other dates are consistent across tests await page.clock.setSystemTime(new Date("2025-12-17T00:00:00Z")); await page.goto("/?template=default"); await expect(page).toHaveURL("/?template=default"); }); test("downloads PDF in English and verifies content", async ({ page, browserName, downloadDir, }) => { const downloadPdfEnglishButton = page.getByRole("link", { name: "Download PDF in English", }); // Wait for download button to be visible and enabled await expect(downloadPdfEnglishButton).toBeVisible(); await expect(downloadPdfEnglishButton).toBeEnabled(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfEnglishButton.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * Render the PDF on a canvas and take a screenshot of it */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "downloads-PDF-in-English.png", ); // navigate back to the previous page await page.goto("/"); await expect(page).toHaveURL("/?template=default"); /** * Switch to Stripe template and download PDF in English with Stripe template */ await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); await page.waitForURL("/?template=stripe"); // Verify that the Stripe template is selected const templateSelect = page.getByRole("combobox", { name: "Invoice Template", }); await expect(templateSelect).toHaveValue("stripe"); // Wait for the download button to be visible and enabled for Stripe template const downloadPdfStripeButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPdfStripeButton).toBeVisible(); await expect(downloadPdfStripeButton).toBeEnabled(); // Click the download button and wait for download const [stripeDownload] = await Promise.all([ page.waitForEvent("download"), downloadPdfStripeButton.click(), ]); // Get the suggested filename const stripeSuggestedFilename = stripeDownload.suggestedFilename(); // save the file to temporary directory const stripePdfFilePath = path.join( downloadDir, `${browserName}-stripe-${stripeSuggestedFilename}`, ); await stripeDownload.saveAs(stripePdfFilePath); // Convert to absolute path and use proper file URL format const stripeAbsolutePath = path.resolve(stripePdfFilePath); await expect.poll(() => fs.existsSync(stripeAbsolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const stripePdfBytes = fs.readFileSync(stripeAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, stripePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `downloads-PDF-in-English-stripe-template.png`, ); }); test("downloads PDF in Polish and verifies content", async ({ page, browserName, downloadDir, }, testInfo) => { // Switch to Polish await page .getByRole("combobox", { name: "Invoice PDF Language" }) .selectOption("pl"); // we wait until this button is visible and enabled, that means that the PDF preview has been regenerated const downloadPdfPolishButton = page.getByRole("link", { name: "Download PDF in Polish", }); await expect(downloadPdfPolishButton).toBeVisible(); await expect(downloadPdfPolishButton).toBeEnabled(); const invoiceItemsSection = page.getByTestId("invoice-items-section"); // update name for the first invoice item await invoiceItemsSection .getByRole("textbox", { name: "Name" }) .fill("Invoice Item 1 TEST"); // update price and quantity for the first invoice item await invoiceItemsSection .getByRole("spinbutton", { name: "Amount (Quantity)" }) .fill("3"); await invoiceItemsSection .getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", }) .fill("1000"); // update VAT for the first invoice item const taxSettingsFieldset = invoiceItemsSection.getByRole("group", { name: "Tax Settings", }); await taxSettingsFieldset .getByRole("textbox", { name: "VAT Rate" }) .fill("10"); await taxSettingsFieldset .getByRole("textbox", { name: "Tax Label" }) .fill("Custom TEST TAX LABEL"); /** ADD NEW INVOICE ITEM */ await invoiceItemsSection .getByRole("button", { name: "Add invoice item" }) .click(); const invoiceItem2Fieldset = invoiceItemsSection.getByRole("group", { name: "Item 2", }); // update name for the second invoice item await invoiceItem2Fieldset .getByRole("textbox", { name: "Name" }) .fill("Invoice Item 2 TEST"); const finalSection = page.getByTestId(`final-section`); // for better debugging screenshots, we fill in the notes field with a test note =) await finalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: downloads PDF in Polish and verifies content (${testInfo.project.name})`, ); // Check that the total is correct (should be 3,300.00) const totalTextbox = page.getByRole("textbox", { name: "Total" }); await expect(totalTextbox).toHaveValue("3,300.00"); /** TEST PERSON AUTHORIZED TO RECEIVE FIELD */ const personAuthorizedToReceiveFieldset = finalSection.getByRole("group", { name: "Person Authorized to Receive", }); // Verify that "Show Person Authorized to Receive in PDF" switch is on by default const showPersonAuthorizedToReceiveSwitch = personAuthorizedToReceiveFieldset.getByRole("switch", { name: "Show Person Authorized to Receive in PDF", }); await expect(showPersonAuthorizedToReceiveSwitch).toBeVisible(); await expect(showPersonAuthorizedToReceiveSwitch).toBeEnabled(); await expect(showPersonAuthorizedToReceiveSwitch).toBeChecked(); const personAuthorizedToReceiveNameInput = personAuthorizedToReceiveFieldset.getByRole("textbox", { name: "Name", }); await personAuthorizedToReceiveNameInput.fill("John Doe"); /** TEST PERSON AUTHORIZED TO ISSUE FIELD */ const personAuthorizedToIssueFieldset = finalSection.getByRole("group", { name: "Person Authorized to Issue", }); const showPersonAuthorizedToIssueSwitch = personAuthorizedToIssueFieldset.getByRole("switch", { name: "Show Person Authorized to Issue in PDF", }); await expect(showPersonAuthorizedToIssueSwitch).toBeVisible(); await expect(showPersonAuthorizedToIssueSwitch).toBeEnabled(); await expect(showPersonAuthorizedToIssueSwitch).toBeChecked(); const personAuthorizedToIssueNameInput = personAuthorizedToIssueFieldset.getByRole("textbox", { name: "Name", }); await personAuthorizedToIssueNameInput.fill("Adam Smith"); // wait, because we update pdf on debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfPolishButton.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "downloads-PDF-in-Polish.png", ); // navigate back to the previous page await page.goto("/"); await expect(page).toHaveURL("/?template=default"); /** * Switch to Stripe template and download PDF in Polish with Stripe template */ await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); await page.waitForURL("/?template=stripe"); // Verify that the Stripe template is selected const templateSelect = page.getByRole("combobox", { name: "Invoice Template", }); await expect(templateSelect).toHaveValue("stripe"); const notesFinalSection = page.getByTestId(`final-section`); // for better debugging screenshots, we fill in the notes field with a test note =) await notesFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: downloads PDF in Polish and verifies content with Stripe template (${testInfo.project.name})`, ); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); // Wait for the download button to be visible and enabled for Stripe template const downloadPdfStripeButton = page.getByRole("link", { name: "Download PDF in Polish", }); await expect(downloadPdfStripeButton).toBeVisible(); await expect(downloadPdfStripeButton).toBeEnabled(); // Click the download button and wait for download const [stripeDownload] = await Promise.all([ page.waitForEvent("download"), downloadPdfStripeButton.click(), ]); // Get the suggested filename const stripeSuggestedFilename = stripeDownload.suggestedFilename(); // save the file to temporary directory const stripePdfFilePath = path.join( downloadDir, `${browserName}-stripe-${stripeSuggestedFilename}`, ); await stripeDownload.saveAs(stripePdfFilePath); // Convert to absolute path and use proper file URL format const stripeAbsolutePath = path.resolve(stripePdfFilePath); await expect.poll(() => fs.existsSync(stripeAbsolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const stripePdfBytes = fs.readFileSync(stripeAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, stripePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `downloads-PDF-in-Polish-stripe-template.png`, ); }); test("update pdf when invoice data changes", async ({ page, browserName, downloadDir, }, testInfo) => { const DATE_FORMAT = "MMMM D, YYYY"; // Switch to another currency via combobox const currencyCombobox = page.getByRole("combobox", { name: "Currency" }); // Open the combobox to select the currency await currencyCombobox.click(); // Select the GBP currency await page.getByRole("option", { name: /^GBP\s/ }).click(); await expect(currencyCombobox).toContainText("GBP"); // check that value is saved in the hidden input await expect(page.locator('input[name="currency"]')).toHaveValue("GBP"); // Switch to another date format await page .getByRole("combobox", { name: "Date format" }) .selectOption(DATE_FORMAT); await page .getByRole("textbox", { name: "Header Notes" }) .fill("HELLO FROM PLAYWRIGHT TEST!"); /** UPDATE SELLER INFORMATION */ const sellerSection = page.getByTestId(`seller-information-section`); // Name field await sellerSection .getByRole("textbox", { name: "Name" }) .fill("PLAYWRIGHT SELLER TEST"); // Toggle VAT Number visibility off await sellerSection .getByRole("switch", { name: `Show the 'Seller Tax Number' Field in the PDF`, }) .click(); // Toggle Account Number visibility off await sellerSection .getByRole("switch", { name: `Show the 'Account Number' Field in the PDF`, }) .click(); // Toggle SWIFT visibility off await sellerSection .getByRole("switch", { name: `Show the 'SWIFT/BIC' Field in the PDF`, }) .click(); // update notes await sellerSection .getByRole("textbox", { name: "Notes" }) .fill("PLAYWRIGHT SELLER NOTES TEST"); // Toggle notes visibility on const sellerNotesSwitch = sellerSection.getByTestId( `sellerNotesInvoiceFormFieldVisibilitySwitch`, ); await expect(sellerNotesSwitch).toHaveRole("switch"); await expect(sellerNotesSwitch).toBeChecked(); /** UPDATE BUYER INFORMATION */ const buyerSection = page.getByTestId(`buyer-information-section`); // Name field await buyerSection .getByRole("textbox", { name: "Name" }) .fill("PLAYWRIGHT BUYER TEST"); // // Address field await buyerSection .getByRole("textbox", { name: "Address" }) .fill("PLAYWRIGHT BUYER ADDRESS TEST"); // // Email field await buyerSection .getByRole("textbox", { name: "Email" }) .fill("TEST_BUYER_EMAIL@mail.com"); // update notes await buyerSection .getByRole("textbox", { name: "Notes" }) .fill("PLAYWRIGHT BUYER NOTES TEST"); // Toggle notes visibility on const buyerNotesSwitch = buyerSection.getByTestId( `buyerNotesInvoiceFormFieldVisibilitySwitch`, ); await expect(buyerNotesSwitch).toHaveRole("switch"); await expect(buyerNotesSwitch).toBeChecked(); const invoiceSection = page.getByTestId(`invoice-items-section`); // Amount field await invoiceSection .getByRole("spinbutton", { name: "Amount (Quantity)" }) .fill("3"); // Net price field await invoiceSection .getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", }) .fill("1000"); // Toggle VAT Table Summary visibility off await page .getByRole("switch", { name: `Show "VAT Table Summary" in the PDF` }) .click(); const notesFinalSection = page.getByTestId(`final-section`); // for better debugging screenshots, we fill in the notes field with a test note =) await notesFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: update pdf when invoice data changes (${testInfo.project.name})`, ); // Wait for PDF preview to regenerate after invoice data changes (debounce timeout) // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfEnglishButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPdfEnglishButton).toBeVisible(); await expect(downloadPdfEnglishButton).toBeEnabled(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfEnglishButton.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `update-pdf-when-invoice-data-changes.png`, ); // navigate back to the previous page await page.goto("/"); await expect(page).toHaveURL("/?template=default"); // Wait for the download button to be ready after navigation const newDownloadPdfButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(newDownloadPdfButton).toBeVisible(); await expect(newDownloadPdfButton).toBeEnabled(); /** * Switch to Stripe template and download PDF in English with Stripe template */ await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); await page.waitForURL("/?template=stripe"); // Verify that the Stripe template is selected const templateSelect = page.getByRole("combobox", { name: "Invoice Template", }); await expect(templateSelect).toHaveValue("stripe"); // for better debugging screenshots, we fill in the notes field with a test note =) await notesFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: update pdf when invoice data changes with Stripe template (${testInfo.project.name})`, ); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfStripeButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPdfStripeButton).toBeVisible(); // Click the download button and wait for download const [stripeDownload] = await Promise.all([ page.waitForEvent("download"), downloadPdfStripeButton.click(), ]); // Get the suggested filename const stripeSuggestedFilename = stripeDownload.suggestedFilename(); // save the file to temporary directory const stripePdfFilePath = path.join( downloadDir, `${browserName}-stripe-${stripeSuggestedFilename}`, ); await stripeDownload.saveAs(stripePdfFilePath); // Convert to absolute path and use proper file URL format const stripeAbsolutePath = path.resolve(stripePdfFilePath); await expect.poll(() => fs.existsSync(stripeAbsolutePath)).toBe(true); const stripePdfBytes = fs.readFileSync(stripeAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, stripePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `update-pdf-when-invoice-data-changes-stripe-template.png`, ); }); test("completes full invoice flow on mobile: tabs navigation, form editing and PDF download in French", async ({ page, browserName, downloadDir, }, testInfo) => { // Set mobile viewport await page.setViewportSize({ width: 375, height: 667 }); // Verify tabs are visible in mobile view await expect(page.getByRole("tab", { name: "Edit Invoice" })).toBeVisible(); await expect(page.getByRole("tab", { name: "Preview PDF" })).toBeVisible(); // Download button in English is visible and enabled const downloadPdfButtonEnglish = page.getByRole("link", { name: "Download PDF in English", }); // Wait for download button to be visible await expect(downloadPdfButtonEnglish).toBeVisible(); // Wait for download button to be enabled await expect(downloadPdfButtonEnglish).toBeEnabled(); // Switch to French await page .getByRole("combobox", { name: "Invoice PDF Language" }) .selectOption("fr"); // Switch currency to GBP via combobox const mobileCurrencyCombobox = page.getByRole("combobox", { name: "Currency", }); await mobileCurrencyCombobox.click(); await page.getByRole("option", { name: /^GBP\s/ }).click(); await expect(mobileCurrencyCombobox).toContainText("GBP"); // check that value is saved in the hidden input await expect(page.locator('input[name="currency"]')).toHaveValue("GBP"); const invoiceNumberFieldset = page.getByRole("group", { name: "Invoice Number", }); const invoiceNumberLabelInput = invoiceNumberFieldset.getByRole("textbox", { name: "Label", }); const invoiceNumberValueInput = invoiceNumberFieldset.getByRole("textbox", { name: "Value", }); await invoiceNumberLabelInput.fill("MOBILE-TEST-001:"); await invoiceNumberValueInput.fill("2/05-2024"); const finalSection = page.getByTestId("final-section"); // for better debugging screenshots, we fill in the notes field with a test note =) await finalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: completes full invoice flow on mobile: tabs navigation, form editing and PDF download in French (${testInfo.project.name})`, ); // Fill in seller information const sellerSection = page.getByTestId("seller-information-section"); await sellerSection .getByRole("textbox", { name: "Name" }) .fill("Mobile Test Seller"); await sellerSection .getByRole("textbox", { name: "Address" }) .fill("456 Mobile St"); // Fill in an invoice item const invoiceItemsSection = page.getByTestId("invoice-items-section"); await invoiceItemsSection .getByRole("spinbutton", { name: "Amount (Quantity)" }) .fill("3"); await invoiceItemsSection .getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", }) .fill("50"); const taxSettingsFieldset = invoiceItemsSection.getByRole("group", { name: "Tax Settings", }); await taxSettingsFieldset .getByRole("textbox", { name: "TVA Rate", exact: true }) .fill("23"); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); // we wait until this button is visible and enabled, that means that the PDF preview has been regenerated const downloadPdfFrenchBtn = page.getByRole("link", { name: "Download PDF in French", }); // Wait for download button to be visible and enabled await expect(downloadPdfFrenchBtn).toBeVisible(); await expect(downloadPdfFrenchBtn).toBeEnabled(); // Switch to preview tab await page.getByRole("tab", { name: "Preview PDF" }).click(); // Verify preview tab is selected await expect( page.getByRole("tabpanel", { name: "Preview PDF" }), ).toBeVisible(); await expect( page.getByRole("tabpanel", { name: "Edit Invoice" }), ).toBeHidden(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfFrenchBtn.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Verify toast appears after download await expect(page.getByTestId("download-pdf-toast")).toBeVisible(); await expect( page.getByRole("link", { name: "Star on GitHub" }), ).toBeVisible(); await expect(page.getByTestId("toast-cta-btn")).toBeVisible(); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `completes-full-invoice-flow-on-mobile.png`, ); // Navigate back to the previous page await page.goto("/"); await expect(page).toHaveURL("/?template=default"); // Set mobile viewport again await page.setViewportSize({ width: 375, height: 667 }); // Switch back to form tab await page.getByRole("tab", { name: "Edit Invoice" }).click(); // Verify form tab is selected and data persists await expect( page.getByRole("tabpanel", { name: "Edit Invoice" }), ).toBeVisible(); await expect( page.getByRole("tabpanel", { name: "Preview PDF" }), ).toBeHidden(); // Verify form data persists await expect(invoiceNumberLabelInput).toHaveValue("MOBILE-TEST-001:"); await expect(invoiceNumberValueInput).toHaveValue("2/05-2024"); await expect( finalSection.getByRole("textbox", { name: "Notes", exact: true }), ).toHaveValue( `Test: completes full invoice flow on mobile: tabs navigation, form editing and PDF download in French (${testInfo.project.name})`, ); // Verify seller information persists await expect( sellerSection.getByRole("textbox", { name: "Name" }), ).toHaveValue("Mobile Test Seller"); await expect( sellerSection.getByRole("textbox", { name: "Address" }), ).toHaveValue("456 Mobile St"); // Verify invoice item persists await expect( invoiceItemsSection.getByRole("spinbutton", { name: "Amount (Quantity)", }), ).toHaveValue("3"); await expect( invoiceItemsSection.getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", }), ).toHaveValue("50"); const newTaxSettingsFieldset = invoiceItemsSection.getByRole("group", { name: "Tax Settings", }); await expect( newTaxSettingsFieldset.getByRole("textbox", { name: "TVA Rate", exact: true, }), ).toHaveValue("23"); // Verify calculations are correct await expect( invoiceItemsSection.getByRole("textbox", { name: "Net Amount", exact: true, }), ).toHaveValue("150.00"); await expect( invoiceItemsSection.getByRole("textbox", { name: "TVA Amount", exact: true, }), ).toHaveValue("34.50"); await expect( invoiceItemsSection.getByRole("textbox", { name: "Pre-tax Amount", exact: true, }), ).toHaveValue("184.50"); /** * Switch to Stripe template and download PDF in English with Stripe template */ await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); await page.waitForURL("/?template=stripe"); // Verify that the Stripe template is selected const templateSelect = page.getByRole("combobox", { name: "Invoice Template", }); await expect(templateSelect).toHaveValue("stripe"); const newFinalSection = page.getByTestId(`final-section`); // for better debugging screenshots, we fill in the notes field with a test note =) await newFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: completes full invoice flow on mobile: tabs navigation, form editing and PDF download in French with Stripe template (${testInfo.project.name})`, ); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfStripeButton = page.getByRole("link", { name: "Download PDF in French", }); await expect(downloadPdfStripeButton).toBeVisible(); // Click the download button and wait for download const [stripeDownload] = await Promise.all([ page.waitForEvent("download"), downloadPdfStripeButton.click(), ]); // Get the suggested filename const stripeSuggestedFilename = stripeDownload.suggestedFilename(); // save the file to temporary directory const stripePdfFilePath = path.join( downloadDir, `${browserName}-stripe-${stripeSuggestedFilename}`, ); await stripeDownload.saveAs(stripePdfFilePath); // Convert to absolute path and use proper file URL format const stripeAbsolutePath = path.resolve(stripePdfFilePath); await expect.poll(() => fs.existsSync(stripeAbsolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const stripePdfBytes = fs.readFileSync(stripeAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, stripePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `completes-full-invoice-flow-on-mobile-stripe-template.png`, ); }); test("should display and persist invoice number in different languages", async ({ page, downloadDir, browserName, }, testInfo) => { const generalInfoSection = page.getByTestId("general-information-section"); const invoiceNumberFieldset = generalInfoSection.getByRole("group", { name: "Invoice Number", }); const invoiceNumberLabelInput = invoiceNumberFieldset.getByRole("textbox", { name: "Label", }); const invoiceNumberValueInput = invoiceNumberFieldset.getByRole("textbox", { name: "Value", }); await expect(invoiceNumberLabelInput).toHaveValue( INITIAL_INVOICE_DATA.invoiceNumberObject.label, ); // we mock the system time to a fixed date, so that the invoice number is consistent across tests await expect(invoiceNumberValueInput).toHaveValue("1/12-2025"); const languageSelect = page.getByRole("combobox", { name: "Invoice PDF Language", }); await languageSelect.selectOption("pl"); await expect(invoiceNumberLabelInput).toHaveValue( `${INVOICE_PDF_TRANSLATIONS.pl.invoiceNumber}:`, ); // we mock the system time to a fixed date, so that the invoice number is consistent across tests await expect(invoiceNumberValueInput).toHaveValue("1/12-2025"); // I can fill in a new invoice number await invoiceNumberLabelInput.fill("Faktura TEST:"); // check that warning message appears const switchToDefaultFormatButton = page.getByRole("button", { name: `Switch to default label ("Faktura nr:")`, }); await expect(switchToDefaultFormatButton).toBeVisible(); // switch to default format await switchToDefaultFormatButton.click(); // check that the invoice number is updated to the default format await expect(invoiceNumberLabelInput).toHaveValue(`Faktura nr:`); // check that the switch to default format button is hidden await expect(switchToDefaultFormatButton).toBeHidden(); // fill once again the invoice number await invoiceNumberLabelInput.fill("Faktura TEST:"); // Switch currency to CHF via combobox const currencyCombobox2 = page.getByRole("combobox", { name: "Currency" }); await currencyCombobox2.click(); await page.getByRole("option", { name: /^CHF\s/ }).click(); await expect(currencyCombobox2).toContainText("CHF"); // check that value is saved in the hidden input await expect(page.locator('input[name="currency"]')).toHaveValue("CHF"); const notesFinalSection = page.getByTestId(`final-section`); // for better debugging screenshots, we fill in the notes field with a test note =) await notesFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: should display and persist invoice number in different languages (${testInfo.project.name})`, ); // we wait until this button is visible and enabled, that means that the PDF preview has been regenerated // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); // we reload the page to test that the invoice number is persisted after page reload await page.reload(); // Verify that the download PDF button is visible after page reload const downloadPdfPlButton = page.getByRole("link", { name: "Download PDF in Polish", }); await expect(downloadPdfPlButton).toBeVisible(); const newInvoiceNumberLabelInput = invoiceNumberFieldset.getByRole( "textbox", { name: "Label", }, ); // Verify that the invoice number is persisted after page reload await expect(newInvoiceNumberLabelInput).toHaveValue("Faktura TEST:"); const newLanguageSelect = page.getByRole("combobox", { name: "Invoice PDF Language", }); // Verify that the language is persisted after page reload await expect(newLanguageSelect).toHaveValue("pl"); // switch to Portuguese await newLanguageSelect.selectOption("pt"); await expect(newInvoiceNumberLabelInput).toHaveValue( `${INVOICE_PDF_TRANSLATIONS.pt.invoiceNumber}:`, ); await newInvoiceNumberLabelInput.fill("Fatura TEST PORTUGUESE N°:"); await expect( page.getByRole("button", { name: `Switch to default label ("Fatura N°:")`, }), ).toBeVisible(); const newCurrencyCombobox = page.getByRole("combobox", { name: "Currency", }); // Verify CHF currency is selected await expect(newCurrencyCombobox).toContainText("CHF"); // check that value is saved in the hidden input await expect(page.locator('input[name="currency"]')).toHaveValue("CHF"); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); // we wait until this button is visible and enabled, that means that the PDF preview has been regenerated const downloadPdfPtButton = page.getByRole("link", { name: "Download PDF in Portuguese", }); await expect(downloadPdfPtButton).toBeVisible(); await expect(downloadPdfPtButton).toBeEnabled(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfPtButton.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `should-display-and-persist-invoice-number-in-different-languages.png`, ); // navigate back to the previous page await page.goto("/"); await expect(page).toHaveURL("/?template=default"); /** * Switch to Stripe template and download PDF in English with Stripe template */ await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); await page.waitForURL("/?template=stripe"); // Verify that the Stripe template is selected const templateSelect = page.getByRole("combobox", { name: "Invoice Template", }); await expect(templateSelect).toHaveValue("stripe"); // Currency should be CHF after navigating back to the previous page const currencyCombobox3 = page.getByRole("combobox", { name: "Currency" }); await expect(currencyCombobox3).toContainText("CHF"); // check that value is saved in the hidden input await expect(page.locator('input[name="currency"]')).toHaveValue("CHF"); const newNotesFinalSection = page.getByTestId(`final-section`); // for better debugging screenshots, we fill in the notes field with a test note =) await newNotesFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: should display and persist invoice number in different languages with Stripe template (${testInfo.project.name})`, ); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfStripeButton = page.getByRole("link", { name: "Download PDF in Portuguese", }); await expect(downloadPdfStripeButton).toBeVisible(); // Click the download button and wait for download const [stripeDownload] = await Promise.all([ page.waitForEvent("download"), downloadPdfStripeButton.click(), ]); // Get the suggested filename const stripeSuggestedFilename = stripeDownload.suggestedFilename(); // save the file to temporary directory const stripePdfFilePath = path.join( downloadDir, `${browserName}-stripe-${stripeSuggestedFilename}`, ); await stripeDownload.saveAs(stripePdfFilePath); // Convert to absolute path and use proper file URL format const stripeAbsolutePath = path.resolve(stripePdfFilePath); await expect.poll(() => fs.existsSync(stripeAbsolutePath)).toBe(true); /** * RENDER PDF ON CANVAS AND TAKE SCREENSHOT OF IT */ const stripePdfBytes = fs.readFileSync(stripeAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, stripePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( `should-display-and-persist-invoice-number-in-different-languages-stripe-template.png`, ); }); test("displays QR code in PDF when QR code data is provided", async ({ page, browserName, downloadDir, }, testInfo) => { const QR_CODE_TEST_DATA = { data: "https://easyinvoicepdf.com", description: "QR Code Description", } as const satisfies { data: string; description: string; }; // verify that we are on the default template await expect(page).toHaveURL("/?template=default"); const finalSection = page.getByTestId("final-section"); const qrCodeFieldset = finalSection.getByRole("group", { name: "QR Code", }); await expect(qrCodeFieldset).toBeVisible(); // Verify that "Show QR Code in PDF" switch is on by default const showQrCodeSwitch = qrCodeFieldset.getByRole("switch", { name: "Show QR Code in PDF", }); await expect(showQrCodeSwitch).toBeVisible(); await expect(showQrCodeSwitch).toBeEnabled(); await expect(showQrCodeSwitch).toBeChecked(); // Fill in the QR code data field await qrCodeFieldset .getByRole("textbox", { name: "Data" }) .fill(QR_CODE_TEST_DATA.data); // Fill in the QR code description field await qrCodeFieldset .getByRole("textbox", { name: "Description (optional)" }) .fill(QR_CODE_TEST_DATA.description); // for better debugging screenshots, we fill in the notes field with a test note =) await finalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill(`Test: ${testInfo.title} (${testInfo.project.name})`); // Wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfEnglishButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPdfEnglishButton).toBeVisible(); await expect(downloadPdfEnglishButton).toBeEnabled(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfEnglishButton.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * Render the PDF on a canvas and take a screenshot of it */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "displays-qr-code-in-pdf-default-template.png", ); /** * TURN OFF QR CODE IN PDF AND DOWNLOAD PDF AGAIN */ // navigate back to the previous page await page.goto("/"); await expect(page).toHaveURL("/?template=default"); // verify that we are on the default template await expect(page).toHaveURL("/?template=default"); const newFinalSection = page.getByTestId("final-section"); const newQrCodeFieldset = newFinalSection.getByRole("group", { name: "QR Code", }); await expect(newQrCodeFieldset).toBeVisible(); // Verify that "Show QR Code in PDF" switch is on by default const newShowQrCodeSwitch = newQrCodeFieldset.getByRole("switch", { name: "Show QR Code in PDF", }); await expect(newShowQrCodeSwitch).toBeVisible(); await expect(newShowQrCodeSwitch).toBeEnabled(); await expect(newShowQrCodeSwitch).toBeChecked(); // toggle the switch off await newShowQrCodeSwitch.click(); // verify that the switch is off await expect(newShowQrCodeSwitch).not.toBeChecked(); // Verify QR Code Data field retains its value after toggling visibility off const newQrCodeDataTextarea = newQrCodeFieldset.getByRole("textbox", { name: "Data", }); await expect(newQrCodeDataTextarea).toBeVisible(); await expect(newQrCodeDataTextarea).toHaveValue(QR_CODE_TEST_DATA.data); // Verify QR Code Description field retains its value after toggling visibility off const newQrCodeDescriptionTextarea = newQrCodeFieldset.getByRole( "textbox", { name: "Description (optional)", }, ); await expect(newQrCodeDescriptionTextarea).toBeVisible(); await expect(newQrCodeDescriptionTextarea).toHaveValue( QR_CODE_TEST_DATA.description, ); // for better debugging screenshots, we fill in the notes field with a test note =) await newFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: ${testInfo.title} - QR code hidden in PDF (${testInfo.project.name})`, ); // wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const newDownloadPdfEnglishButton = page.getByRole("link", { name: "Download PDF in English", }); // Download the PDF again const [downloadPdfWithoutQrCode] = await Promise.all([ page.waitForEvent("download"), newDownloadPdfEnglishButton.click(), ]); // Get the suggested filename const suggestedFilenameWithoutQrCode = downloadPdfWithoutQrCode.suggestedFilename(); // save the file to temporary directory const pdfFilePath2 = path.join( downloadDir, `${browserName}-${suggestedFilenameWithoutQrCode}`, ); await downloadPdfWithoutQrCode.saveAs(pdfFilePath2); /** * Render the PDF on a canvas and take a screenshot to verify QR code is not displayed */ const pdfBytesWithoutQrCode = fs.readFileSync(pdfFilePath2); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytesWithoutQrCode); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "qr-code-hidden-in-pdf-default-template.png", ); }); test("generates multi-page PDF when invoice has many items", async ({ page, browserName, downloadDir, }, testInfo) => { // Verify we're on the default template await expect(page).toHaveURL("/?template=default"); const invoiceItemsSection = page.getByTestId("invoice-items-section"); // Update tax label for the first item const firstItemFieldset = invoiceItemsSection.getByRole("group", { name: "Item 1", }); const firstItemTaxSettingsFieldset = firstItemFieldset.getByRole("group", { name: "Tax Settings", }); await firstItemTaxSettingsFieldset .getByRole("textbox", { name: "Tax Label" }) .fill("Sales Tax"); // Add additional invoice items to trigger multiple-page PDF for (let i = 0; i < 17; i++) { await invoiceItemsSection .getByRole("button", { name: "Add invoice item" }) .click(); // Fill minimal required fields for the new item const itemFieldset = invoiceItemsSection.getByRole("group", { name: `Item ${i + 2}`, // Item numbers start at 1 }); await itemFieldset .getByRole("textbox", { name: "Name" }) .fill( `Item ${i + 2}. Some long item name that should be wrapped to the next line. Some long item name that should be wrapped to the next line. Some long item name that should be wrapped to the next line.`, ); // Set VAT to 10% for each item const taxSettingsFieldset = itemFieldset.getByRole("group", { name: "Tax Settings", }); // Use different tax rates: 10%, 20%, or 50% const taxRate = // eslint-disable-next-line playwright/no-conditional-in-test (i + 2) % 3 === 0 ? "50" : (i + 2) % 2 === 0 ? "20" : "10"; await taxSettingsFieldset .getByRole("textbox", { name: "Sales Tax Rate", exact: true }) .fill(taxRate); await itemFieldset .getByRole("spinbutton", { name: "Net Price (Rate or Unit Price)", }) .fill(`${1000 * (i + 1)}`); } const finalSection = page.getByTestId("final-section"); // for better debugging screenshots, we fill in the notes field with a test note await finalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: generates multi-page PDF when invoice has many items (${testInfo.project.name})`, ); // Wait for PDF preview to regenerate after invoice data changes (debounce timeout) // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfEnglishButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPdfEnglishButton).toBeVisible(); await expect(downloadPdfEnglishButton).toBeEnabled(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfEnglishButton.click(), ]); // Get the suggested filename const suggestedFilename = download.suggestedFilename(); // save the file to temporary directory const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); // Convert to absolute path and use proper file URL format const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); /** * RENDER ALL PDF PAGES ON A SINGLE CANVAS AND TAKE SCREENSHOT */ const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderMultiPagePdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "default-template-multi-pages.png", ); }); test("generates PDF with logo when using default template", async ({ page, browserName, downloadDir, }) => { await expect(page).toHaveURL("/?template=default"); const generalInfoSection = page.getByTestId("general-information-section"); // Upload a valid logo await uploadLogoFile(page); // Verify logo preview is visible await expect(page.getByText("Logo uploaded successfully!")).toBeVisible(); await expect( generalInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); await expect( generalInfoSection.getByText( "Logo uploaded successfully. Click the X to remove it.", ), ).toBeVisible(); // Wait for debounce timeout // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(800); const downloadPDFButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPDFButton).toBeVisible(); await expect(downloadPDFButton).toBeEnabled(); // Click the download button and wait for download const [download] = await Promise.all([ page.waitForEvent("download"), downloadPDFButton.click(), ]); const suggestedFilename = download.suggestedFilename(); const pdfFilePath = path.join( downloadDir, `${browserName}-${suggestedFilename}`, ); await download.saveAs(pdfFilePath); const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "pdf-with-logo-default-template.png", ); /** * VERIFY LOGO PERSISTS AFTER NAVIGATING BACK TO DEFAULT TEMPLATE */ // Navigate back and switch to Stripe template to verify logo persists await page.goto("/?template=default"); await expect(page).toHaveURL("/?template=default"); // Verify logo is still present after navigation const newGeneralInfoSection = page.getByTestId( "general-information-section", ); await expect( newGeneralInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); /** * VERIFY LOGO PERSISTS AFTER SWITCHING TO STRIPE TEMPLATE */ // Switch to Stripe template await page .getByRole("combobox", { name: "Invoice Template" }) .selectOption("stripe"); await page.waitForURL("/?template=stripe"); // Verify logo persists after template switch await expect( newGeneralInfoSection.getByAltText("Company logo preview"), ).toBeVisible(); // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(800); // Download PDF with Stripe template + logo const stripeDownloadPDFButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(stripeDownloadPDFButton).toBeVisible(); await expect(stripeDownloadPDFButton).toBeEnabled(); const [stripeDownload] = await Promise.all([ page.waitForEvent("download"), stripeDownloadPDFButton.click(), ]); const stripeSuggestedFilename = stripeDownload.suggestedFilename(); const stripePdfFilePath = path.join( downloadDir, `${browserName}-stripe-${stripeSuggestedFilename}`, ); await stripeDownload.saveAs(stripePdfFilePath); const stripeAbsolutePath = path.resolve(stripePdfFilePath); await expect.poll(() => fs.existsSync(stripeAbsolutePath)).toBe(true); const stripePdfBytes = fs.readFileSync(stripeAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, stripePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "pdf-with-logo-stripe-template-after-switch.png", ); }); test("toggles seller and buyer email visibility in PDF", async ({ page, browserName, downloadDir, }, testInfo) => { await expect(page).toHaveURL("/?template=default"); /* * PHASE 1: Fill inline form with email switch OFF -> PDF screenshot (emails hidden) */ const sellerSection = page.getByTestId("seller-information-section"); const buyerSection = page.getByTestId("buyer-information-section"); // Fill seller fields inline (no saved seller selected, so switch is enabled) await sellerSection .getByRole("textbox", { name: "Name (Required)" }) .fill("Email Visibility Test Seller"); await sellerSection .getByRole("textbox", { name: "Address (Required)" }) .fill("123 Seller Street\nSeller City, 10001"); await sellerSection .getByRole("textbox", { name: "Email" }) .fill("VISIBLE-SELLER@test.com"); // Toggle seller email switch OFF via inline form const sellerEmailSwitch = sellerSection.getByRole("switch", { name: "Show the 'Email' field in the PDF", }); await expect(sellerEmailSwitch).toBeChecked(); await sellerEmailSwitch.click(); await expect(sellerEmailSwitch).not.toBeChecked(); // Fill buyer fields inline (no saved buyer selected, so switch is enabled) await buyerSection .getByRole("textbox", { name: "Name (Required)" }) .fill("Email Visibility Test Buyer"); await buyerSection .getByRole("textbox", { name: "Address (Required)" }) .fill("456 Buyer Avenue\nBuyer City, 20002"); await buyerSection .getByRole("textbox", { name: "Email" }) .fill("VISIBLE-BUYER@test.com"); // Toggle buyer email switch OFF via inline form const buyerEmailSwitch = buyerSection.getByRole("switch", { name: "Show the 'Email' field in the PDF", }); await expect(buyerEmailSwitch).toBeChecked(); await buyerEmailSwitch.click(); await expect(buyerEmailSwitch).not.toBeChecked(); const finalSection = page.getByTestId("final-section"); await finalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: ${testInfo.title} - emails hidden (${testInfo.project.name})`, ); // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const downloadPdfButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(downloadPdfButton).toBeVisible(); await expect(downloadPdfButton).toBeEnabled(); // Download PDF with emails hidden (toggled off via inline form) const [download] = await Promise.all([ page.waitForEvent("download"), downloadPdfButton.click(), ]); const pdfFilePath = path.join( downloadDir, `${browserName}-${download.suggestedFilename()}`, ); await download.saveAs(pdfFilePath); const absolutePath = path.resolve(pdfFilePath); await expect.poll(() => fs.existsSync(absolutePath)).toBe(true); const pdfBytes = fs.readFileSync(absolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, pdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "email-hidden-in-pdf-default-template.png", ); /* * PHASE 2: Save seller/buyer via dialog with email ON -> PDF screenshot (emails visible) */ await page.goto("/?template=default"); await expect(page).toHaveURL("/?template=default"); // Create seller via dialog with email visible await page.getByRole("button", { name: "New Seller" }).click(); const manageSellerDialog = page.getByTestId("manage-seller-dialog"); await manageSellerDialog .getByRole("textbox", { name: "Name (Required)" }) .fill("Email Visibility Test Seller"); await manageSellerDialog .getByRole("textbox", { name: "Address (Required)" }) .fill("123 Seller Street\nSeller City, 10001"); await manageSellerDialog .getByRole("textbox", { name: "Email" }) .fill("VISIBLE-SELLER@test.com"); // Verify email visibility switch is checked by default in dialog const sellerEmailSwitchInDialog = manageSellerDialog.getByRole("switch", { name: "Show the 'Email' field in the PDF", }); await expect(sellerEmailSwitchInDialog).toBeVisible(); await expect(sellerEmailSwitchInDialog).toBeChecked(); await manageSellerDialog .getByRole("button", { name: "Save Seller" }) .click(); await expect(manageSellerDialog).toBeHidden(); await expect( page.getByText("Seller added and applied to invoice", { exact: true }), ).toBeVisible(); // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); // Create buyer via dialog with email visible await page.getByRole("button", { name: "New Buyer" }).click(); const manageBuyerDialog = page.getByTestId("manage-buyer-dialog"); await manageBuyerDialog .getByRole("textbox", { name: "Name (Required)" }) .fill("Email Visibility Test Buyer"); await manageBuyerDialog .getByRole("textbox", { name: "Address (Required)" }) .fill("456 Buyer Avenue\nBuyer City, 20002"); await manageBuyerDialog .getByRole("textbox", { name: "Email" }) .fill("VISIBLE-BUYER@test.com"); // Verify email visibility switch is checked by default in dialog const buyerEmailSwitchInDialog = manageBuyerDialog.getByRole("switch", { name: "Show the 'Email' field in the PDF", }); await expect(buyerEmailSwitchInDialog).toBeVisible(); await expect(buyerEmailSwitchInDialog).toBeChecked(); await manageBuyerDialog.getByRole("button", { name: "Save Buyer" }).click(); await expect(manageBuyerDialog).toBeHidden(); await expect( page.getByText("Buyer added and applied to invoice", { exact: true }), ).toBeVisible(); const newFinalSection = page.getByTestId("final-section"); await newFinalSection .getByRole("textbox", { name: "Notes", exact: true }) .fill( `Test: ${testInfo.title} - emails visible (${testInfo.project.name})`, ); // eslint-disable-next-line playwright/no-wait-for-timeout await page.waitForTimeout(700); const newDownloadPdfButton = page.getByRole("link", { name: "Download PDF in English", }); await expect(newDownloadPdfButton).toBeVisible(); await expect(newDownloadPdfButton).toBeEnabled(); // Download PDF with emails visible (saved via dialog) const [downloadVisible] = await Promise.all([ page.waitForEvent("download"), newDownloadPdfButton.click(), ]); const visiblePdfFilePath = path.join( downloadDir, `${browserName}-visible-${downloadVisible.suggestedFilename()}`, ); await downloadVisible.saveAs(visiblePdfFilePath); const visibleAbsolutePath = path.resolve(visiblePdfFilePath); await expect.poll(() => fs.existsSync(visibleAbsolutePath)).toBe(true); const visiblePdfBytes = fs.readFileSync(visibleAbsolutePath); await page.goto("about:blank"); await renderPdfOnCanvas(page, visiblePdfBytes); await page.waitForFunction( () => (window as unknown as { __PDF_RENDERED__: boolean }) .__PDF_RENDERED__ === true, ); await expect(page.locator("canvas")).toHaveScreenshot( "email-visible-in-pdf-default-template.png", ); }); });