From aa84e9a01dd0f77bc7da338807e61654172831a4 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 9 Apr 2026 12:11:57 +0200 Subject: [PATCH 1/6] add new pages --- src/fixtures/fixtures.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fixtures/fixtures.ts b/src/fixtures/fixtures.ts index ede5d0b..ff883b5 100644 --- a/src/fixtures/fixtures.ts +++ b/src/fixtures/fixtures.ts @@ -33,6 +33,7 @@ import PutawayDetailsPage from '@/pages/putaway/putawayDetails/PutawayDetailsPag import ReceivingPage from '@/pages/receiving/ReceivingPage'; import OldViewShipmentPage from '@/pages/stockMovementShow/OldViewShipmentPage'; import StockMovementShowPage from '@/pages/stockMovementShow/StockMovementShowPage'; +import EditTransactionPage from '@/pages/transactions/EditTransactionPgage'; import TransactionListPage from '@/pages/transactions/TransactionListPage'; import CreateUserPage from '@/pages/user/CreateUserPage'; import EditUserPage from '@/pages/user/editUser/EditUserPage'; @@ -71,6 +72,7 @@ type Fixtures = { oldViewShipmentPage: OldViewShipmentPage; putawayListPage: PutawayListPage; productEditPage: ProductEditPage; + editTransactionPage: EditTransactionPage; // COMPONENTS navbar: Navbar; locationChooser: LocationChooser; @@ -145,6 +147,8 @@ export const test = baseTest.extend({ use(new OldViewShipmentPage(page)), putawayListPage: async ({ page }, use) => use(new PutawayListPage(page)), productEditPage: async ({ page }, use) => use(new ProductEditPage(page)), + editTransactionPage: async ({ page }, use) => + use(new EditTransactionPage(page)), // COMPONENTS navbar: async ({ page }, use) => use(new Navbar(page)), locationChooser: async ({ page }, use) => use(new LocationChooser(page)), From 82b67960d13241b563bfc2e31c5bc49b720b9bf7 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 9 Apr 2026 12:13:14 +0200 Subject: [PATCH 2/6] add new elements to existing pages --- .../product/productShow/sections/RecordStockSection.ts | 3 +++ src/pages/putaway/steps/CompleteStep.ts | 8 ++++++++ src/pages/transactions/components/TransactionTable.ts | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/src/pages/product/productShow/sections/RecordStockSection.ts b/src/pages/product/productShow/sections/RecordStockSection.ts index 3bb3142..811ddfd 100644 --- a/src/pages/product/productShow/sections/RecordStockSection.ts +++ b/src/pages/product/productShow/sections/RecordStockSection.ts @@ -2,13 +2,16 @@ import { Page } from '@playwright/test'; import BasePageModel from '@/pages/BasePageModel'; import LineItemsTable from '@/pages/product/productShow/sections/components/LineItemsTable'; +import RecordStockTable from '@/pages/product/productShow/sections/components/RecortStockTable'; class RecordStockSection extends BasePageModel { lineItemsTable: LineItemsTable; + recordStockTable: RecordStockTable; constructor(page: Page) { super(page); this.lineItemsTable = new LineItemsTable(page); + this.recordStockTable = new RecordStockTable(page); } } diff --git a/src/pages/putaway/steps/CompleteStep.ts b/src/pages/putaway/steps/CompleteStep.ts index e4a838e..c7bc321 100644 --- a/src/pages/putaway/steps/CompleteStep.ts +++ b/src/pages/putaway/steps/CompleteStep.ts @@ -41,6 +41,14 @@ class CompleteStep extends BasePageModel { get editButton() { return this.page.getByTestId('edit-button').first(); } + + get validationOnQtyInReceivingBin() { + return this.page + .locator('[class*="alert"]') + .getByText( + /Quantity available 0 is less than quantity to putaway \d+ for product/ + ); + } } export default CompleteStep; diff --git a/src/pages/transactions/components/TransactionTable.ts b/src/pages/transactions/components/TransactionTable.ts index 470f944..57a77bb 100644 --- a/src/pages/transactions/components/TransactionTable.ts +++ b/src/pages/transactions/components/TransactionTable.ts @@ -24,6 +24,12 @@ class TransactionTable extends BasePageModel { .locator('.action-menu-item') .getByRole('link', { name: 'Delete' }); } + + get editButton() { + return this.page + .locator('.action-menu-item') + .getByRole('link', { name: 'Edit' }); + } } class Row extends BasePageModel { From f251e26bec51664f528e97a0ec9c6b6a66496d26 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 9 Apr 2026 12:14:08 +0200 Subject: [PATCH 3/6] add record stock table --- .../sections/components/RecortStockTable.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/pages/product/productShow/sections/components/RecortStockTable.ts diff --git a/src/pages/product/productShow/sections/components/RecortStockTable.ts b/src/pages/product/productShow/sections/components/RecortStockTable.ts new file mode 100644 index 0000000..c26daf8 --- /dev/null +++ b/src/pages/product/productShow/sections/components/RecortStockTable.ts @@ -0,0 +1,35 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class RecordStockTable extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get section() { + return this.page.getByRole('region', { name: 'Record Stock' }); + } + + get table() { + return this.section.getByRole('table'); + } + + get rows() { + return this.table.getByRole('row'); + } + + row(index: number) { + return new Row(this.page, this.rows.nth(index)); + } +} + +class Row extends BasePageModel { + row: Locator; + constructor(page: Page, row: Locator) { + super(page); + this.row = row; + } +} + +export default RecordStockTable; From e2b4f3aabb0d739f258cf8987191d3ada9e1c047 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 9 Apr 2026 12:15:00 +0200 Subject: [PATCH 4/6] add edit transaction page and components --- .../transactions/EditTransactionPgage.ts | 19 ++++++++ .../components/TransactionDetailsHeaderTab.ts | 47 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/pages/transactions/EditTransactionPgage.ts create mode 100644 src/pages/transactions/components/TransactionDetailsHeaderTab.ts diff --git a/src/pages/transactions/EditTransactionPgage.ts b/src/pages/transactions/EditTransactionPgage.ts new file mode 100644 index 0000000..bb16057 --- /dev/null +++ b/src/pages/transactions/EditTransactionPgage.ts @@ -0,0 +1,19 @@ +import { Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; +import TransactionDetailsHeaderTab from '@/pages/transactions/components/TransactionDetailsHeaderTab'; + +class EditTransactionPage extends BasePageModel { + transactionDetailsHeaderTab: TransactionDetailsHeaderTab; + + constructor(page: Page) { + super(page); + this.transactionDetailsHeaderTab = new TransactionDetailsHeaderTab(page); + } + + get saveButton() { + return this.page.getByRole('button', { name: 'Save' }); + } +} + +export default EditTransactionPage; diff --git a/src/pages/transactions/components/TransactionDetailsHeaderTab.ts b/src/pages/transactions/components/TransactionDetailsHeaderTab.ts new file mode 100644 index 0000000..296e28c --- /dev/null +++ b/src/pages/transactions/components/TransactionDetailsHeaderTab.ts @@ -0,0 +1,47 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class TransactionDetailsHeaderTab extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get section() { + return this.page.locator('#transaction-header'); + } + + get table() { + return this.section.getByRole('table'); + } + + get rows() { + return this.table.getByRole('row'); + } + + row(index: number) { + return new Row(this.page, this.rows.nth(index)); + } +} + +class Row extends BasePageModel { + row: Locator; + constructor(page: Page, row: Locator) { + super(page); + this.row = row; + } + + get transactionDateMinuteSelect() { + return this.row.locator('select[name="transactionDate_minute"]'); + } + + async decreaseMinute() { + const currentValue = await this.transactionDateMinuteSelect.inputValue(); + const current = parseInt(currentValue || '0', 10); + const next = (current - 1 + 60) % 60; + const formatted = String(next).padStart(2, '0'); + await this.transactionDateMinuteSelect.selectOption(formatted); + } +} + +export default TransactionDetailsHeaderTab; From 845d52577f265f725e8788b04df6038d3b1f8e9c Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 9 Apr 2026 12:15:33 +0200 Subject: [PATCH 5/6] add test for validate qty removed from receiving bin --- ...dationOnQtyRemovedFromReceivingBin.test.ts | 277 ++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts diff --git a/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts b/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts new file mode 100644 index 0000000..5a9fc9a --- /dev/null +++ b/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts @@ -0,0 +1,277 @@ +import Navbar from '@/components/Navbar'; +import AppConfig from '@/config/AppConfig'; +import { ShipmentType } from '@/constants/ShipmentType'; +import { expect, test } from '@/fixtures/fixtures'; +import ProductShowPage from '@/pages/product/productShow/ProductShowPage'; +import { StockMovementResponse } from '@/types'; +import RefreshCachesUtils from '@/utils/RefreshCaches'; +import { getShipmentId, getShipmentItemId } from '@/utils/shipmentUtils'; + +test.describe('Assert validation on qty removed from receiving bin', () => { + let STOCK_MOVEMENT: StockMovementResponse; + + test.beforeEach( + async ({ + supplierLocationService, + stockMovementService, + productService, + receivingService, + }) => { + const supplierLocation = await supplierLocationService.getLocation(); + STOCK_MOVEMENT = await stockMovementService.createInbound({ + originId: supplierLocation.id, + }); + + productService.setProduct('5'); + const product = await productService.getProduct(); + productService.setProduct('4'); + const product2 = await productService.getProduct(); + + await stockMovementService.addItemsToInboundStockMovement( + STOCK_MOVEMENT.id, + [ + { productId: product.id, quantity: 10 }, + { productId: product2.id, quantity: 10 }, + ] + ); + + await stockMovementService.sendInboundStockMovement(STOCK_MOVEMENT.id, { + shipmentType: ShipmentType.AIR, + }); + + const { data: stockMovement } = + await stockMovementService.getStockMovement(STOCK_MOVEMENT.id); + const shipmentId = getShipmentId(stockMovement); + const { data: receipt } = await receivingService.getReceipt(shipmentId); + const receivingBin = + AppConfig.instance.receivingBinPrefix + STOCK_MOVEMENT.identifier; + + await receivingService.createReceivingBin(shipmentId, receipt); + + await receivingService.updateReceivingItems(shipmentId, [ + { + shipmentItemId: getShipmentItemId(receipt, 0, 0), + quantityReceiving: 10, + binLocationName: receivingBin, + }, + { + shipmentItemId: getShipmentItemId(receipt, 0, 1), + quantityReceiving: 10, + binLocationName: receivingBin, + }, + ]); + await receivingService.completeReceipt(shipmentId); + } + ); + + test.afterEach( + async ({ + stockMovementShowPage, + stockMovementService, + navbar, + transactionListPage, + oldViewShipmentPage, + }) => { + await navbar.configurationButton.click(); + await navbar.transactions.click(); + for (let n = 1; n < 4; n++) { + await transactionListPage.deleteTransaction(1); + } + await stockMovementShowPage.goToPage(STOCK_MOVEMENT.id); + await stockMovementShowPage.detailsListTable.oldViewShipmentPage.click(); + await oldViewShipmentPage.undoStatusChangeButton.click(); + await stockMovementShowPage.isLoaded(); + await stockMovementShowPage.rollbackButton.click(); + + await stockMovementService.deleteStockMovement(STOCK_MOVEMENT.id); + } + ); + + test('Assert validation on qty removed from receiving bin', async ({ + page, + transactionListPage, + editTransactionPage, + stockMovementShowPage, + createPutawayPage, + internalLocationService, + productShowPage, + putawayDetailsPage, + productService, + putawayListPage, + browser, + navbar, + }) => { + const receivingBin = + AppConfig.instance.receivingBinPrefix + STOCK_MOVEMENT.identifier; + productService.setProduct('5'); + const product = await productService.getProduct(); + productService.setProduct('4'); + const product2 = await productService.getProduct(); + const internalLocation = await internalLocationService.getLocation(); + + await test.step('Edit transaction date of transfer in', async () => { + await page.goto('./dashboard'); + await navbar.configurationButton.click(); + await navbar.transactions.click(); + // eslint-disable-next-line playwright/no-networkidle + await page.waitForLoadState('networkidle'); + await transactionListPage.table.row(1).actionsButton.click(); + await transactionListPage.table.editButton.click(); + await editTransactionPage.transactionDetailsHeaderTab + .row(2) + .transactionDateMinuteSelect.click(); + await editTransactionPage.transactionDetailsHeaderTab + .row(2) + .decreaseMinute(); + await editTransactionPage.saveButton.click(); + }); + + await test.step('Go to create putaway page', async () => { + await stockMovementShowPage.goToPage(STOCK_MOVEMENT.id); + await stockMovementShowPage.isLoaded(); + await RefreshCachesUtils.refreshCaches({ + navbar, + }); + await navbar.inbound.click(); + await navbar.createPutaway.click(); + await createPutawayPage.isLoaded(); + }); + + await test.step('Start putaway', async () => { + await createPutawayPage.table + .row(0) + .getExpandBinLocation(receivingBin) + .click(); + await expect( + createPutawayPage.table.row(1).getProductName(product.name) + ).toBeVisible(); + await expect( + createPutawayPage.table.row(2).getProductName(product2.name) + ).toBeVisible(); + await createPutawayPage.table.row(1).checkbox.click(); + await createPutawayPage.table.row(2).checkbox.click(); + await createPutawayPage.startPutawayButton.click(); + await createPutawayPage.startStep.isLoaded(); + }); + + await test.step('Select bin to putaway and go to next page', async () => { + await createPutawayPage.startStep.table.row(1).putawayBinSelect.click(); + await createPutawayPage.startStep.table + .row(1) + .getPutawayBin(internalLocation.name) + .click(); + await createPutawayPage.startStep.table.row(2).putawayBinSelect.click(); + await createPutawayPage.startStep.table + .row(2) + .getPutawayBin(internalLocation.name) + .click(); + await createPutawayPage.startStep.nextButton.click(); + await createPutawayPage.completeStep.isLoaded(); + }); + + await test.step('Open new tab and edit qty to 0 in receiving bin on stock card', async () => { + const newPage = await browser.newPage(); + const newProductShowPage = new ProductShowPage(newPage); + const newNavbar = new Navbar(newPage); + await newProductShowPage.goToPage(product.id); + await newProductShowPage.recordStockButton.click(); + await newProductShowPage.recordStock.lineItemsTable + .row(1) + .newQuantity.getByRole('textbox') + .fill('0'); + await newProductShowPage.recordStock.lineItemsTable.saveButton.click(); + await newNavbar.profileButton.click(); + await newNavbar.refreshCachesButton.click(); + await newPage.close(); + }); + + await test.step('Try to complete putaway and assert error message', async () => { + await createPutawayPage.completeStep.isLoaded(); + await createPutawayPage.completeStep.completePutawayButton.click(); + await expect( + createPutawayPage.completeStep.validationOnQtyInReceivingBin + ).toBeVisible(); + }); + + await test.step('Go backward and assert validatiion', async () => { + await createPutawayPage.completeStep.editButton.click(); + await createPutawayPage.startStep.isLoaded(); + await expect(createPutawayPage.startStep.saveButton).toBeDisabled(); + await expect(createPutawayPage.startStep.nextButton).toBeDisabled(); + await createPutawayPage.startStep.table.row(1).editButton.click(); + await createPutawayPage.startStep.table.row(1).quantityInput.hover(); + await createPutawayPage.startStep.table.assertValidationOnQtyField( + 'Quantity cannot be greater than original putaway item quantity' + ); + }); + + await test.step('Delete invalid line', async () => { + await expect(createPutawayPage.startStep.table.rows).toHaveCount(3); + await createPutawayPage.startStep.table.row(1).deleteButton.click(); + await expect(createPutawayPage.startStep.table.rows).toHaveCount(2); + await expect(createPutawayPage.startStep.saveButton).toBeEnabled(); + await expect(createPutawayPage.startStep.nextButton).toBeEnabled(); + await createPutawayPage.startStep.saveButton.click(); + }); + + await test.step('Edit qty to lower in receiving bin when putaway started', async () => { + await productShowPage.goToPage(product2.id); + await productShowPage.recordStockButton.click(); + await productShowPage.recordStock.lineItemsTable + .row(2) + .newQuantity.getByRole('textbox') + .fill('5'); + await productShowPage.recordStock.lineItemsTable.saveButton.click(); + await RefreshCachesUtils.refreshCaches({ + navbar, + }); + }); + + await test.step('Go to putaway list page and edit created pending putaway', async () => { + await putawayListPage.goToPage(); + await putawayListPage.isLoaded(); + const row = putawayListPage.table.row(1); + await row.actionsButton.click(); + await row.viewOrderDetails.click(); + await putawayDetailsPage.isLoaded(); + }); + + await test.step('Edit pending putaway and assert validation on qty', async () => { + await putawayDetailsPage.editButton.click(); + await createPutawayPage.startStep.isLoaded(); + await expect(createPutawayPage.startStep.saveButton).toBeDisabled(); + await expect(createPutawayPage.startStep.nextButton).toBeDisabled(); + await createPutawayPage.startStep.table.row(1).editButton.click(); + await createPutawayPage.startStep.table.row(1).quantityInput.hover(); + await createPutawayPage.startStep.table.assertValidationOnQtyField( + 'Quantity cannot be greater than original putaway item quantity' + ); + }); + + await test.step('Edit qty in pending putaway', async () => { + await createPutawayPage.startStep.table.row(1).quantityInput.fill('5'); + await expect(createPutawayPage.startStep.saveButton).toBeEnabled(); + await expect(createPutawayPage.startStep.nextButton).toBeEnabled(); + }); + + await test.step('Complete putaway', async () => { + await createPutawayPage.startStep.nextButton.click(); + await createPutawayPage.completeStep.isLoaded(); + await createPutawayPage.completeStep.completePutawayButton.click(); + await putawayDetailsPage.isLoaded(); + await expect(putawayDetailsPage.statusTag).toHaveText('Completed'); + }); + + await test.step('Assert putaway bin and qty on stock card', async () => { + await productShowPage.goToPage(product2.id); + await productShowPage.inStockTab.click(); + await productShowPage.inStockTabSection.isLoaded(); + await expect( + productShowPage.inStockTabSection.row(2).binLocation + ).toHaveText(internalLocation.name); + await expect( + productShowPage.inStockTabSection.row(2).quantityOnHand + ).toHaveText('5'); + }); + }); +}); From c5f87ac1ef570d4f0c7ca9d324185f66267c8767 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Fri, 10 Apr 2026 15:48:09 +0200 Subject: [PATCH 6/6] add improvements after review --- .../components/TransactionDetailsHeaderTab.ts | 10 +++++----- .../validationOnQtyRemovedFromReceivingBin.test.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/transactions/components/TransactionDetailsHeaderTab.ts b/src/pages/transactions/components/TransactionDetailsHeaderTab.ts index 296e28c..842b9f5 100644 --- a/src/pages/transactions/components/TransactionDetailsHeaderTab.ts +++ b/src/pages/transactions/components/TransactionDetailsHeaderTab.ts @@ -36,11 +36,11 @@ class Row extends BasePageModel { } async decreaseMinute() { - const currentValue = await this.transactionDateMinuteSelect.inputValue(); - const current = parseInt(currentValue || '0', 10); - const next = (current - 1 + 60) % 60; - const formatted = String(next).padStart(2, '0'); - await this.transactionDateMinuteSelect.selectOption(formatted); + const selectedValue = await this.transactionDateMinuteSelect.inputValue(); + const parsedValue = parseInt(selectedValue || '0', 10); + const nextMinute = (parsedValue - 1 + 60) % 60; + const optionToSelect = String(nextMinute).padStart(2, '0'); + await this.transactionDateMinuteSelect.selectOption(optionToSelect); } } diff --git a/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts b/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts index 5a9fc9a..124703b 100644 --- a/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts +++ b/src/tests/putaway/validationOnQtyRemovedFromReceivingBin.test.ts @@ -193,7 +193,7 @@ test.describe('Assert validation on qty removed from receiving bin', () => { ).toBeVisible(); }); - await test.step('Go backward and assert validatiion', async () => { + await test.step('Go backward and assert validation', async () => { await createPutawayPage.completeStep.editButton.click(); await createPutawayPage.startStep.isLoaded(); await expect(createPutawayPage.startStep.saveButton).toBeDisabled();