diff --git a/src/pages/putaway/components/SplitModal.ts b/src/pages/putaway/components/SplitModal.ts new file mode 100644 index 0000000..e1854c1 --- /dev/null +++ b/src/pages/putaway/components/SplitModal.ts @@ -0,0 +1,36 @@ +import { expect, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +import SplitModalTable from './SplitModalTable'; + +class SplitModal extends BasePageModel { + table: SplitModalTable; + + constructor(page: Page) { + super(page); + this.table = new SplitModalTable(page); + } + + get modal() { + return this.page.locator('.ReactModal__Content'); + } + + async isLoaded() { + await expect(this.modal).toBeVisible(); + } + + get saveButton() { + return this.modal.getByTestId('save-button'); + } + + get cancelButton() { + return this.modal.getByTestId('cancel-button'); + } + + get addLineButton() { + return this.modal.getByTestId('add-line-button'); + } +} + +export default SplitModal; diff --git a/src/pages/putaway/components/SplitModalTable.ts b/src/pages/putaway/components/SplitModalTable.ts new file mode 100644 index 0000000..71f4399 --- /dev/null +++ b/src/pages/putaway/components/SplitModalTable.ts @@ -0,0 +1,52 @@ +import { Locator, Page } from '@playwright/test'; + +import BasePageModel from '@/pages/BasePageModel'; + +class SplitModalTable 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; + } + + get deleteButton() { + return this.row.getByTestId('delete-button'); + } + + async getPutawayBin(putawayBin: string) { + await this.row + .getByTestId('bin-select') + .getByRole('textbox') + .fill(putawayBin); + await this.page + .getByTestId('custom-select-dropdown-menu') + .locator('.react-select__option') + .nth(0) + .click(); + } + + get quantityField() { + return this.row.getByRole('cell').getByTestId('quantity-input'); + } +} + +export default SplitModalTable; diff --git a/src/pages/putaway/components/StartPutawayTable.ts b/src/pages/putaway/components/StartPutawayTable.ts index d8ea4bd..3681890 100644 --- a/src/pages/putaway/components/StartPutawayTable.ts +++ b/src/pages/putaway/components/StartPutawayTable.ts @@ -33,7 +33,7 @@ class Row extends BasePageModel { } get splitLineButton() { - return this.row.getByRole('button', { name: 'Split line' }); + return this.row.getByTestId('open-modal'); } get deleteButton() { @@ -58,6 +58,10 @@ class Row extends BasePageModel { getCurrentBin(currentBin: string) { return this.row.getByTestId('cell-0-currentBin').getByText(currentBin); } + + get quantityField() { + return this.row.getByTestId('cell-0-quantity').getByRole('spinbutton'); + } } export default StartPutawayTable; diff --git a/src/pages/putaway/list/PutawayListTable.ts b/src/pages/putaway/list/PutawayListTable.ts index 303708c..7997f94 100644 --- a/src/pages/putaway/list/PutawayListTable.ts +++ b/src/pages/putaway/list/PutawayListTable.ts @@ -31,6 +31,12 @@ class PutawayListTable extends BasePageModel { this.page.once('dialog', (dialog) => dialog.accept()); await this.deleteOrderButton.click(); } + + get emptyPutawayList() { + return this.table + .locator('.empty fade center') + .getByText('No orders match the given criteria'); + } } class Row extends BasePageModel { diff --git a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts index a434c30..b4351cb 100644 --- a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts +++ b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts @@ -113,6 +113,16 @@ class PutawayDetailsPage extends BasePageModel { get generatePutawayListButton() { return this.page.getByRole('link', { name: 'Generate Putaway List' }); } + + async assertClickOnEditButtonWhenPutawayCompleted() { + this.page.once('dialog', (dialog) => { + expect(dialog.message()).toContain( + 'This feature is not available for completed and canceled putaways' + ); + dialog.accept(); + }); + await this.editButton.click(); + } } export default PutawayDetailsPage; diff --git a/src/pages/putaway/steps/StartStep.ts b/src/pages/putaway/steps/StartStep.ts index 159003e..0524ac0 100644 --- a/src/pages/putaway/steps/StartStep.ts +++ b/src/pages/putaway/steps/StartStep.ts @@ -2,14 +2,17 @@ import { expect, Page } from '@playwright/test'; import BasePageModel from '@/pages/BasePageModel'; +import SplitModal from '../components/SplitModal'; import StartPutawayTable from '../components/StartPutawayTable'; class StartStep extends BasePageModel { table: StartPutawayTable; + splitModal: SplitModal; constructor(page: Page) { super(page); this.table = new StartPutawayTable(page); + this.splitModal = new SplitModal(page); } async isLoaded() { @@ -23,6 +26,30 @@ class StartStep extends BasePageModel { get saveButton() { return this.page.getByTestId('save-button'); } + + get generatePutawayListButton() { + return this.page.getByTestId('export-button'); + } + + get sortByCurrentBinButton() { + return this.page.getByTestId('sort-button'); + } + + get validationOnEditCompletedPutaway() { + return this.page + .locator('.s-alert-box-inner') + .getByText(/Can't update completed putaway/); + } + + get validationOnDeleteItemFromCompletedPutaway() { + return this.page + .locator('.s-alert-box-inner') + .getByText(/Can't remove an item on completed putaway/); + } + + async closeDisplayedError() { + return this.page.locator('.alert-close-icon').click(); + } } export default StartStep; diff --git a/src/tests/putaway/assertAttemptToEditCompletedPutaway.test.ts b/src/tests/putaway/assertAttemptToEditCompletedPutaway.test.ts new file mode 100644 index 0000000..2aa6d42 --- /dev/null +++ b/src/tests/putaway/assertAttemptToEditCompletedPutaway.test.ts @@ -0,0 +1,202 @@ +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('Assert attempt to edit completed putaway', () => { + 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.createReceivingBin(shipmentId, receipt); + + await receivingService.updateReceivingItems(shipmentId, [ + { + shipmentItemId: getShipmentItemId(receipt, 0, 0), + quantityReceiving: 10, + binLocationName: receivingBin, + }, + ]); + 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('Assert attempt to edit completed putaway', async ({ + stockMovementShowPage, + navbar, + createPutawayPage, + internalLocationService, + putawayDetailsPage, + putawayListPage, + page, + }) => { + await test.step('Go to create putaway page', async () => { + await stockMovementShowPage.goToPage(STOCK_MOVEMENT.id); + await stockMovementShowPage.isLoaded(); + await navbar.profileButton.click(); + await navbar.refreshCachesButton.click(); + 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 try to edit completed putaway', async () => { + await putawayDetailsPage.assertClickOnEditButtonWhenPutawayCompleted(); + }); + + await test.step('Assert delete button is not visible for completed putaway', async () => { + await putawayDetailsPage.summaryActionsButton.click(); + await expect(putawayDetailsPage.actionsDeleteButton).toBeHidden(); + }); + + await test.step('Go backward in browser', async () => { + await page.goBack(); + await createPutawayPage.startStep.isLoaded(); + }); + + await test.step('Assert attemps to edit in completed putaway', async () => { + await createPutawayPage.startStep.saveButton.click(); + await expect( + createPutawayPage.startStep.validationOnEditCompletedPutaway + ).toBeVisible(); + await createPutawayPage.startStep.closeDisplayedError(); + await createPutawayPage.startStep.nextButton.click(); + await expect( + createPutawayPage.startStep.validationOnEditCompletedPutaway + ).toBeVisible(); + await createPutawayPage.startStep.closeDisplayedError(); + await createPutawayPage.startStep.generatePutawayListButton.click(); + await expect( + createPutawayPage.startStep.validationOnEditCompletedPutaway + ).toBeVisible(); + await createPutawayPage.startStep.closeDisplayedError(); + await createPutawayPage.startStep.sortByCurrentBinButton.click(); + await expect( + createPutawayPage.startStep.validationOnEditCompletedPutaway + ).toBeVisible(); + await createPutawayPage.startStep.closeDisplayedError(); + }); + + await test.step('Assert attemps to edit item in completed putaway', async () => { + await createPutawayPage.startStep.isLoaded(); + await createPutawayPage.startStep.table.row(0).deleteButton.click(); + await expect( + createPutawayPage.startStep.validationOnDeleteItemFromCompletedPutaway + ).toBeVisible(); + await createPutawayPage.startStep.closeDisplayedError(); + await createPutawayPage.startStep.table.row(0).splitLineButton.click(); + await createPutawayPage.startStep.splitModal.isLoaded(); + await createPutawayPage.startStep.splitModal.addLineButton.click(); + const internalLocation = await internalLocationService.getLocation(); + await createPutawayPage.startStep.splitModal.table + .row(1) + .getPutawayBin(internalLocation.name); + await createPutawayPage.startStep.splitModal.table + .row(1) + .quantityField.fill('5'); + await createPutawayPage.startStep.splitModal.table + .row(2) + .getPutawayBin(internalLocation.name); + await createPutawayPage.startStep.splitModal.table + .row(2) + .quantityField.fill('5'); + await createPutawayPage.startStep.splitModal.saveButton.click(); + await expect( + createPutawayPage.startStep.validationOnEditCompletedPutaway + ).toBeVisible(); + await createPutawayPage.startStep.closeDisplayedError(); + await createPutawayPage.startStep.table.row(0).editButton.click(); + await createPutawayPage.startStep.table.row(0).quantityField.fill('20'); + await expect(createPutawayPage.startStep.nextButton).toBeDisabled(); + await expect(createPutawayPage.startStep.saveButton).toBeDisabled(); + }); + + await test.step('Go putaway list page and assert pending putaway is not created', async () => { + await putawayListPage.goToPage(); + await putawayListPage.isLoaded(); + await putawayListPage.emptyPutawayList.isVisible(); + }); + }); +});