From a63b23961efd61f5ea5e10f7158a5bc2fd7ca11b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 21 Apr 2025 04:25:42 +0000 Subject: [PATCH] Playwright test --- src/frontend/tests/helpers.ts | 9 +++++++ src/frontend/tests/pages/pui_build.spec.ts | 15 ++++++------ src/frontend/tests/pages/pui_part.spec.ts | 24 ++++++++++++++++++- .../tests/pages/pui_purchase_order.spec.ts | 11 +++++---- .../tests/pages/pui_sales_order.spec.ts | 13 +++++----- src/frontend/tests/pages/pui_stock.spec.ts | 19 ++++++++------- src/frontend/tests/pui_forms.spec.ts | 18 +++++++------- src/frontend/tests/pui_plugins.spec.ts | 15 ++++++------ src/frontend/tests/pui_settings.spec.ts | 10 ++++---- .../tests/settings/selectionList.spec.ts | 8 +++---- 10 files changed, 88 insertions(+), 54 deletions(-) diff --git a/src/frontend/tests/helpers.ts b/src/frontend/tests/helpers.ts index 443eff8dee..f9a02c1595 100644 --- a/src/frontend/tests/helpers.ts +++ b/src/frontend/tests/helpers.ts @@ -1,5 +1,14 @@ import { baseUrl } from './defaults'; +/** + * Helper function to submit a form dialog. + * Includes a small delay to allow the form values to be debounced + */ +export const submitForm = async (page) => { + await page.waitForTimeout(250); + await page.getByRole('button', { name: 'Submit' }).click(); +}; + /** * Open the filter drawer for the currently visible table * @param page - The page object diff --git a/src/frontend/tests/pages/pui_build.spec.ts b/src/frontend/tests/pages/pui_build.spec.ts index ec23ebc69d..75d3ad8ffb 100644 --- a/src/frontend/tests/pages/pui_build.spec.ts +++ b/src/frontend/tests/pages/pui_build.spec.ts @@ -6,7 +6,8 @@ import { getRowFromCell, loadTab, navigate, - setTableChoiceFilter + setTableChoiceFilter, + submitForm } from '../helpers.ts'; import { doCachedLogin } from '../login.ts'; @@ -133,7 +134,7 @@ test('Build Order - Edit', async ({ browser }) => { await page.getByLabel('date-field-start_date').fill('2026-09-09'); // Submit the form - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Expect error await page.getByText('Errors exist for one or more form fields').waitFor(); @@ -178,7 +179,7 @@ test('Build Order - Build Outputs', async ({ browser }) => { await page.getByLabel('text-field-batch_code').fill('BATCH12345'); await page.getByLabel('related-field-location').click(); await page.getByText('Reel Storage').click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Should be an error as the number of serial numbers doesn't match the quantity await page.getByText('Errors exist for one or more').waitFor(); @@ -187,7 +188,7 @@ test('Build Order - Build Outputs', async ({ browser }) => { // Fix the quantity await page.getByLabel('number-field-quantity').fill('2'); await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Check that new serial numbers have been created await page @@ -204,7 +205,7 @@ test('Build Order - Build Outputs', async ({ browser }) => { const row = await getRowFromCell(cell); await row.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Cancel' }).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Build outputs have been cancelled').waitFor(); // Complete the other output @@ -215,7 +216,7 @@ test('Build Order - Build Outputs', async ({ browser }) => { await page.getByLabel('related-field-location').click(); await page.getByText('Mechanical Lab').click(); await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Build outputs have been completed').waitFor(); }); @@ -365,7 +366,7 @@ test('Build Order - Duplicate', async ({ browser }) => { // Submit the duplicate request and ensure it completes await page.getByRole('button', { name: 'Submit' }).isEnabled(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByRole('tab', { name: 'Build Details' }).waitFor(); await page.getByRole('tab', { name: 'Build Details' }).click(); diff --git a/src/frontend/tests/pages/pui_part.spec.ts b/src/frontend/tests/pages/pui_part.spec.ts index f201563bc3..d6ba84e43c 100644 --- a/src/frontend/tests/pages/pui_part.spec.ts +++ b/src/frontend/tests/pages/pui_part.spec.ts @@ -3,7 +3,8 @@ import { clearTableFilters, getRowFromCell, loadTab, - navigate + navigate, + submitForm } from '../helpers'; import { doCachedLogin } from '../login'; @@ -113,6 +114,27 @@ test('Parts - BOM', async ({ browser }) => { await page.getByRole('button', { name: 'Close' }).click(); }); +test('Part - Editing', async ({ browser }) => { + const page = await doCachedLogin(browser, { url: 'part/104/details' }); + + await page.getByText('A square table - with blue paint').first().waitFor(); + + // Open part edit dialog + await page.keyboard.press('Control+E'); + + await page.getByLabel('text-field-keywords').fill('table furniture'); + + // Test URL validation + await page.getByLabel('text-field-link').fill('htxp-??QQQ++'); + await submitForm(page); + await page.getByText('Enter a valid URL.').waitFor(); + + // Fill with an empty URL + await page.getByLabel('text-field-link').fill(''); + await submitForm(page); + await page.getByText('Item Updated').waitFor(); +}); + test('Parts - Locking', async ({ browser }) => { const page = await doCachedLogin(browser, { url: 'part/104/bom' }); await loadTab(page, 'Bill of Materials'); diff --git a/src/frontend/tests/pages/pui_purchase_order.spec.ts b/src/frontend/tests/pages/pui_purchase_order.spec.ts index 38875159fe..108d6ccabf 100644 --- a/src/frontend/tests/pages/pui_purchase_order.spec.ts +++ b/src/frontend/tests/pages/pui_purchase_order.spec.ts @@ -9,7 +9,8 @@ import { loadTab, navigate, openFilterDrawer, - setTableChoiceFilter + setTableChoiceFilter, + submitForm } from '../helpers.ts'; import { doCachedLogin } from '../login.ts'; @@ -180,7 +181,7 @@ test('Purchase Orders - General', async ({ browser }) => { await page.getByRole('button', { name: 'Submit' }).isEnabled(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByRole('tab', { name: 'Details' }).waitFor(); }); @@ -289,7 +290,7 @@ test('Purchase Orders - Order Parts', async ({ browser }) => { await page.getByLabel('action-button-add-to-selected').click(); await page.getByLabel('number-field-quantity').fill('100'); await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page .getByText('All selected parts added to a purchase order') .waitFor(); @@ -357,7 +358,7 @@ test('Purchase Orders - Receive Items', async ({ browser }) => { // Short timeout to allow for debouncing await page.waitForTimeout(200); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Items received').waitFor(); await loadTab(page, 'Received Stock'); @@ -380,7 +381,7 @@ test('Purchase Orders - Duplicate', async ({ browser }) => { // Submit the duplicate request and ensure it completes await page.getByRole('button', { name: 'Submit' }).isEnabled(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByRole('tab', { name: 'Order Details' }).waitFor(); await page.getByRole('tab', { name: 'Order Details' }).click(); diff --git a/src/frontend/tests/pages/pui_sales_order.spec.ts b/src/frontend/tests/pages/pui_sales_order.spec.ts index 37ef9a8d2d..90aa0b2ead 100644 --- a/src/frontend/tests/pages/pui_sales_order.spec.ts +++ b/src/frontend/tests/pages/pui_sales_order.spec.ts @@ -4,7 +4,8 @@ import { clearTableFilters, globalSearch, loadTab, - setTableChoiceFilter + setTableChoiceFilter, + submitForm } from '../helpers.ts'; import { doCachedLogin } from '../login.ts'; @@ -82,7 +83,7 @@ test('Sales Orders - Basic Tests', async ({ browser }) => { await page.getByText('Selling stuff').first().waitFor(); await page.getByText('On Hold').first().waitFor(); await page.getByRole('button', { name: 'Issue Order' }).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Order should now be "in progress" await page.getByText('In Progress').first().waitFor(); @@ -96,7 +97,7 @@ test('Sales Orders - Basic Tests', async ({ browser }) => { // Mark the order as "on hold" again await page.getByLabel('action-menu-order-actions-hold').click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('On Hold').first().waitFor(); await page.getByRole('button', { name: 'Issue Order' }).waitFor(); @@ -119,7 +120,7 @@ test('Sales Orders - Shipments', async ({ browser }) => { await page.getByLabel('action-button-add-shipment').click(); await page.getByLabel('text-field-tracking_number').fill('1234567890'); await page.getByLabel('text-field-invoice_number').fill('9876543210'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Expected field error await page @@ -152,7 +153,7 @@ test('Sales Orders - Shipments', async ({ browser }) => { // Change the tracking number await page.getByLabel('text-field-tracking_number').fill(tracking_number); await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Click through to a particular shipment await page.getByLabel('row-action-menu-0').click(); @@ -216,7 +217,7 @@ test('Sales Orders - Duplicate', async ({ browser }) => { // Submit the duplicate request and ensure it completes await page.getByRole('button', { name: 'Submit' }).isEnabled(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByRole('tab', { name: 'Order Details' }).waitFor(); await page.getByRole('tab', { name: 'Order Details' }).click(); diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts index f4683570e1..6e97705983 100644 --- a/src/frontend/tests/pages/pui_stock.spec.ts +++ b/src/frontend/tests/pages/pui_stock.spec.ts @@ -5,7 +5,8 @@ import { loadTab, navigate, openFilterDrawer, - setTableChoiceFilter + setTableChoiceFilter, + submitForm } from '../helpers.js'; import { doCachedLogin } from '../login.js'; @@ -136,7 +137,7 @@ test('Stock - Serial Numbers', async ({ browser }) => { // Add delay to account to field debounce await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Expected error messages await page.getByText('Errors exist for one or more form fields').waitFor(); @@ -148,7 +149,7 @@ test('Stock - Serial Numbers', async ({ browser }) => { // Now, with correct quantity await page.getByLabel('number-field-quantity').fill('51'); await page.waitForTimeout(250); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.waitForTimeout(250); await page @@ -172,7 +173,7 @@ test('Stock - Serial Navigation', async ({ browser }) => { await page.getByLabel('action-menu-stock-actions').click(); await page.getByLabel('action-menu-stock-actions-search').click(); await page.getByLabel('text-field-serial').fill('359'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Start at serial 359 await page.getByText('359', { exact: true }).first().waitFor(); @@ -184,7 +185,7 @@ test('Stock - Serial Navigation', async ({ browser }) => { await page.getByLabel('action-button-find-serial').click(); await page.getByLabel('text-field-serial').fill('200'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Serial Number: 200').waitFor(); await page.getByText('200', { exact: true }).first().waitFor(); @@ -201,7 +202,7 @@ test('Stock - Serialize', async ({ browser }) => { await page.getByLabel('text-field-serial_numbers').fill('200-250'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page .getByText('Group range 200-250 exceeds allowed quantity') .waitFor(); @@ -251,7 +252,7 @@ test('Stock - Stock Actions', async ({ browser }) => { await launchStockAction('add'); await page.getByLabel('number-field-quantity').fill('12'); await setStockStatus('Lost'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Lost').first().waitFor(); await page.getByText('Unavailable').first().waitFor(); @@ -261,7 +262,7 @@ test('Stock - Stock Actions', async ({ browser }) => { await launchStockAction('remove'); await page.getByLabel('number-field-quantity').fill('99'); await setStockStatus('Damaged'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('36').first().waitFor(); await page.getByText('Damaged').first().waitFor(); @@ -270,7 +271,7 @@ test('Stock - Stock Actions', async ({ browser }) => { await launchStockAction('count'); await page.getByLabel('number-field-quantity').fill('123'); await setStockStatus('Incoming goods inspection'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('123').first().waitFor(); await page.getByText('Custom Status').first().waitFor(); diff --git a/src/frontend/tests/pui_forms.spec.ts b/src/frontend/tests/pui_forms.spec.ts index 18a1a655f6..53877746d9 100644 --- a/src/frontend/tests/pui_forms.spec.ts +++ b/src/frontend/tests/pui_forms.spec.ts @@ -1,6 +1,6 @@ /** Unit tests for form validation, rendering, etc */ import test from 'playwright/test'; -import { navigate } from './helpers'; +import { navigate, submitForm } from './helpers'; import { doCachedLogin } from './login'; test('Forms - Stock Item Validation', async ({ browser }) => { @@ -14,7 +14,7 @@ test('Forms - Stock Item Validation', async ({ browser }) => { // Create new stock item form await page.getByLabel('action-button-add-stock-item').click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Check for validation errors await page.getByText('Form Error').waitFor(); @@ -31,7 +31,7 @@ test('Forms - Stock Item Validation', async ({ browser }) => { await page.getByLabel('number-field-quantity').fill('-1'); await page.getByLabel('related-field-part').click(); await page.getByRole('option', { name: /1551AGY/ }).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Check for validation errors await page.getByText('Errors exist for one or more form fields').waitFor(); @@ -46,14 +46,14 @@ test('Forms - Stock Item Validation', async ({ browser }) => { // Create the stock item await page.getByLabel('number-field-quantity').fill('123'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Edit the resulting stock item await page.getByLabel('action-menu-stock-item-actions').click(); await page.getByLabel('action-menu-stock-item-actions-edit').click(); await page.getByLabel('number-field-purchase_price').fill('-1'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Errors exist for one or more form fields').waitFor(); await page .getByText('Ensure this value is greater than or equal to 0') @@ -68,7 +68,7 @@ test('Forms - Stock Item Validation', async ({ browser }) => { // Correct the price await page.getByLabel('number-field-purchase_price').fill('1.2345'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Item Updated').waitFor(); // Ensure the stock item has been updated correctly @@ -89,7 +89,7 @@ test('Forms - Supplier Validation', async ({ browser }) => { await page.getByLabel('action-button-add-company').click(); await page.getByLabel('text-field-website').fill('not-a-website'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Check for validation errors await page.getByText('Form Error').waitFor(); @@ -111,7 +111,7 @@ test('Forms - Supplier Validation', async ({ browser }) => { .getByLabel('text-field-website') .fill('https://www.test-website.co.uk'); await page.getByLabel('text-field-name').fill(supplierName); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('A description').first().waitFor(); await page @@ -123,7 +123,7 @@ test('Forms - Supplier Validation', async ({ browser }) => { await page.waitForURL('**/purchasing/index/**'); await page.getByLabel('action-button-add-company').click(); await page.getByLabel('text-field-name').fill(supplierName); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Is prevented, due to uniqueness requirements await page.getByText('Form Error').waitFor(); diff --git a/src/frontend/tests/pui_plugins.spec.ts b/src/frontend/tests/pui_plugins.spec.ts index 91402f20d2..93b63d3e26 100644 --- a/src/frontend/tests/pui_plugins.spec.ts +++ b/src/frontend/tests/pui_plugins.spec.ts @@ -5,7 +5,8 @@ import { clickOnRowMenu, loadTab, navigate, - setTableChoiceFilter + setTableChoiceFilter, + submitForm } from './helpers.js'; import { doCachedLogin } from './login.js'; import { setPluginState, setSettingState } from './settings.js'; @@ -45,14 +46,14 @@ test('Plugins - Settings', async ({ browser, request }) => { await page .getByLabel('number-field-value') .fill(originalValue == '999' ? '1000' : '999'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.waitForTimeout(500); // Change it back await page.getByLabel('edit-setting-NUMERICAL_SETTING').click(); await page.getByLabel('number-field-value').fill(originalValue); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Select supplier await page.getByLabel('edit-setting-SELECT_COMPANY').click(); @@ -81,7 +82,7 @@ test('Plugins - Functionality', async ({ browser }) => { // Activate the plugin (unless already activated) if ((await page.getByRole('menuitem', { name: 'Deactivate' }).count()) == 0) { await page.getByRole('menuitem', { name: 'Activate' }).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('The plugin was activated').waitFor(); await page.waitForTimeout(250); } @@ -89,7 +90,7 @@ test('Plugins - Functionality', async ({ browser }) => { // Deactivate the plugin again await clickOnRowMenu(cell); await page.getByRole('menuitem', { name: 'Deactivate' }).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('The plugin was deactivated').waitFor(); }); @@ -210,7 +211,7 @@ test('Plugins - Locate Item', async ({ browser, request }) => { // "Locate" this item await page.getByLabel('action-button-locate-item').click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Item location requested').waitFor(); // Show the location @@ -218,6 +219,6 @@ test('Plugins - Locate Item', async ({ browser, request }) => { await page.waitForTimeout(500); await page.getByLabel('action-button-locate-item').click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByText('Item location requested').waitFor(); }); diff --git a/src/frontend/tests/pui_settings.spec.ts b/src/frontend/tests/pui_settings.spec.ts index f4d27f2d32..b6a3730052 100644 --- a/src/frontend/tests/pui_settings.spec.ts +++ b/src/frontend/tests/pui_settings.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from './baseFixtures.js'; import { apiUrl } from './defaults.js'; -import { getRowFromCell, loadTab, navigate } from './helpers.js'; +import { getRowFromCell, loadTab, navigate, submitForm } from './helpers.js'; import { doCachedLogin } from './login.js'; import { setSettingState } from './settings.js'; @@ -149,8 +149,7 @@ test('Settings - Admin', async ({ browser }) => { const newDescription = `${oldDescription} (edited)`; await page.getByLabel('text-field-description').fill(newDescription); - await page.waitForTimeout(500); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); // Edit second item - 'Box (Large)' const boxCell = await page.getByRole('cell', { @@ -172,12 +171,11 @@ test('Settings - Admin', async ({ browser }) => { await roomRow.getByLabel(/row-action-menu-/i).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); await page.getByLabel('text-field-name').fill('Room'); - await page.waitForTimeout(500); + await page.waitForTimeout(200); await page .getByLabel('text-field-description') .fill(newDescription.replaceAll(' (edited)', '')); - await page.waitForTimeout(500); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); }); test('Settings - Admin - Barcode History', async ({ browser, request }) => { diff --git a/src/frontend/tests/settings/selectionList.spec.ts b/src/frontend/tests/settings/selectionList.spec.ts index 1f899ce78e..a022b79865 100644 --- a/src/frontend/tests/settings/selectionList.spec.ts +++ b/src/frontend/tests/settings/selectionList.spec.ts @@ -1,5 +1,5 @@ import { test } from '../baseFixtures'; -import { navigate } from '../helpers'; +import { navigate, submitForm } from '../helpers'; import { doCachedLogin } from '../login'; test('PUI - Admin - Parameter', async ({ browser }) => { @@ -53,7 +53,7 @@ test('PUI - Admin - Parameter', async ({ browser }) => { await page.getByLabel('action-button-add-selection-').click(); await page.getByLabel('text-field-name').fill('some list'); await page.getByLabel('text-field-description').fill('Listdescription'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByRole('cell', { name: 'some list' }).waitFor(); await page.waitForTimeout(200); @@ -74,7 +74,7 @@ test('PUI - Admin - Parameter', async ({ browser }) => { .locator('div') .first() .click(); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); await page.getByRole('cell', { name: 'my custom parameter' }).click(); // Fill parameter @@ -99,5 +99,5 @@ test('PUI - Admin - Parameter', async ({ browser }) => { .fill('my custom parameter'); await page.getByRole('option', { name: 'my custom parameter' }).click(); await page.getByLabel('choice-field-data').fill('2'); - await page.getByRole('button', { name: 'Submit' }).click(); + await submitForm(page); });