From 493572987afaf0937e6327c366f5f2de54122147 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:17:06 +0200 Subject: [PATCH 01/23] update receiving service --- src/api/ReceivingService.ts | 104 +++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/src/api/ReceivingService.ts b/src/api/ReceivingService.ts index 57410ac..7af1575 100644 --- a/src/api/ReceivingService.ts +++ b/src/api/ReceivingService.ts @@ -5,7 +5,8 @@ import BaseServiceModel from '@/api/BaseServiceModel'; import { PartialReceiptStatus } from '@/constants/PartialReceiptStatus'; import { ApiResponse, - Container, ReceiptPayload, + Container, + ReceiptPayload, ReceiptResponse, ReceivingItemPayload, ShipmentItem, @@ -52,7 +53,7 @@ class ReceivingService extends BaseServiceModel { try { await this.changeReceiptStatus(id, PartialReceiptStatus.ROLLBACK); } catch (error) { - throw new Error('Problem rolling back partial receipt') + throw new Error('Problem rolling back partial receipt'); } } @@ -78,17 +79,21 @@ class ReceivingService extends BaseServiceModel { */ async updateReceivingItems( id: string, - items: ReceivingItemPayload[], + items: ReceivingItemPayload[] ): Promise { try { const receipt = await this.getReceipt(id); const shipmentItemsToUpdate = this.extractShipmentItemIds(items); - const containers = this.buildUpdatedContainers(receipt.data.containers, items, shipmentItemsToUpdate); + const containers = this.buildUpdatedContainers( + receipt.data.containers, + items, + shipmentItemsToUpdate + ); const payload: ReceiptPayload = { ...receipt.data, containers: containers, recipient: receipt?.data?.recipient?.id, - } + }; await this.request.post(`./api/partialReceiving/${id}`, { data: payload, }); @@ -133,14 +138,19 @@ class ReceivingService extends BaseServiceModel { async splitReceivingLine( id: string, originalReceiptItemId: string, - newLines: ReceivingItemPayload[], + newLines: ReceivingItemPayload[] ) { try { const receipt = await this.getReceipt(id); - const originalShipmentItem = this.findOriginalShipmentItem(receipt.data.containers, originalReceiptItemId); + const originalShipmentItem = this.findOriginalShipmentItem( + receipt.data.containers, + originalReceiptItemId + ); if (!originalShipmentItem) { - throw new Error(`Original shipment item with ID ${originalReceiptItemId} not found`); + throw new Error( + `Original shipment item with ID ${originalReceiptItemId} not found` + ); } this.validateQuantity(originalShipmentItem.quantityShipped, newLines); @@ -159,17 +169,26 @@ class ReceivingService extends BaseServiceModel { } } - private findOriginalShipmentItem(containers: Container[], receiptItemId: string): ShipmentItem | undefined { - return _.flatten(containers.map(c => c.shipmentItems)) - .find(item => item.receiptItemId === receiptItemId); + private findOriginalShipmentItem( + containers: Container[], + receiptItemId: string + ): ShipmentItem | undefined { + return _.flatten(containers.map((c) => c.shipmentItems)).find( + (item) => item.receiptItemId === receiptItemId + ); } - private validateQuantity(originalQuantityShipped: number | undefined, newLines: ReceivingItemPayload[]) { + private validateQuantity( + originalQuantityShipped: number | undefined, + newLines: ReceivingItemPayload[] + ) { const sumOfQuantityShipped = _.sumBy(newLines, 'quantityShipped'); const originalQty = originalQuantityShipped || 0; if (originalQty < sumOfQuantityShipped) { - throw new Error('Sum of quantity shipped is greater than the original quantity shipped'); + throw new Error( + 'Sum of quantity shipped is greater than the original quantity shipped' + ); } } @@ -178,41 +197,45 @@ class ReceivingService extends BaseServiceModel { originalReceiptItemId: string, newLines: ReceivingItemPayload[] ): UnflattenContainer[] { - const splittedItem = newLines.find(line => line.receiptItemId === originalReceiptItemId); - const linesToSave = newLines.filter(line => !line.receiptItemId); + const splittedItem = newLines.find( + (line) => line.receiptItemId === originalReceiptItemId + ); + const linesToSave = newLines.filter((line) => !line.receiptItemId); - return containers.map(container => { + return containers.map((container) => { const updatedShipmentItems = _.flatten( container.shipmentItems.map((shipmentItem: ShipmentItem) => { if (shipmentItem.receiptItemId === originalReceiptItemId) { - return [ - { ...shipmentItem, ...splittedItem }, - ...linesToSave - ]; + return [{ ...shipmentItem, ...splittedItem }, ...linesToSave]; } return shipmentItem; }) ); - return unflatten({ ...container, shipmentItems: updatedShipmentItems }) as UnflattenContainer; + return unflatten({ + ...container, + shipmentItems: updatedShipmentItems, + }) as UnflattenContainer; }); } - private buildPayload(receiptData: ReceiptResponse, containers: UnflattenContainer[]): ReceiptPayload { + private buildPayload( + receiptData: ReceiptResponse, + containers: UnflattenContainer[] + ): ReceiptPayload { return { ...receiptData, containers, - recipient: receiptData.recipient.id + recipient: receiptData.recipient.id, }; } private async saveSplitLines(id: string, payload: ReceiptPayload) { await this.request.post(`./api/partialReceiving/${id}`, { - data: payload + data: payload, }); } - private async changeReceiptStatus( id: string, status: PartialReceiptStatus @@ -233,42 +256,47 @@ class ReceivingService extends BaseServiceModel { private buildUpdatedContainers( containers: Container[], items: ReceivingItemPayload[], - shipmentItemsToUpdate: string[], + shipmentItemsToUpdate: string[] ): UnflattenContainer[] { return containers.map((container) => { return unflatten({ ...container, - shipmentItems: container.shipmentItems.map((shipmentItem: ShipmentItem) => { - if (shipmentItemsToUpdate.includes(shipmentItem.shipmentItemId)) { - const updatedItem = this.findItemToUpdate(items, shipmentItem.shipmentItemId); - return this.mergeShipmentItem(shipmentItem, updatedItem); - } + shipmentItems: container.shipmentItems.map( + (shipmentItem: ShipmentItem) => { + if (shipmentItemsToUpdate.includes(shipmentItem.shipmentItemId)) { + const updatedItem = this.findItemToUpdate( + items, + shipmentItem.shipmentItemId + ); + return this.mergeShipmentItem(shipmentItem, updatedItem); + } - return shipmentItem; - }), + return shipmentItem; + } + ), }) as UnflattenContainer; }); } private findItemToUpdate( items: ReceivingItemPayload[], - shipmentItemId: string, + shipmentItemId: string ): ReceivingItemPayload | undefined { return items.find((item) => item.shipmentItemId === shipmentItemId); } private mergeShipmentItem( original: ShipmentItem, - update?: ReceivingItemPayload, + update?: ReceivingItemPayload ): ShipmentItem { return { ...original, - binLocationId: update?.binLocationId ?? original.binLocationId, - quantityReceiving: update?.quantityReceiving ?? original.quantityReceiving, + 'binLocation.name': update?.binLocationName ?? original.binLocationName, + quantityReceiving: + update?.quantityReceiving ?? original.quantityReceiving, comment: update?.comment ?? original.comment, }; } - } export default ReceivingService; From 8aef1be4f48eebd943ac3258601559b9fef12ccc Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:18:05 +0200 Subject: [PATCH 02/23] add elements to navbar --- src/components/Navbar.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/Navbar.ts b/src/components/Navbar.ts index 7587ddf..a66dd26 100644 --- a/src/components/Navbar.ts +++ b/src/components/Navbar.ts @@ -31,6 +31,10 @@ class Navbar extends BasePageModel { return this.navbar.getByRole('menuitem', { name: 'Edit Profile' }); } + get refreshCachesButton() { + return this.navbar.getByRole('menuitem', { name: 'Refresh Caches' }); + } + // Nav Items get dashboard() { return this.getNavItem('Dashboard'); @@ -79,6 +83,18 @@ class Navbar extends BasePageModel { get listProducts() { return this.getNavItem('List Products'); } + + get inbound() { + return this.getNavItem('Inbound'); + } + + get createPutaway() { + return this.getNavItem('Create Putaway'); + } + + get transactions() { + return this.getNavItem('Transactions'); + } } export default Navbar; From 6479d17e61c6a3d2b6fd8e78ec296695ab2d938d Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:18:40 +0200 Subject: [PATCH 03/23] update appConfig --- src/config/AppConfig.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/config/AppConfig.ts b/src/config/AppConfig.ts index 7e3e14b..0789f9c 100644 --- a/src/config/AppConfig.ts +++ b/src/config/AppConfig.ts @@ -25,6 +25,7 @@ export enum LOCATION_KEY { DEPOT = 'depot', WARD = 'ward', NO_PICK_AND_PUTAWAY_STOCK = 'noPickAndPutawayStockDepot', + BIN_LOCATION = 'internalLocation', } export enum PRODUCT_KEY { @@ -256,6 +257,19 @@ class AppConfig { required: false, type: LocationTypeCode.DEPOT, }), + + internalLocation: new LocationConfig({ + id: env.get('LOCATION_INTERNAL').asString(), + key: LOCATION_KEY.BIN_LOCATION, + name: this.uniqueIdentifier.generateUniqueString('bin-location'), + requiredActivityCodes: new Set([ + ActivityCode.PICK_STOCK, + ActivityCode.PUTAWAY_STOCK, + ]), + required: false, + type: LocationTypeCode.BIN_LOCATION, + parentLocation: env.get('LOCATION_MAIN').required().asString(), + }), }; this.products = { @@ -292,7 +306,7 @@ class AppConfig { key: PRODUCT_KEY.FIVE, name: this.uniqueIdentifier.generateUniqueString('aa-product-five'), //'aa' part was added to improve visibility of ordering products alphabetically - quantity: 160, + quantity: 0, required: false, }), }; From 3e4cf165108ad2cee83dc4348a18d14fa11a1b35 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:19:04 +0200 Subject: [PATCH 04/23] update locationConfig --- src/config/LocationConfig.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/LocationConfig.ts b/src/config/LocationConfig.ts index a6ed3c4..27075aa 100644 --- a/src/config/LocationConfig.ts +++ b/src/config/LocationConfig.ts @@ -9,6 +9,7 @@ type LocationConfigProps = { key: string; requiredActivityCodes: Set; type: LocationTypeCode; + parentLocation?: string; } & ( | { id: string; @@ -29,6 +30,7 @@ class LocationConfig { type: LocationTypeCode; required: boolean; key: string; + parentLocation?: string; constructor({ key, @@ -37,6 +39,7 @@ class LocationConfig { requiredActivityCodes, type, required, + parentLocation, }: LocationConfigProps) { this.id = id || ''; this.name = name || ''; @@ -44,6 +47,7 @@ class LocationConfig { this.type = type; this.required = required ?? false; this.key = key; + this.parentLocation = parentLocation || ''; } /** Should create a new location for testing From 5c0d7311d8ef4269fc5166230b2c4b8fa48e4a20 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:19:33 +0200 Subject: [PATCH 05/23] update fixtures --- src/fixtures/fixtures.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/fixtures/fixtures.ts b/src/fixtures/fixtures.ts index 164f4d7..c37cb0c 100644 --- a/src/fixtures/fixtures.ts +++ b/src/fixtures/fixtures.ts @@ -30,8 +30,12 @@ import CreatePersonPage from '@/pages/people/CreatePersonPage'; import PersonsListPage from '@/pages/people/PersonsListPage'; import CreateProductPage from '@/pages/product/CreateProductPage'; import ProductShowPage from '@/pages/product/productShow/ProductShowPage'; +import CreatePutawayPage from '@/pages/putaway/CreatePutawayPage'; +import PutawayDetailsPage from '@/pages/putaway/putawayDetails/PutawayDetailsPage'; import ReceivingPage from '@/pages/receiving/ReceivingPage'; +import OldViewShipmentPage from '@/pages/stockMovementShow/OldViewShipmentPage'; import StockMovementShowPage from '@/pages/stockMovementShow/StockMovementShowPage'; +import TransactionListPage from '@/pages/transactions/TransactionListPage'; import CreateUserPage from '@/pages/user/CreateUserPage'; import EditUserPage from '@/pages/user/editUser/EditUserPage'; import UserListPage from '@/pages/user/UserListPage'; @@ -63,6 +67,10 @@ type Fixtures = { invoiceListPage: InvoiceListPage; personsListPage: PersonsListPage; createPersonPage: CreatePersonPage; + createPutawayPage: CreatePutawayPage; + putawayDetailsPage: PutawayDetailsPage; + transactionListPage: TransactionListPage; + oldViewShipmentPage: OldViewShipmentPage; // COMPONENTS navbar: Navbar; locationChooser: LocationChooser; @@ -81,6 +89,7 @@ type Fixtures = { depotLocationService: LocationData; wardLocationService: LocationData; noPickAndPutawayStockDepotService: LocationData; + internalLocationService: LocationData; // PRODUCT DATA mainProductService: ProductData; otherProductService: ProductData; @@ -129,6 +138,13 @@ export const test = baseTest.extend({ invoiceListPage: async ({ page }, use) => use(new InvoiceListPage(page)), personsListPage: async ({ page }, use) => use(new PersonsListPage(page)), createPersonPage: async ({ page }, use) => use(new CreatePersonPage(page)), + createPutawayPage: async ({ page }, use) => use(new CreatePutawayPage(page)), + putawayDetailsPage: async ({ page }, use) => + use(new PutawayDetailsPage(page)), + transactionListPage: async ({ page }, use) => + use(new TransactionListPage(page)), + oldViewShipmentPage: async ({ page }, use) => + use(new OldViewShipmentPage(page)), // COMPONENTS navbar: async ({ page }, use) => use(new Navbar(page)), locationChooser: async ({ page }, use) => use(new LocationChooser(page)), @@ -158,6 +174,8 @@ export const test = baseTest.extend({ use(new LocationData(LOCATION_KEY.WARD, page.request)), noPickAndPutawayStockDepotService: async ({ page }, use) => use(new LocationData(LOCATION_KEY.NO_PICK_AND_PUTAWAY_STOCK, page.request)), + internalLocationService: async ({ page }, use) => + use(new LocationData(LOCATION_KEY.BIN_LOCATION, page.request)), // PRODUCTS mainProductService: async ({ page }, use) => use(new ProductData(PRODUCT_KEY.ONE, page.request)), From a5a2d6168b4715c44e0d440d205d5578e1249caf Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:20:02 +0200 Subject: [PATCH 06/23] add elements to page --- src/pages/stockMovementShow/components/DetailsTable.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pages/stockMovementShow/components/DetailsTable.ts b/src/pages/stockMovementShow/components/DetailsTable.ts index 5831ac6..735525f 100644 --- a/src/pages/stockMovementShow/components/DetailsTable.ts +++ b/src/pages/stockMovementShow/components/DetailsTable.ts @@ -30,6 +30,14 @@ class DetailsTable extends BasePageModel { get destinationValue() { return this.destinationRow.locator('.value'); } + + get shipmentRow() { + return this.rows.filter({ hasText: 'Shipment' }); + } + + get oldViewShipmentPage() { + return this.shipmentRow.locator('.value').getByRole('link'); + } } export default DetailsTable; From 7343fbf39a13a9e23c4318f5d22a28e4500f90c0 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:20:30 +0200 Subject: [PATCH 07/23] update types --- src/types.d.ts | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/types.d.ts b/src/types.d.ts index 9bc5b82..ebf6bea 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -49,6 +49,7 @@ type LocationResponse = { name: string; code: string; }; + parentLocation?: string; }; type CreateLocationPayload = { @@ -201,6 +202,11 @@ type StockMovementResponse = { requestType: unknown; sourceType: unknown; picklist: { id: string }; + associations: { + shipment: { + id: string; + }; + }; }; type UpdateStockMovementItemsPayload = { @@ -244,7 +250,7 @@ type ReceiptResponse = { description: string; recipient: Recipient; isShipmentFromPurchaseOrder: boolean; -} +}; type Container = { containerId: string | null; @@ -253,7 +259,7 @@ type Container = { parentContainerName: string | null; containerType: string | null; shipmentItems: ShipmentItem[]; -} +}; type ContainerInfo = { id: string; @@ -307,8 +313,9 @@ type ShipmentItem = { unitOfMeasure: string; packSize: number; packsRequested: number; - original?: boolean, -} + original?: boolean; + 'binLocation.name': string; +}; type Recipient = { id: string; @@ -318,20 +325,21 @@ type Recipient = { email: string; username: string; roles: string[]; -} +}; type ReceivingItemPayload = { - shipmentItemId: string, - quantityReceiving?: number, - quantityShipped?: number, - comment?: string, - binLocationId?: string, - lotNumber?: string, - expirationDate?: string, - original?: boolean, - newLine?: boolean, - receiptItemId?: string | null, -} + shipmentItemId: string; + quantityReceiving?: number; + quantityShipped?: number; + comment?: string; + binLocationId?: string; + lotNumber?: string; + expirationDate?: string; + original?: boolean; + newLine?: boolean; + receiptItemId?: string | null; + binLocationName?: string; +}; type ReceiptPayload = Omit & { containers: UnflattenContainer[]; From 842fd1484d57b457676373c27326546560b61f6a Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:20:58 +0200 Subject: [PATCH 08/23] add new location --- .github/workflows/playwright.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 15be24a..06422f1 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -67,6 +67,7 @@ jobs: LOCATION_DEPOT: ${{ secrets.LOCATION_DEPOT }} LOCATION_WARD: ${{ secrets.LOCATION_WARD }} LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT: ${{ secrets.LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT }} + LOCATION_INTERNAL: ${{ secrets.LOCATION_INTERNAL }} PRODUCT_ONE: ${{ secrets.PRODUCT_ONE }} PRODUCT_TWO: ${{ secrets.PRODUCT_TWO }} PRODUCT_THREE: ${{ secrets.PRODUCT_THREE }} From 8dc6f1183652950824fe9d7295823e93ad1700d5 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:22:33 +0200 Subject: [PATCH 09/23] add putaway pages --- src/pages/putaway/CreatePutawayPage.ts | 38 ++++++++++++ .../components/CompletePutawayTable.ts | 32 ++++++++++ .../putaway/components/CreatePutawayTable.ts | 36 +++++++++++ .../putaway/components/StartPutawayTable.ts | 59 +++++++++++++++++++ .../putawayDetails/PutawayDetailsPage.ts | 57 ++++++++++++++++++ .../putawayDetails/components/SummaryTable.ts | 38 ++++++++++++ src/pages/putaway/steps/CompleteStep.ts | 24 ++++++++ src/pages/putaway/steps/StartStep.ts | 24 ++++++++ 8 files changed, 308 insertions(+) create mode 100644 src/pages/putaway/CreatePutawayPage.ts create mode 100644 src/pages/putaway/components/CompletePutawayTable.ts create mode 100644 src/pages/putaway/components/CreatePutawayTable.ts create mode 100644 src/pages/putaway/components/StartPutawayTable.ts create mode 100644 src/pages/putaway/putawayDetails/PutawayDetailsPage.ts create mode 100644 src/pages/putaway/putawayDetails/components/SummaryTable.ts create mode 100644 src/pages/putaway/steps/CompleteStep.ts create mode 100644 src/pages/putaway/steps/StartStep.ts diff --git a/src/pages/putaway/CreatePutawayPage.ts b/src/pages/putaway/CreatePutawayPage.ts new file mode 100644 index 0000000..40af4f4 --- /dev/null +++ b/src/pages/putaway/CreatePutawayPage.ts @@ -0,0 +1,38 @@ +import { expect, Page } from '@playwright/test'; + +import WizzardSteps from '@/components/WizzardSteps'; +import BasePageModel from '@/pages/BasePageModel'; +import StartStep from '@/pages/putaway/steps/StartStep'; + +import CreatePutawayTable from './components/CreatePutawayTable'; +import CompleteStep from './steps/CompleteStep'; + +class CreatePutawayPage extends BasePageModel { + startStep: StartStep; + completeStep: CompleteStep; + + wizzardSteps: WizzardSteps; + table: CreatePutawayTable; + + constructor(page: Page) { + super(page); + this.table = new CreatePutawayTable(page); + this.startStep = new StartStep(page); + this.completeStep = new CompleteStep(page); + + const stepNames = ['Start', 'Complete']; + this.wizzardSteps = new WizzardSteps(page, stepNames); + } + + async isLoaded() { + await expect( + this.page.getByTestId('content-wrap').getByText('Create Putaway') + ).toBeVisible(); + } + + get startPutawayButton() { + return this.page.getByTestId('start-putaway').nth(0); + } +} + +export default CreatePutawayPage; diff --git a/src/pages/putaway/components/CompletePutawayTable.ts b/src/pages/putaway/components/CompletePutawayTable.ts new file mode 100644 index 0000000..5c0803b --- /dev/null +++ b/src/pages/putaway/components/CompletePutawayTable.ts @@ -0,0 +1,32 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class CompletePutawayTable extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get table() { + return this.page.getByTestId('wizardPage').getByRole('grid'); + } + + get rows() { + return this.table.getByRole('rowgroup'); + } + + 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 CompletePutawayTable; diff --git a/src/pages/putaway/components/CreatePutawayTable.ts b/src/pages/putaway/components/CreatePutawayTable.ts new file mode 100644 index 0000000..690b1cf --- /dev/null +++ b/src/pages/putaway/components/CreatePutawayTable.ts @@ -0,0 +1,36 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class CreatePutawayTable extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get table() { + return this.page.getByTestId('wizardPage').getByRole('grid'); + } + + get rows() { + return this.table.getByRole('rowgroup'); + } + + 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 checkbox() { + return this.row.getByRole('checkbox'); + } +} + +export default CreatePutawayTable; diff --git a/src/pages/putaway/components/StartPutawayTable.ts b/src/pages/putaway/components/StartPutawayTable.ts new file mode 100644 index 0000000..b9d70db --- /dev/null +++ b/src/pages/putaway/components/StartPutawayTable.ts @@ -0,0 +1,59 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class StartPutawayTable extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get table() { + return this.page.getByTestId('wizardPage').getByRole('grid'); + } + + get rows() { + return this.table.getByRole('rowgroup'); + } + + 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 editButton() { + return this.row.getByTestId('edit-button'); + } + + get splitLineButton() { + return this.row.getByRole('button', { name: 'Split line' }); + } + + get deleteButton() { + return this.row.getByTestId('delete-button'); + } + + getItem(name: string) { + return this.row.getByTestId('label-field').getByText(name); + } + + get putawayBinSelect() { + return this.row.getByTestId('select-bin'); + } + + getPutawayBin(putawayBin: string) { + return this.page + .getByTestId('custom-select-dropdown-menu') + .getByRole('listitem') + .getByText(putawayBin, { exact: true }); + } +} + +export default StartPutawayTable; diff --git a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts new file mode 100644 index 0000000..180d2ae --- /dev/null +++ b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts @@ -0,0 +1,57 @@ +import { expect, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +import SummaryTable from './components/SummaryTable'; + +class PutawayDetailsPage extends BasePageModel { + table: SummaryTable; + constructor(page: Page) { + super(page); + this.table = new SummaryTable(page); + } + + async isLoaded() { + await expect(this.summary).toBeVisible(); + } + + get summary() { + return this.page.locator('#order-summary'); + } + + get statusTag() { + return this.summary.locator('.tag-alert'); + } + + // TABS + get summaryTab() { + return this.page.getByRole('link', { name: 'Summary' }); + } + + get itemStatusTab() { + return this.page.getByRole('link', { name: 'Item Status' }); + } + + get itemDetailsTab() { + return this.page.getByRole('link', { name: 'Item Status' }); + } + + get documentTab() { + return this.page.getByRole('link', { name: 'Documents' }); + } + + get commentsTab() { + return this.page.getByRole('link', { name: 'Comments' }); + } + + // BUTTONS + get listOrdersButton() { + return this.page.getByRole('link', { name: 'List Orders' }); + } + + get editButton() { + return this.page.getByRole('button', { name: 'Edit Putaway' }); + } +} + +export default PutawayDetailsPage; diff --git a/src/pages/putaway/putawayDetails/components/SummaryTable.ts b/src/pages/putaway/putawayDetails/components/SummaryTable.ts new file mode 100644 index 0000000..2a78f05 --- /dev/null +++ b/src/pages/putaway/putawayDetails/components/SummaryTable.ts @@ -0,0 +1,38 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class SummaryTable extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get table() { + return this.page.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; + } + + getProductName(name: string) { + return this.row + .locator('[class="order-item even dataRow"]') + .getByRole('link') + .getByText(name); + } +} + +export default SummaryTable; diff --git a/src/pages/putaway/steps/CompleteStep.ts b/src/pages/putaway/steps/CompleteStep.ts new file mode 100644 index 0000000..e60b96e --- /dev/null +++ b/src/pages/putaway/steps/CompleteStep.ts @@ -0,0 +1,24 @@ +import { expect, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +import CompletePutawayTable from '../components/CompletePutawayTable'; + +class CompleteStep extends BasePageModel { + table: CompletePutawayTable; + + constructor(page: Page) { + super(page); + this.table = new CompletePutawayTable(page); + } + + async isLoaded() { + await expect(this.table.table).toBeVisible(); + } + + get completePutawayButton() { + return this.page.getByTestId('complete-putaway-button').nth(1); + } +} + +export default CompleteStep; diff --git a/src/pages/putaway/steps/StartStep.ts b/src/pages/putaway/steps/StartStep.ts new file mode 100644 index 0000000..d386b6c --- /dev/null +++ b/src/pages/putaway/steps/StartStep.ts @@ -0,0 +1,24 @@ +import { expect, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +import StartPutawayTable from '../components/StartPutawayTable'; + +class StartStep extends BasePageModel { + table: StartPutawayTable; + + constructor(page: Page) { + super(page); + this.table = new StartPutawayTable(page); + } + + async isLoaded() { + await expect(this.table.table).toBeVisible(); + } + + get nextButton() { + return this.page.getByTestId('next-button'); + } +} + +export default StartStep; From b451c496e751317b37e88cadc0c2ff8d8d1ff822 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:23:08 +0200 Subject: [PATCH 10/23] add old view shipment page --- .../stockMovementShow/OldViewShipmentPage.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/pages/stockMovementShow/OldViewShipmentPage.ts diff --git a/src/pages/stockMovementShow/OldViewShipmentPage.ts b/src/pages/stockMovementShow/OldViewShipmentPage.ts new file mode 100644 index 0000000..1db912f --- /dev/null +++ b/src/pages/stockMovementShow/OldViewShipmentPage.ts @@ -0,0 +1,15 @@ +import { Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class OldViewShipmentPage extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get undoStatusChangeButton() { + return this.page.getByRole('link', { name: 'Undo status change' }); + } +} + +export default OldViewShipmentPage; From 8eebaa417a2aaeb976085dc9a4f3d7ad73faa88f Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:23:35 +0200 Subject: [PATCH 11/23] add transaction pages --- src/pages/transactions/TransactionListPage.ts | 19 +++++++++ .../components/TransactionTable.ts | 41 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/pages/transactions/TransactionListPage.ts create mode 100644 src/pages/transactions/components/TransactionTable.ts diff --git a/src/pages/transactions/TransactionListPage.ts b/src/pages/transactions/TransactionListPage.ts new file mode 100644 index 0000000..8c82e38 --- /dev/null +++ b/src/pages/transactions/TransactionListPage.ts @@ -0,0 +1,19 @@ +import { Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +import TransactionTable from './components/TransactionTable'; + +class TransactionListPage extends BasePageModel { + table: TransactionTable; + constructor(page: Page) { + super(page); + this.table = new TransactionTable(page); + } + + get successMessage() { + return this.page.locator('.message'); + } +} + +export default TransactionListPage; diff --git a/src/pages/transactions/components/TransactionTable.ts b/src/pages/transactions/components/TransactionTable.ts new file mode 100644 index 0000000..470f944 --- /dev/null +++ b/src/pages/transactions/components/TransactionTable.ts @@ -0,0 +1,41 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class TransactionTable extends BasePageModel { + constructor(page: Page) { + super(page); + } + + get table() { + return this.page.getByRole('table'); + } + + get rows() { + return this.table.getByRole('row'); + } + + row(index: number) { + return new Row(this.page, this.rows.nth(index)); + } + + get deleteButton() { + return this.page + .locator('.action-menu-item') + .getByRole('link', { name: 'Delete' }); + } +} + +class Row extends BasePageModel { + row: Locator; + constructor(page: Page, row: Locator) { + super(page); + this.row = row; + } + + get actionsButton() { + return this.row.locator('.action-menu').getByRole('button'); + } +} + +export default TransactionTable; From 09dbb06c203c2f0bdc62feac669b4a62efc333ba Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:24:14 +0200 Subject: [PATCH 12/23] add shipmentUtils --- src/utils/shipmentUtils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/utils/shipmentUtils.ts diff --git a/src/utils/shipmentUtils.ts b/src/utils/shipmentUtils.ts new file mode 100644 index 0000000..936c9ff --- /dev/null +++ b/src/utils/shipmentUtils.ts @@ -0,0 +1,14 @@ +import { ReceiptResponse, StockMovementResponse } from '@/types'; + +export const getShipmentId = (stockMovement: StockMovementResponse) => { + return stockMovement.associations.shipment.id; +}; + +export const getShipmentItemId = ( + receipt: ReceiptResponse, + containerIndex: number, + shipmentItemIndex: number +) => { + return receipt.containers[containerIndex].shipmentItems[shipmentItemIndex] + .shipmentItemId; +}; From 75f61cd380c5a0d5d68d3000d4e9f2ccdd58ebde Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Mon, 13 Oct 2025 11:24:53 +0200 Subject: [PATCH 13/23] add create putaway test --- src/tests/putaway/createPutaway.test.ts | 143 ++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/tests/putaway/createPutaway.test.ts diff --git a/src/tests/putaway/createPutaway.test.ts b/src/tests/putaway/createPutaway.test.ts new file mode 100644 index 0000000..183cc23 --- /dev/null +++ b/src/tests/putaway/createPutaway.test.ts @@ -0,0 +1,143 @@ +//import AppConfig from '@/config/AppConfig'; +import { ShipmentType } from '@/constants/ShipmentType'; +import { expect, test } from '@/fixtures/fixtures'; +import { StockMovementResponse } from '@/types'; +import { getShipmentId, getShipmentItemId } from '@/utils/shipmentUtils'; + +test.describe('Putaway received inbound shipment', () => { + let STOCK_MOVEMENT: StockMovementResponse; + + test.beforeEach( + async ({ + supplierLocationService, + stockMovementService, + fifthProductService, + receivingService, + }) => { + const supplierLocation = await supplierLocationService.getLocation(); + STOCK_MOVEMENT = await stockMovementService.createInbound({ + originId: supplierLocation.id, + }); + + const product = await fifthProductService.getProduct(); + + await stockMovementService.addItemsToInboundStockMovement( + STOCK_MOVEMENT.id, + [{ productId: product.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.updateReceivingItems(shipmentId, [ + { + shipmentItemId: getShipmentItemId(receipt, 0, 0), + quantityReceiving: 10, + binLocationName: 'R-804CSX', + }, + ]); + await receivingService.completeReceipt(shipmentId); + } + ); + + test.afterEach( + async ({ + stockMovementShowPage, + stockMovementService, + navbar, + transactionListPage, + oldViewShipmentPage, + }) => { + await navbar.configurationButton.click(); + await navbar.transactions.click(); + await transactionListPage.table.row(1).actionsButton.click(); + await transactionListPage.table.deleteButton.click(); + await expect(transactionListPage.successMessage).toBeVisible(); + await transactionListPage.table.row(1).actionsButton.click(); + await transactionListPage.table.deleteButton.click(); + await expect(transactionListPage.successMessage).toBeVisible(); + + 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('Create putaway inbound stock movement', async ({ + stockMovementShowPage, + navbar, + createPutawayPage, + internalLocationService, + productShowPage, + putawayDetailsPage, + fifthProductService, + }) => { + await test.step('Go to stock movement show page', async () => { + await stockMovementShowPage.goToPage(STOCK_MOVEMENT.id); + await stockMovementShowPage.isLoaded(); + await expect(stockMovementShowPage.statusTag).toHaveText('Received'); + await navbar.profileButton.click(); + await navbar.refreshCachesButton.click(); + }); + + await test.step('Go to create putaway page', async () => { + await navbar.inbound.click(); + await navbar.createPutaway.click(); + await createPutawayPage.isLoaded(); + }); + + await test.step('Start putaway', async () => { + await createPutawayPage.table.row(0).checkbox.click(); + await createPutawayPage.startPutawayButton.click(); + await createPutawayPage.startStep.isLoaded(); + }); + + await test.step('Select bin to putaway', async () => { + const internalLocation = await internalLocationService.getLocation(); + await createPutawayPage.startStep.table.row(0).putawayBinSelect.click(); + await createPutawayPage.startStep.table + .row(0) + .getPutawayBin(internalLocation.name) + .click(); + await createPutawayPage.startStep.nextButton.click(); + }); + + await test.step('Go to next page and complete putaway', async () => { + await createPutawayPage.completeStep.isLoaded(); + await createPutawayPage.completeStep.completePutawayButton.click(); + }); + + await test.step('Assert completing putaway', async () => { + await putawayDetailsPage.isLoaded(); + await expect(putawayDetailsPage.statusTag).toHaveText('Completed'); + }); + + await test.step('Assert putaway bin on stock card', async () => { + await putawayDetailsPage.summaryTab.click(); + const product = await fifthProductService.getProduct(); + await putawayDetailsPage.table + .row(1) + .getProductName(product.name) + .click(); + //console.log(row); + await productShowPage.inStockTab.click(); + await productShowPage.inStockTabSection.isLoaded(); + const internalLocation = await internalLocationService.getLocation(); + await expect( + productShowPage.inStockTabSection.row(1).binLocation + ).toHaveText(internalLocation.name); + }); + }); +}); From 17b6cefac2ff4439f10459dd8b74fb068f8712d0 Mon Sep 17 00:00:00 2001 From: Alan Nadolny Date: Mon, 13 Oct 2025 16:37:51 +0200 Subject: [PATCH 14/23] OBPIH-7298 Add create receiving bin service method --- src/api/ReceivingService.ts | 36 ++++++++++++++++++++++++++++++++++++ src/types.d.ts | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/api/ReceivingService.ts b/src/api/ReceivingService.ts index 7af1575..301101a 100644 --- a/src/api/ReceivingService.ts +++ b/src/api/ReceivingService.ts @@ -102,6 +102,28 @@ class ReceivingService extends BaseServiceModel { } } + /* + As arguments take the shipment id and the receipt object. It should be called before updating items + to create a receiving bin. + */ + async createReceivingBin( + id: string, + receipt: ReceiptResponse, + ): Promise { + try { + const payload: ReceiptPayload = { + ...receipt, + containers: this.createEmptyContainers(receipt?.containers), + recipient: receipt.recipient?.id, + }; + await this.request.post(`./api/partialReceiving/${id}`, { + data: payload, + }); + } catch (error) { + throw new Error('Problem creating receiving bin'); + } + } + /* As arguments take the shipment id, id of receipt item that needs to be split, new lines are an array filled with new items, including the original one, that is split. @@ -169,6 +191,20 @@ class ReceivingService extends BaseServiceModel { } } + private createEmptyContainers( + containers: Container[] + ): Container[] { + return containers?.map((container) => { + return { + ...container, + shipmentItems: container?.shipmentItems?.map(shipmentItem => ({ + ...shipmentItem, + quantityReceiving: 0, + })) + } + }) + } + private findOriginalShipmentItem( containers: Container[], receiptItemId: string diff --git a/src/types.d.ts b/src/types.d.ts index ebf6bea..e345a31 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -342,7 +342,7 @@ type ReceivingItemPayload = { }; type ReceiptPayload = Omit & { - containers: UnflattenContainer[]; + containers: UnflattenContainer[] | Container[]; recipient: string; }; From 1fab5b046924e41e4ea0ffb804b167e1cc1ab37f Mon Sep 17 00:00:00 2001 From: Alan Nadolny Date: Mon, 13 Oct 2025 16:38:23 +0200 Subject: [PATCH 15/23] OBPIH-7298 Add create receiving in tests --- src/tests/putaway/createPutaway.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tests/putaway/createPutaway.test.ts b/src/tests/putaway/createPutaway.test.ts index 183cc23..12a6e40 100644 --- a/src/tests/putaway/createPutaway.test.ts +++ b/src/tests/putaway/createPutaway.test.ts @@ -1,4 +1,4 @@ -//import AppConfig from '@/config/AppConfig'; +import AppConfig from '@/config/AppConfig'; import { ShipmentType } from '@/constants/ShipmentType'; import { expect, test } from '@/fixtures/fixtures'; import { StockMovementResponse } from '@/types'; @@ -34,14 +34,16 @@ test.describe('Putaway received inbound shipment', () => { 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; + 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: 'R-804CSX', + binLocationName: receivingBin, }, ]); await receivingService.completeReceipt(shipmentId); @@ -131,7 +133,6 @@ test.describe('Putaway received inbound shipment', () => { .row(1) .getProductName(product.name) .click(); - //console.log(row); await productShowPage.inStockTab.click(); await productShowPage.inStockTabSection.isLoaded(); const internalLocation = await internalLocationService.getLocation(); From 6283e719b5789d20704b36b2ee75f9350d650914 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Tue, 14 Oct 2025 11:25:55 +0200 Subject: [PATCH 16/23] update env example --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 244447d..3f57985 100644 --- a/.env.example +++ b/.env.example @@ -6,12 +6,15 @@ USER_ALT_USERNAME=alt USER_ALT_PASSWORD=password USER_MANAGER_USERNAME=manager USER_MANAGER_PASSWORD=password +USER_IMPERSONATOR_USERNAME=impersonator +USER_IMPERSONATOR_PASSWORD=password LOCATION_MAIN=locationId LOCATION_NO_MANAGE_INVENOTRY_DEPOT=locationId LOCATION_SUPPLIER=locationId LOCATION_SUPPLIER_ALT=locationId LOCATION_DEPOT=locationId LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT=locationId +LOCATION_INTERNAL=locationId LOCATION_WARD=locationId PRODUCT_ONE=productId PRODUCT_TWO=productId From e951c4195cb31484f6b60a4af425b124ac46bf56 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Tue, 14 Oct 2025 11:27:14 +0200 Subject: [PATCH 17/23] update page elements and naming --- src/pages/putaway/putawayDetails/PutawayDetailsPage.ts | 5 +++-- .../putaway/putawayDetails/components/SummaryTable.ts | 9 +++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts index 180d2ae..5acd488 100644 --- a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts +++ b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts @@ -5,10 +5,11 @@ import BasePageModel from '@/pages/BasePageModel'; import SummaryTable from './components/SummaryTable'; class PutawayDetailsPage extends BasePageModel { - table: SummaryTable; + summaryTable: SummaryTable; + constructor(page: Page) { super(page); - this.table = new SummaryTable(page); + this.summaryTable = new SummaryTable(page); } async isLoaded() { diff --git a/src/pages/putaway/putawayDetails/components/SummaryTable.ts b/src/pages/putaway/putawayDetails/components/SummaryTable.ts index 2a78f05..dd3bf06 100644 --- a/src/pages/putaway/putawayDetails/components/SummaryTable.ts +++ b/src/pages/putaway/putawayDetails/components/SummaryTable.ts @@ -8,7 +8,7 @@ class SummaryTable extends BasePageModel { } get table() { - return this.page.getByRole('table'); + return this.page.getByRole('table', { name: 'Summary' }); } get rows() { @@ -27,11 +27,8 @@ class Row extends BasePageModel { this.row = row; } - getProductName(name: string) { - return this.row - .locator('[class="order-item even dataRow"]') - .getByRole('link') - .getByText(name); + get productName() { + return this.row.getByTestId('product-name'); } } From dbc87bac152b679397c2513932713b7ba27c9bae Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Tue, 14 Oct 2025 11:28:03 +0200 Subject: [PATCH 18/23] add putaway test --- src/tests/putaway/createPutaway.test.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tests/putaway/createPutaway.test.ts b/src/tests/putaway/createPutaway.test.ts index 12a6e40..0737a42 100644 --- a/src/tests/putaway/createPutaway.test.ts +++ b/src/tests/putaway/createPutaway.test.ts @@ -77,7 +77,7 @@ test.describe('Putaway received inbound shipment', () => { } ); - test('Create putaway inbound stock movement', async ({ + test('Create putaway from inbound stock movement', async ({ stockMovementShowPage, navbar, createPutawayPage, @@ -86,7 +86,7 @@ test.describe('Putaway received inbound shipment', () => { putawayDetailsPage, fifthProductService, }) => { - await test.step('Go to stock movement show page', async () => { + await test.step('Go to stock movement show page and assert received status', async () => { await stockMovementShowPage.goToPage(STOCK_MOVEMENT.id); await stockMovementShowPage.isLoaded(); await expect(stockMovementShowPage.statusTag).toHaveText('Received'); @@ -129,10 +129,7 @@ test.describe('Putaway received inbound shipment', () => { await test.step('Assert putaway bin on stock card', async () => { await putawayDetailsPage.summaryTab.click(); const product = await fifthProductService.getProduct(); - await putawayDetailsPage.table - .row(1) - .getProductName(product.name) - .click(); + await productShowPage.goToPage(product.id) await productShowPage.inStockTab.click(); await productShowPage.inStockTabSection.isLoaded(); const internalLocation = await internalLocationService.getLocation(); From 10eaadea1f874b5e5dc9826bb946d19f852e569b Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 23 Oct 2025 12:27:07 +0200 Subject: [PATCH 19/23] add page locators --- src/pages/location/createLocation/CreateLocationPage.ts | 2 +- .../createLocation/tabs/LocationConfigurationTabSection.ts | 4 ++++ .../location/createLocation/tabs/ZoneLocationTabSection.ts | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/location/createLocation/CreateLocationPage.ts b/src/pages/location/createLocation/CreateLocationPage.ts index c00932f..4a608f9 100644 --- a/src/pages/location/createLocation/CreateLocationPage.ts +++ b/src/pages/location/createLocation/CreateLocationPage.ts @@ -44,7 +44,7 @@ class CreateLocationPage extends BasePageModel { } get actionButton() { - return this.page.getByRole('button', { name: 'action' }); + return this.page.getByRole('button', { name: 'Action' }); } get deleteLocationButton() { diff --git a/src/pages/location/createLocation/tabs/LocationConfigurationTabSection.ts b/src/pages/location/createLocation/tabs/LocationConfigurationTabSection.ts index 89aa706..d8f85da 100644 --- a/src/pages/location/createLocation/tabs/LocationConfigurationTabSection.ts +++ b/src/pages/location/createLocation/tabs/LocationConfigurationTabSection.ts @@ -13,6 +13,10 @@ class LocationConfigurationTabSection extends BasePageModel { return this.section.getByLabel('Use Default Settings'); } + get activeCheckbox() { + return this.section.getByRole('checkbox').first(); + } + get saveButton() { return this.section.getByRole('button', { name: 'Save' }); } diff --git a/src/pages/location/createLocation/tabs/ZoneLocationTabSection.ts b/src/pages/location/createLocation/tabs/ZoneLocationTabSection.ts index a82bb3f..6403160 100644 --- a/src/pages/location/createLocation/tabs/ZoneLocationTabSection.ts +++ b/src/pages/location/createLocation/tabs/ZoneLocationTabSection.ts @@ -32,6 +32,10 @@ class ZoneLocationsTabSection extends BasePageModel { get deleteZoneButton() { return this.page.getByRole('link', { name: 'Delete' }); } + + get editZoneButton() { + return this.page.getByRole('link', { name: 'Edit', exact: true }); + } } export default ZoneLocationsTabSection; From 280654b7b2de443a3d5f095b4b0f35d343d589c5 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 23 Oct 2025 12:28:15 +0200 Subject: [PATCH 20/23] add location deactivation to receive tests --- .../editBinLocationWhenReceive.test.ts | 29 +++++++++++++------ src/tests/receiving/receiveToHoldBin.test.ts | 7 +++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/tests/receiving/editBinLocationWhenReceive.test.ts b/src/tests/receiving/editBinLocationWhenReceive.test.ts index cfa3857..086c2fd 100644 --- a/src/tests/receiving/editBinLocationWhenReceive.test.ts +++ b/src/tests/receiving/editBinLocationWhenReceive.test.ts @@ -77,7 +77,7 @@ test.describe('Edit Bin Location when receive inbound stock movement', () => { await stockMovementShowPage.rollbackButton.click(); await stockMovementService.deleteStockMovement(STOCK_MOVEMENT.id); - await test.step('Delete created bin location', async () => { + await test.step('Deactivate created bin location', async () => { const mainLocation = await mainLocationService.getLocation(); await page.goto('./location/list'); await locationListPage.searchByLocationNameField.fill( @@ -97,7 +97,10 @@ test.describe('Edit Bin Location when receive inbound stock movement', () => { 'Enter' ); await createLocationPage.binLocationTabSection.isLoaded(); - await createLocationPage.binLocationTabSection.deleteBinButton.click(); + await createLocationPage.binLocationTabSection.editBinButton.click(); + await createLocationPage.locationConfigurationTab.click(); + await createLocationPage.locationConfigurationTabSection.activeCheckbox.uncheck(); + await createLocationPage.locationConfigurationTabSection.saveButton.click(); }); } ); @@ -257,7 +260,7 @@ test.describe('Edit Bin Location to bin with zone when receive inbound stock mov await stockMovementShowPage.rollbackButton.click(); await stockMovementService.deleteStockMovement(STOCK_MOVEMENT.id); - await test.step('Delete created bin location', async () => { + await test.step('Deactivate created bin location', async () => { const mainLocation = await mainLocationService.getLocation(); await page.goto('./location/list'); await locationListPage.searchByLocationNameField.fill( @@ -277,10 +280,13 @@ test.describe('Edit Bin Location to bin with zone when receive inbound stock mov 'Enter' ); await createLocationPage.binLocationTabSection.isLoaded(); - await createLocationPage.binLocationTabSection.deleteBinButton.click(); + await createLocationPage.binLocationTabSection.editBinButton.click(); + await createLocationPage.locationConfigurationTab.click(); + await createLocationPage.locationConfigurationTabSection.activeCheckbox.uncheck(); + await createLocationPage.locationConfigurationTabSection.saveButton.click(); }); - await test.step('Delete created zone location', async () => { + await test.step('Deactivate created zone location', async () => { await createLocationPage.zoneLocationTab.click(); await createLocationPage.zoneLocationTabSection.isLoaded(); await createLocationPage.zoneLocationTabSection.searchField.fill( @@ -290,8 +296,10 @@ test.describe('Edit Bin Location to bin with zone when receive inbound stock mov 'Enter' ); await createLocationPage.zoneLocationTabSection.isLoaded(); - await createLocationPage.zoneLocationTabSection.deleteZoneButton.click(); - await createLocationPage.zoneLocationTabSection.isLoaded(); + await createLocationPage.zoneLocationTabSection.editZoneButton.click(); + await createLocationPage.locationConfigurationTab.click(); + await createLocationPage.locationConfigurationTabSection.activeCheckbox.uncheck(); + await createLocationPage.locationConfigurationTabSection.saveButton.click(); }); } ); @@ -436,7 +444,7 @@ test.describe('Edit Bin Location when receive for all lines', () => { await stockMovementShowPage.rollbackButton.click(); await stockMovementService.deleteStockMovement(STOCK_MOVEMENT.id); - await test.step('Delete created bin location', async () => { + await test.step('Deactivate created bin location', async () => { const mainLocation = await mainLocationService.getLocation(); await page.goto('./location/list'); await locationListPage.searchByLocationNameField.fill( @@ -456,7 +464,10 @@ test.describe('Edit Bin Location when receive for all lines', () => { 'Enter' ); await createLocationPage.binLocationTabSection.isLoaded(); - await createLocationPage.binLocationTabSection.deleteBinButton.click(); + await createLocationPage.binLocationTabSection.editBinButton.click(); + await createLocationPage.locationConfigurationTab.click(); + await createLocationPage.locationConfigurationTabSection.activeCheckbox.uncheck(); + await createLocationPage.locationConfigurationTabSection.saveButton.click(); }); } ); diff --git a/src/tests/receiving/receiveToHoldBin.test.ts b/src/tests/receiving/receiveToHoldBin.test.ts index 230772a..805d373 100644 --- a/src/tests/receiving/receiveToHoldBin.test.ts +++ b/src/tests/receiving/receiveToHoldBin.test.ts @@ -94,7 +94,7 @@ test.describe('Receive item into hold bin', () => { await stockMovementShowPage.rollbackButton.click(); await stockMovementService.deleteStockMovement(STOCK_MOVEMENT.id); - await test.step('Delete created bin location', async () => { + await test.step('Deatitave created bin location', async () => { const mainLocation = await mainLocationService.getLocation(); await page.goto('./location/list'); await locationListPage.searchByLocationNameField.fill( @@ -114,7 +114,10 @@ test.describe('Receive item into hold bin', () => { 'Enter' ); await createLocationPage.binLocationTabSection.isLoaded(); - await createLocationPage.binLocationTabSection.deleteBinButton.click(); + await createLocationPage.binLocationTabSection.editBinButton.click(); + await createLocationPage.locationConfigurationTab.click(); + await createLocationPage.locationConfigurationTabSection.activeCheckbox.uncheck(); + await createLocationPage.locationConfigurationTabSection.saveButton.click(); }); } ); From 6e953fc92b750f1009768524c3d00e4ce192497a Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 23 Oct 2025 12:29:08 +0200 Subject: [PATCH 21/23] add focus on fields --- src/tests/inbound/createInbound/tableShortcuts.test.ts | 4 ++++ src/tests/receiving/tableShortcutsInReceiving.test.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/tests/inbound/createInbound/tableShortcuts.test.ts b/src/tests/inbound/createInbound/tableShortcuts.test.ts index b17ef13..30b85bd 100644 --- a/src/tests/inbound/createInbound/tableShortcuts.test.ts +++ b/src/tests/inbound/createInbound/tableShortcuts.test.ts @@ -78,6 +78,7 @@ test('Use Control+ArrowDown copy cell shortcut', async ({ await createInboundPage.addItemsStep.table .row(0) .packLevel1Field.textbox.fill(ROW.packLevel1); + await createInboundPage.addItemsStep.table.row(0).packLevel1Field.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); @@ -96,6 +97,7 @@ test('Use Control+ArrowDown copy cell shortcut', async ({ await createInboundPage.addItemsStep.table .row(0) .packLevel2Field.textbox.fill(ROW.packLevel2); + await createInboundPage.addItemsStep.table.row(0).packLevel2Field.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); @@ -114,6 +116,7 @@ test('Use Control+ArrowDown copy cell shortcut', async ({ await createInboundPage.addItemsStep.table .row(0) .lotField.textbox.fill(ROW.lotNumber); + await createInboundPage.addItemsStep.table.row(0).lotField.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); @@ -132,6 +135,7 @@ test('Use Control+ArrowDown copy cell shortcut', async ({ await createInboundPage.addItemsStep.table .row(0) .quantityField.numberbox.fill(ROW.quantity); + await createInboundPage.addItemsStep.table.row(0).quantityField.numberbox.focus(); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); diff --git a/src/tests/receiving/tableShortcutsInReceiving.test.ts b/src/tests/receiving/tableShortcutsInReceiving.test.ts index 047d770..065220d 100644 --- a/src/tests/receiving/tableShortcutsInReceiving.test.ts +++ b/src/tests/receiving/tableShortcutsInReceiving.test.ts @@ -95,6 +95,7 @@ test.describe('Use table shortcuts on receiving page', () => { await receivingPage.receivingStep.table .row(1) .receivingNowField.textbox.fill('10'); + await receivingPage.receivingStep.table.row(1).receivingNowField.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); @@ -116,6 +117,7 @@ test.describe('Use table shortcuts on receiving page', () => { await receivingPage.receivingStep.table .row(1) .commentField.textbox.fill(comment1); + await receivingPage.receivingStep.table.row(1).commentField.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); await page.keyboard.press('Control+ArrowDown'); @@ -137,6 +139,7 @@ test.describe('Use table shortcuts on receiving page', () => { await receivingPage.receivingStep.table .row(5) .receivingNowField.textbox.fill('5'); + await receivingPage.receivingStep.table.row(5).receivingNowField.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await expect( receivingPage.receivingStep.table.row(6).receivingNowField.textbox @@ -144,6 +147,7 @@ test.describe('Use table shortcuts on receiving page', () => { await receivingPage.receivingStep.table .row(5) .commentField.textbox.fill(comment2); + await receivingPage.receivingStep.table.row(5).commentField.textbox.focus(); await page.keyboard.press('Control+ArrowDown'); await expect( receivingPage.receivingStep.table.row(6).commentField.textbox From 4ab067e22fbf35068676d76baf493f95ec223a67 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 23 Oct 2025 12:30:22 +0200 Subject: [PATCH 22/23] improve selectors --- src/pages/receiving/components/CheckTable.ts | 2 +- src/pages/user/editUser/tabs/AuthorizationTabSection.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/receiving/components/CheckTable.ts b/src/pages/receiving/components/CheckTable.ts index aec22b5..c0207a7 100644 --- a/src/pages/receiving/components/CheckTable.ts +++ b/src/pages/receiving/components/CheckTable.ts @@ -46,7 +46,7 @@ class Row extends BasePageModel { } get cancelRemainingCheckbox() { - return this.row.getByTestId('checkbox'); + return this.row.getByTestId('form-field').getByTestId('checkbox'); } } diff --git a/src/pages/user/editUser/tabs/AuthorizationTabSection.ts b/src/pages/user/editUser/tabs/AuthorizationTabSection.ts index 9203f5c..98aba39 100644 --- a/src/pages/user/editUser/tabs/AuthorizationTabSection.ts +++ b/src/pages/user/editUser/tabs/AuthorizationTabSection.ts @@ -33,7 +33,7 @@ class AuthorizationTabSection extends BasePageModel { } get addLocationRolesButton() { - return this.section.getByRole('button', { name: 'Add Location Roles' }); + return this.section.getByRole('cell').getByRole('button', { name: 'Add Location Roles' }); } get defaultLocationSelect() { From 4ab95b6264997bfca471b671f14240ab1c20fb19 Mon Sep 17 00:00:00 2001 From: Katarzyna Date: Thu, 23 Oct 2025 12:31:22 +0200 Subject: [PATCH 23/23] fix export test --- src/pages/inbound/list/InboundListPage.ts | 5 ++ .../inbound/createInbound/exportItems.test.ts | 55 ++++++++++--------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/pages/inbound/list/InboundListPage.ts b/src/pages/inbound/list/InboundListPage.ts index 396b45d..7d465fe 100644 --- a/src/pages/inbound/list/InboundListPage.ts +++ b/src/pages/inbound/list/InboundListPage.ts @@ -72,6 +72,11 @@ class InboundListPage extends BasePageModel { await this.exportStockMovementsButton.click(); return await this.fileHandler.saveFile(); } + + async waitForNetworkIdle() { + // eslint-disable-next-line playwright/no-networkidle + await this.page.waitForLoadState('networkidle'); + } } export default InboundListPage; diff --git a/src/tests/inbound/createInbound/exportItems.test.ts b/src/tests/inbound/createInbound/exportItems.test.ts index e7b9d83..f6c6cce 100644 --- a/src/tests/inbound/createInbound/exportItems.test.ts +++ b/src/tests/inbound/createInbound/exportItems.test.ts @@ -144,38 +144,36 @@ test.describe('Export all incoming items', () => { } ); - test.afterEach( - async ({ stockMovementService, stockMovementShowPage }) => { - await stockMovementShowPage.goToPage(INBOUND_ID); - await stockMovementShowPage.isLoaded(); - const isRollbackLastReceiptButtonVisible = - await stockMovementShowPage.rollbackLastReceiptButton.isVisible(); - const isRollbackButtonVisible = - await stockMovementShowPage.rollbackButton.isVisible(); - - // due to failed test, shipment might not be received which will not show the button - if (isRollbackLastReceiptButtonVisible) { - await stockMovementShowPage.rollbackLastReceiptButton.click(); - } + test.afterEach(async ({ stockMovementService, stockMovementShowPage }) => { + await stockMovementShowPage.goToPage(INBOUND_ID); + await stockMovementShowPage.isLoaded(); + const isRollbackLastReceiptButtonVisible = + await stockMovementShowPage.rollbackLastReceiptButton.isVisible(); + const isRollbackButtonVisible = + await stockMovementShowPage.rollbackButton.isVisible(); + + // due to failed test, shipment might not be received which will not show the button + if (isRollbackLastReceiptButtonVisible) { + await stockMovementShowPage.rollbackLastReceiptButton.click(); + } - if (isRollbackButtonVisible) { - await stockMovementShowPage.rollbackButton.click(); - } + if (isRollbackButtonVisible) { + await stockMovementShowPage.rollbackButton.click(); + } - await stockMovementService.deleteStockMovement(INBOUND_ID); + await stockMovementService.deleteStockMovement(INBOUND_ID); - for (const workbook of workbooks) { - workbook.delete(); - } + for (const workbook of workbooks) { + workbook.delete(); + } - if (INBOUND2_ID) { - await stockMovementShowPage.goToPage(INBOUND2_ID); - await stockMovementShowPage.isLoaded(); - await stockMovementShowPage.rollbackButton.click(); - await stockMovementService.deleteStockMovement(INBOUND2_ID); - } + if (INBOUND2_ID) { + await stockMovementShowPage.goToPage(INBOUND2_ID); + await stockMovementShowPage.isLoaded(); + await stockMovementShowPage.rollbackButton.click(); + await stockMovementService.deleteStockMovement(INBOUND2_ID); } - ); + }); test('Export all incoming items should include shipped items', async ({ inboundListPage, @@ -189,6 +187,7 @@ test.describe('Export all incoming items', () => { let downloadedTemplateFile: WorkbookUtils; await test.step('Download file', async () => { + await inboundListPage.waitForNetworkIdle(); const { fullFilePath } = await inboundListPage.downloadAllIncomingItems(); filePath = fullFilePath; }); @@ -250,6 +249,7 @@ test.describe('Export all incoming items', () => { }); await test.step('Download file', async () => { + await inboundListPage.waitForNetworkIdle(); const { fullFilePath } = await inboundListPage.downloadAllIncomingItems(); filePath = fullFilePath; }); @@ -315,6 +315,7 @@ test.describe('Export all incoming items', () => { }); await test.step('Download file', async () => { + await inboundListPage.waitForNetworkIdle(); const { fullFilePath } = await inboundListPage.downloadAllIncomingItems(); filePath = fullFilePath; });