easy-invoice-pdf/e2e/buyer.test.ts
Vlad Sazonau e6f54d5dfc
feat: add e2e tests with playwright and other improvements (#79)
* feat: Add language attribute to date input fields in invoice form components

- Include `lang="en"` attribute in date input fields of `InvoiceForm` and `GeneralInformation` components for improved accessibility and localization support.

* fix: Update language attribute for date input fields in invoice form components

* refactor: Improve layout and organization of invoice components

- Remove unnecessary margin from the main container in the Home component.
- Wrap the share invoice button and PDF download link in a fragment for better structure.
- Adjust margins for the ProjectInfo and action button container for improved spacing.
- Update the InvoicePDFViewer height to use full height for better responsiveness.
- Remove the deprecated RegenerateInvoiceButton component to streamline the codebase.
- Update the InvoiceClientPage to accept handleShareInvoice prop for better functionality.
- Clean up unused language attributes in date input fields across invoice form components.

* feat: Integrate Playwright for end-to-end testing and enhance invoice form components

- Add Playwright configuration and dependencies for E2E testing.
- Create GitHub Actions workflow for automated E2E tests on deployment.
- Implement initial E2E tests for the Invoice Generator Page, verifying UI elements and form functionality.
- Refactor invoice form components to include data-testid attributes for better testability.
- Update .gitignore to exclude Playwright-related files and directories.

* chore: Update GitHub Actions workflow for E2E testing and enhance test coverage

- Upgrade pnpm version from 8 to 10 in the E2E workflow for improved package management.
- Add new test case to verify header buttons and links on the Invoice Generator Page, ensuring UI elements are displayed correctly and have the expected attributes.

* chore: Enhance ESLint configuration for Playwright integration

- Add Playwright ESLint plugin to package.json for improved E2E testing support.
- Update .eslintrc.json to include overrides for E2E test files.
- Clean up GitHub Actions workflow by removing unnecessary pnpm version specification.

* chore: Update Playwright configuration and improve test assertions

- Increase timeout for expect assertions and test execution from 15 seconds to 30 seconds for better stability in E2E tests.
- Comment out mobile viewport tests to streamline configuration and focus on desktop testing.

* chore: Update configuration and refactor invoice form components

- Add compiler options to remove console logs in production and enhance logging for fetch requests in next.config.mjs.
- Update package.json to include new type definitions for ua-parser-js and add ua-parser-js as a dependency.
- Refactor invoice form components to remove form prefix IDs, simplifying data-testid attributes for better testability.
- Introduce DeviceContext for managing device type state and improve responsiveness in invoice form components.
- Implement server-side device detection using user agent parsing for better rendering on mobile and desktop views.
- Update media query hooks to streamline device type checks across components.

* chore: Update Playwright configuration and enhance invoice form tests

- Reduce timeout for expect assertions from 30 seconds to 15 seconds for improved test performance.
- Add new test for handling currency switching in the Invoice Generator Page, verifying correct currency display and calculations.
- Refactor buyer and seller information components to include tooltip messages and improve accessibility with aria attributes.
- Update BuyerDialog and BuyerManagement components to enhance user experience with better visibility and edit functionality for buyer details.

* chore: Update Playwright installation command in GitHub Actions workflow

- Modify Playwright installation command to remove explicit browser specification, allowing for default browser installation with dependencies.

* chore: Update GitHub Actions E2E workflow for Playwright report handling

- Change condition for uploading Playwright report to ensure it uploads regardless of test outcome.
- Reduce retention days for uploaded reports from 5 to 3 for better resource management.

* chore: Update Playwright installation command in GitHub Actions workflow

- Specify installation of Chromium and WebKit browsers along with dependencies for enhanced testing capabilities.

* chore: Enhance E2E tests for seller and buyer management functionality

- Add tests to verify the deletion process for sellers and buyers, including confirmation dialogs and success messages.
- Ensure localStorage data is correctly saved and parsed for both seller and buyer information.
- Introduce default data constants for sellers and buyers to streamline test setup.
- Improve accessibility by adding screen reader text for delete buttons in the seller management component.

* chore: Pin versions of GitHub Actions in E2E workflow for stability

- Update actions/checkout, pnpm/action-setup, actions/setup-node, and actions/upload-artifact to specific versions for improved reliability and security.
- Comment added to clarify the rationale for using pinned versions.

* chore: Add E2E test for accordion items visibility and localStorage state management

- Implement test to verify that accordion items are visible, collapsible, and their state is correctly saved in localStorage.
- Ensure state persistence across page reloads and validate updated states after toggling sections.
- Introduce ACCORDION_STATE_LOCAL_STORAGE_KEY and AccordionState type for better type safety and clarity.

* chore: Update Playwright configuration and add comprehensive E2E tests for seller and buyer management

- Increase timeout for expect assertions and test execution from 30 seconds to 60 seconds for improved stability in E2E tests.
- Introduce new E2E tests for seller and buyer management, covering creation, editing, and deletion processes, including confirmation dialogs and success messages.
- Ensure localStorage data is correctly saved and parsed for both seller and buyer information.
- Implement detailed validation for form fields and visibility toggles in seller and buyer management dialogs.
- Enhance accessibility by adding screen reader text for buttons and tooltips in the management components.

* chore: Refactor Playwright configuration and enhance invoice item validation tests

- Introduce a constant for timeout values in Playwright configuration for consistency and maintainability.
- Add comprehensive validation tests for amount, net price, and VAT fields in the invoice items section, ensuring proper error messages for invalid inputs.
- Update expected error messages in the schema to match the new formatting for better clarity.
- Improve test structure by utilizing descriptive variable names and modularizing input handling for better readability.

* chore: add pdf e2e tests

- Add `pdf-parse` and its type definitions to package.json for PDF handling capabilities.
- Increase Playwright timeout from 30 seconds to 60 seconds for improved test stability.
- Introduce comprehensive E2E tests for PDF generation, verifying content in both English and Polish.
- Implement cleanup procedures for test downloads to ensure a clean testing environment.
- Validate invoice data updates in the generated PDF, ensuring accurate content reflects user inputs.

* chore: add eslint, knip, lint-staged

* chore: run prettier

* chore: minor improvements

* chore: add more test and improved e2e config

* minor fixes

* minor fixes

* chore: add new test
2025-03-27 21:41:55 +01:00

309 lines
9.9 KiB
TypeScript

import { DEFAULT_BUYER_DATA, type BuyerData } from "@/app/schema";
import { expect, test } from "@playwright/test";
test.describe("Buyer management", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
test("create/edit buyer", async ({ page }) => {
// Open buyer management dialog
await page.getByRole("button", { name: "New Buyer" }).click();
// Fill in all buyer details
const testData = {
name: "New Test Client",
address: "456 Client Avenue\nClient City, 54321\nClient Country",
vatNoFieldIsVisible: true,
vatNo: "987654321",
email: "client@example.com",
} as const satisfies BuyerData;
const manageBuyerDialog = page.getByTestId(`manage-buyer-dialog`);
// Fill in form fields
await manageBuyerDialog
.getByRole("textbox", { name: "Name" })
.fill(testData.name);
await manageBuyerDialog
.getByRole("textbox", { name: "Address" })
.fill(testData.address);
await manageBuyerDialog
.getByRole("textbox", { name: "VAT Number" })
.fill(testData.vatNo);
await manageBuyerDialog
.getByRole("textbox", { name: "Email" })
.fill(testData.email);
// Verify VAT visibility switch is checked by default
await expect(
manageBuyerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0)
).toBeChecked();
// Toggle VAT visibility switch
await manageBuyerDialog
.getByRole("switch", { name: "Show in PDF" })
.nth(0)
.click(); // Toggle VAT Number visibility
// Verify "Apply to Current Invoice" switch is checked by default
await expect(
manageBuyerDialog.getByRole("switch", {
name: "Apply to Current Invoice",
})
).toBeChecked();
// Cancel button is shown
await expect(
manageBuyerDialog.getByRole("button", { name: "Cancel" })
).toBeVisible();
// Save buyer
await manageBuyerDialog.getByRole("button", { name: "Save Buyer" }).click();
// Verify success toast message is visible
await expect(
page.getByText("Buyer added successfully", { exact: true })
).toBeVisible();
// Verify buyer data is actually saved in localStorage
const storedData = (await page.evaluate(() => {
return localStorage.getItem("EASY_INVOICE_PDF_BUYERS");
})) as string;
expect(storedData).toBeTruthy();
const parsedData = JSON.parse(storedData) as BuyerData[];
expect(parsedData[0]).toMatchObject({
name: testData.name,
address: testData.address,
vatNo: testData.vatNo,
vatNoFieldIsVisible: false,
email: testData.email,
} satisfies BuyerData);
// Verify all saved details in the Buyer Information section form
const buyerForm = page.getByTestId(`buyer-information-section`);
// Try to find desktop tooltip icon first
const desktopTooltipExists =
(await buyerForm
.getByTestId("form-section-tooltip-info-icon-desktop")
.count()) > 0;
// If desktop tooltip exists, hover over it; otherwise find and click mobile tooltip
// eslint-disable-next-line playwright/no-conditional-in-test
if (desktopTooltipExists) {
// Get desktop tooltip icons and hover over the first one because we use tooltip
const desktopTooltips = buyerForm.getByTestId(
"form-section-tooltip-info-icon-desktop"
);
await desktopTooltips.first().hover();
} else {
// Get mobile tooltip icons and click the first one because we use popover
const mobileTooltips = buyerForm.getByTestId(
"form-section-tooltip-info-icon-mobile"
);
await mobileTooltips.first().click();
}
// Check that HTML title attributes contain the tooltip message on input fields
const nameInput = buyerForm.getByRole("textbox", { name: "Name" });
await expect(nameInput).toHaveAttribute(
"title",
"Buyer details are locked. Click the edit buyer button to modify."
);
// Buyer Name
await expect(nameInput).toHaveAttribute("aria-readonly", "true");
await expect(nameInput).toHaveValue(testData.name);
// Buyer Address
await expect(
buyerForm.getByRole("textbox", { name: "Address" })
).toHaveAttribute("aria-readonly", "true");
await expect(
buyerForm.getByRole("textbox", { name: "Address" })
).toHaveValue(testData.address);
// Buyer VAT Number
await expect(
buyerForm.getByRole("textbox", { name: "VAT Number" })
).toHaveAttribute("aria-readonly", "true");
await expect(
buyerForm.getByRole("textbox", { name: "VAT Number" })
).toHaveValue(testData.vatNo);
const vatNumberSwitch = buyerForm.getByTestId(`buyerVatNoFieldIsVisible`);
// Verify VAT Number switch is not checked as we toggled it off
await expect(vatNumberSwitch).not.toBeChecked();
await expect(vatNumberSwitch).toBeDisabled();
// Buyer Email
await expect(
buyerForm.getByRole("textbox", { name: "Email" })
).toHaveAttribute("aria-readonly", "true");
await expect(buyerForm.getByRole("textbox", { name: "Email" })).toHaveValue(
testData.email
);
// Verify the buyer appears in the dropdown
await expect(
buyerForm.getByRole("combobox", { name: "Select Buyer" })
).toContainText(testData.name);
// Test edit functionality
await buyerForm.getByRole("button", { name: "Edit buyer" }).click();
// Verify all fields are populated in edit dialog
await expect(
manageBuyerDialog.getByRole("textbox", { name: "Name" })
).toHaveValue(testData.name);
await expect(
manageBuyerDialog.getByRole("textbox", { name: "Address" })
).toHaveValue(testData.address);
await expect(
manageBuyerDialog.getByRole("textbox", { name: "VAT Number" })
).toHaveValue(testData.vatNo);
await expect(
manageBuyerDialog.getByRole("textbox", { name: "Email" })
).toHaveValue(testData.email);
// Verify visibility switch state persisted in edit dialog
await expect(
manageBuyerDialog.getByRole("switch", { name: "Show in PDF" }).nth(0)
).not.toBeChecked();
// Update some data in edit mode
const updatedName = "Updated Client Corp";
await manageBuyerDialog
.getByRole("textbox", { name: "Name" })
.fill(updatedName);
// Re-enable VAT visibility
await manageBuyerDialog
.getByRole("switch", { name: "Show in PDF" })
.nth(0)
.click();
// Save updated buyer
await manageBuyerDialog.getByRole("button", { name: "Save Buyer" }).click();
// Verify success toast for update
await expect(
page.getByText("Buyer updated successfully", { exact: true })
).toBeVisible();
// Verify updated information is displayed
await expect(buyerForm.getByRole("textbox", { name: "Name" })).toHaveValue(
updatedName
);
// Verify VAT visibility is now enabled
await expect(
buyerForm.getByTestId(`buyerVatNoFieldIsVisible`)
).toBeChecked();
});
test("delete buyer", async ({ page }) => {
// First add a buyer
await page.getByRole("button", { name: "New Buyer" }).click();
const testData = {
name: "Test Delete Buyer",
address: "456 Delete Avenue",
email: "delete@buyer.com",
vatNoFieldIsVisible: true,
vatNo: "123456789",
} as const satisfies BuyerData;
const manageBuyerDialog = page.getByTestId(`manage-buyer-dialog`);
// Fill in basic buyer details
await manageBuyerDialog
.getByRole("textbox", { name: "Name" })
.fill(testData.name);
await manageBuyerDialog
.getByRole("textbox", { name: "Address" })
.fill(testData.address);
await manageBuyerDialog
.getByRole("textbox", { name: "Email" })
.fill(testData.email);
// Save buyer
await manageBuyerDialog.getByRole("button", { name: "Save Buyer" }).click();
// Verify buyer was added
const buyerForm = page.getByTestId(`buyer-information-section`);
await expect(
buyerForm.getByRole("combobox", { name: "Select Buyer" })
).toContainText(testData.name);
// Click delete button
await buyerForm.getByRole("button", { name: "Delete buyer" }).click();
// Verify delete confirmation dialog appears
await expect(page.getByRole("alertdialog")).toBeVisible();
await expect(
page.getByText(
`Are you sure you want to delete "${testData.name}" buyer?`
)
).toBeVisible();
// Cancel button is shown
await expect(page.getByRole("button", { name: "Cancel" })).toBeVisible();
// Click cancel button
await page.getByRole("button", { name: "Cancel" }).click();
// Verify dialog is closed
await expect(page.getByRole("alertdialog")).toBeHidden();
// Click delete button once again to open the dialog
await buyerForm.getByRole("button", { name: "Delete buyer" }).click();
// Verify delete confirmation dialog appears
await expect(page.getByRole("alertdialog")).toBeVisible();
await expect(
page.getByText(
`Are you sure you want to delete "${testData.name}" buyer?`
)
).toBeVisible();
// Confirm deletion
await page.getByRole("button", { name: "Delete" }).click();
// Verify success message
await expect(
page.getByText("Buyer deleted successfully", { exact: true })
).toBeVisible();
// Verify buyer is removed from dropdown
// because we have only one buyer, dropdown will be completely hidden
await expect(
buyerForm.getByRole("combobox", { name: "Select Buyer" })
).toBeHidden();
// Verify form is reset to default values
await expect(buyerForm.getByRole("textbox", { name: "Name" })).toHaveValue(
DEFAULT_BUYER_DATA.name
);
await expect(
buyerForm.getByRole("textbox", { name: "Address" })
).toHaveValue(DEFAULT_BUYER_DATA.address);
await expect(buyerForm.getByRole("textbox", { name: "Email" })).toHaveValue(
DEFAULT_BUYER_DATA.email
);
await expect(
buyerForm.getByRole("textbox", { name: "VAT Number" })
).toHaveValue(DEFAULT_BUYER_DATA.vatNo);
});
});