From 7c03b6fcf2203b1fbdd2b8f2440792a6a902b43c Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 27 Oct 2026 16:45:43 +1100 Subject: [PATCH] Frontend coverage (#13114) * Shipment row actions * Test return order parametric view * SalesOrderLineItem actions * sales order parametric view * playwright tests for sales order allocations --- src/frontend/tests/pages/pui_return.spec.ts | 25 ++- src/frontend/tests/pages/pui_sales.spec.ts | 186 +++++++++++++++++++- 2 files changed, 209 insertions(+), 2 deletions(-) diff --git a/src/frontend/tests/pages/pui_return.spec.ts b/src/frontend/tests/pages/pui_return.spec.ts index 076fc24973..0c984addc1 100644 --- a/src/frontend/tests/pages/pui_return.spec.ts +++ b/src/frontend/tests/pages/pui_return.spec.ts @@ -1,6 +1,11 @@ import { expect } from '@playwright/test'; import { test } from '../baseFixtures'; -import { loadTab } from '../helpers'; +import { + clearTableFilters, + loadTab, + showParametricView, + tableSearch +} from '../helpers'; import { doCachedLogin } from '../login'; test('Return Orders - Receive Items', async ({ browser }) => { @@ -29,3 +34,21 @@ test('Return Orders - Receive Items', async ({ browser }) => { await page.getByRole('banner').getByText('Receive Items').waitFor(); await page.getByRole('button', { name: 'Cancel' }).click(); }); + +test('Return Orders - Parametric View', async ({ browser }) => { + const page = await doCachedLogin(browser, { + url: 'sales/index/returnorders' + }); + + await showParametricView(page); + await clearTableFilters(page); + + // Columns and data are rendered in the parametric table + await page.getByRole('button', { name: 'Customer' }).waitFor(); + await page.getByRole('cell', { name: 'RMA-0002' }).waitFor(); + + // Search within the parametric table + await tableSearch(page, 'RMA-0002'); + await page.getByRole('cell', { name: 'RMA-0002' }).waitFor(); + await expect(page.getByRole('cell', { name: 'RMA-0001' })).toHaveCount(0); +}); diff --git a/src/frontend/tests/pages/pui_sales.spec.ts b/src/frontend/tests/pages/pui_sales.spec.ts index 9161476f3c..444e70669d 100644 --- a/src/frontend/tests/pages/pui_sales.spec.ts +++ b/src/frontend/tests/pages/pui_sales.spec.ts @@ -9,7 +9,8 @@ import { setTableChoiceFilter, showCalendarView, showParametricView, - showTableView + showTableView, + tableSearch } from '../helpers.ts'; import { doCachedLogin } from '../login.ts'; @@ -382,6 +383,62 @@ test('Sales Orders - Complete Shipment', async ({ browser }) => { await page.getByRole('cell', { name: 'Customer D' }).waitFor(); }); +// Check / uncheck / view / cancel shipment row actions +test('Sales Orders - Shipment Row Actions', async ({ browser }) => { + const page = await doCachedLogin(browser, { + url: 'sales/sales-order/7/shipments' + }); + + const shipmentReference = `SHIP-${Math.floor(Math.random() * 100000)}`; + + await clearTableFilters(page); + await page + .getByRole('button', { name: 'action-button-add-shipment' }) + .click(); + await page + .getByLabel('text-field-reference', { exact: true }) + .fill(shipmentReference); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Shipment created').first().waitFor(); + + const cell = page.getByRole('cell', { name: shipmentReference }); + + // Check the shipment + await clickOnRowMenu(cell); + await page.getByRole('menuitem', { name: 'Check Shipment' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Shipment marked as checked').first().waitFor(); + + // Uncheck the shipment + await clickOnRowMenu(cell); + await page.getByRole('menuitem', { name: 'Uncheck Shipment' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Shipment marked as unchecked').first().waitFor(); + + // Open the "complete shipment" form, then cancel it + await clickOnRowMenu(cell); + await page.getByRole('menuitem', { name: 'Complete Shipment' }).click(); + await page.getByRole('button', { name: 'Cancel' }).click(); + + // View the parent sales order from the global shipments table + await navigate(page, 'sales/index/shipments'); + await clearTableFilters(page); + await page.getByLabel('table-search-input').fill(shipmentReference); + await clickOnRowMenu(page.getByRole('cell', { name: shipmentReference })); + await page.getByRole('menuitem', { name: 'View Sales Order' }).click(); + await page.getByText('Sales Order: SO0007').first().waitFor(); + + // Cancel the shipment + await loadTab(page, 'Shipments'); + await clearTableFilters(page); + await clickOnRowMenu(page.getByRole('cell', { name: shipmentReference })); + await page.getByRole('menuitem', { name: 'Cancel' }).click(); + await page.getByRole('button', { name: 'Delete' }).click(); + await page.getByRole('cell', { name: shipmentReference }).waitFor({ + state: 'detached' + }); +}); + test('Sales Orders - Duplicate', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'sales/sales-order/14/detail' @@ -482,3 +539,130 @@ test('Sales Orders - Price Breaks', async ({ browser }) => { page.getByRole('textbox', { name: 'number-field-sale_price' }) ).toHaveValue('78', { timeout: 500 }); }); + +// Exercise the row and table actions on the sales order line item table +test('Sales Orders - Line Item Actions', async ({ browser }) => { + const page = await doCachedLogin(browser, { url: 'sales/sales-order/11/' }); + + // Work on a duplicate, to avoid modifying fixture data + await page.getByRole('button', { name: 'action-menu-order-actions' }).click(); + await page.getByRole('menuitem', { name: 'Duplicate' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Pending').first().waitFor(); + + await loadTab(page, 'Line Items'); + await page.waitForLoadState('networkidle'); + + // Add a new line item + await page.getByRole('button', { name: 'action-menu-add-line-item' }).click(); + await page + .getByRole('menuitem', { name: 'action-menu-add-line-item-add' }) + .click(); + await page.getByLabel('related-field-part').fill('software'); + await page.getByRole('option', { name: 'Software License' }).click(); + await page.getByLabel('number-field-quantity').fill('3'); + await page.getByLabel('text-field-reference').fill('New Line'); + await page.getByRole('button', { name: 'Submit' }).click(); + + // Edit the new line item + await clickOnRowMenu(page.getByRole('cell', { name: 'New Line' })); + await page.getByRole('menuitem', { name: 'Edit' }).click(); + await page.getByLabel('text-field-reference').fill('Edited Line'); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByRole('cell', { name: 'Edited Line' }).waitFor(); + + // Duplicate the line item + await clickOnRowMenu(page.getByRole('cell', { name: 'Edited Line' })); + await page.getByRole('menuitem', { name: 'Duplicate' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.waitForLoadState('networkidle'); + + // Delete a line item + await clickOnRowMenu(page.getByRole('cell', { name: 'Edited Line' }).first()); + await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'Delete', exact: true }).click(); + await page.waitForLoadState('networkidle'); + + // Open the other row actions, where available + const region = page.getByRole('region', { name: 'Line Items', exact: true }); + await region.getByLabel('Select all records').click(); + + await region + .getByRole('button', { name: 'action-button-allocate-stock' }) + .click(); + await page.getByRole('button', { name: 'Cancel' }).click(); + + await region + .getByRole('button', { name: 'action-button-order-parts' }) + .click(); + await page.getByRole('banner').getByRole('button').click(); +}); + +test('Sales Orders - Parametric View', async ({ browser }) => { + const page = await doCachedLogin(browser, { + url: 'sales/index/salesorders' + }); + + await showParametricView(page); + await clearTableFilters(page); + + // Columns and data are rendered in the parametric table + await page.getByRole('button', { name: 'Customer' }).waitFor(); + await page.getByRole('cell', { name: 'SO0001' }).waitFor(); + + // Search within the parametric table + await tableSearch(page, 'SO0001'); + await page.getByRole('cell', { name: 'SO0001' }).waitFor(); + await expect(page.getByRole('cell', { name: 'SO0002' })).toHaveCount(0); +}); + +// Edit and remove stock allocations (allocation forms) +test('Sales Orders - Edit Allocations', async ({ browser }) => { + const page = await doCachedLogin(browser, { url: 'sales/sales-order/11/' }); + + // Work on a duplicate, to avoid modifying fixture data + await page.getByRole('button', { name: 'action-menu-order-actions' }).click(); + await page.getByRole('menuitem', { name: 'Duplicate' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Pending').first().waitFor(); + + await loadTab(page, 'Line Items'); + await page + .getByRole('button', { name: 'action-button-auto-allocate-' }) + .click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Stock allocation complete').first().waitFor(); + + // Open the edit allocation form (serialized stock, so quantity is fixed) + await loadTab(page, 'Allocated Stock'); + await page.waitForLoadState('networkidle'); + const row = page.getByRole('row').nth(1); + await row.getByLabel(/row-action-menu-/i).click(); + await page.getByRole('menuitem', { name: 'Edit' }).click(); + await page.waitForLoadState('networkidle'); + await page.getByLabel('number-field-quantity').waitFor(); + await page.getByRole('button', { name: 'Cancel' }).click(); + + // Remove the allocation + await page + .getByRole('row') + .nth(1) + .getByLabel(/row-action-menu-/i) + .click(); + await page.getByRole('menuitem', { name: 'Remove' }).click(); + await page.getByRole('button', { name: 'Remove', exact: true }).click(); + await page.waitForLoadState('networkidle'); + + // Open the manual allocation form + await loadTab(page, 'Line Items'); + await clickOnRowMenu(page.getByRole('cell', { name: 'widget.blue' }).first()); + await page.getByRole('menuitem', { name: 'Allocate stock' }).click(); + await page.getByLabel('related-field-stock_item').waitFor(); + await page.getByRole('button', { name: 'Cancel' }).click(); + + // Cancel the order to release allocated stock + await page.getByRole('button', { name: 'action-menu-order-actions' }).click(); + await page.getByRole('menuitem', { name: 'Cancel' }).click(); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByText('Cancelled').first().waitFor(); +});