diff --git a/playwright/data/e2eTestData.ts b/playwright/data/e2eTestData.ts index 1aa6c118b65..bc02435ff63 100644 --- a/playwright/data/e2eTestData.ts +++ b/playwright/data/e2eTestData.ts @@ -433,10 +433,20 @@ export const ORDERS = { id: "T3JkZXI6ZWFhZjA0MzgtNzkyYi00ZTdlLWIyODUtMTBkMjViMjM0MzRk", }, fullyPaidOrdersWithSingleTransaction: { - ids: [ - "T3JkZXI6ZjZjZWUxMzItNDk2Zi00MWUyLWJkNTItYTk1MDM1YTVlZmVm", - "T3JkZXI6YzI4YjFmYmEtZWU1NS00YmU5LTg5MjktNTMyYzk5MDlkZGVk", - ], + first: { + id: "T3JkZXI6ZjZjZWUxMzItNDk2Zi00MWUyLWJkNTItYTk1MDM1YTVlZmVm", + lineItems: [ + { + name: "Bean Juice", + quantity: "1", + }, + { name: "Lake Tunes", quantity: "2" }, + ], + }, + second: { + id: "T3JkZXI6YzI4YjFmYmEtZWU1NS00YmU5LTg5MjktNTMyYzk5MDlkZGVk", + lineItems: ["Blue Hoodie 2", "Black Hoodie", "Mustard Hoodie", "White Hoodie"], + }, }, fullyPaidOrderWithSeveralTransactions: { id: "T3JkZXI6MTVhYTEwMzYtZWE3OS00MzJiLTliODctNDhlYTMwYmU1NmNl", @@ -444,6 +454,19 @@ export const ORDERS = { partiallyPaidOrder: { id: "T3JkZXI6NmVlMDMwMTctZTViOS00OGNmLWFkYTQtODg4YTQ5MDI3ZjNk", }, + orderWithRefunds: { + id: "T3JkZXI6Y2YyY2EwNWYtZmQ3Yy00ODk5LThjZTktMzQ4NjYxYThjZDkx", + refunds: [ + { + lineOrderRefundId: "T3JkZXJHcmFudGVkUmVmdW5kOjE=", + amount: 4.5, + }, + { + manualRefundId: "", + amount: 22.0, + }, + ], + }, }; export const SHIPPING_METHODS = { diff --git a/playwright/pages/dialogs/lineRefundReasonDialog.ts b/playwright/pages/dialogs/lineRefundReasonDialog.ts new file mode 100644 index 00000000000..0801f9a5cc6 --- /dev/null +++ b/playwright/pages/dialogs/lineRefundReasonDialog.ts @@ -0,0 +1,21 @@ +import type { Page } from "@playwright/test"; + +export class AddLineRefundReasonDialog { + readonly page: Page; + + constructor( + page: Page, + readonly lineRefundReasonInput = page.getByTestId("line-refund-reason-input"), + readonly confirmButton = page.getByTestId("confirm-button"), + ) { + this.page = page; + } + + async provideLineRefundReason(reason: string) { + await this.lineRefundReasonInput.fill(reason); + } + + async submitLineRefundReason() { + await this.confirmButton.click(); + } +} diff --git a/playwright/pages/dialogs/orderRefundDialog.ts b/playwright/pages/dialogs/orderRefundDialog.ts new file mode 100644 index 00000000000..c4257228f12 --- /dev/null +++ b/playwright/pages/dialogs/orderRefundDialog.ts @@ -0,0 +1,24 @@ +import { BasePage } from "@pages/basePage"; +import { Page } from "@playwright/test"; + +export class OrderRefundDialog extends BasePage { + constructor( + page: Page, + readonly standardRefund = page.getByTestId("standard-refund"), + readonly manualRefund = page.getByTestId("manual-refund"), + readonly backButton = page.getByTestId("back-button"), + readonly proceedButton = page.getByTestId("proceed-button"), + ) { + super(page); + } + + async pickLineItemsRefund() { + await this.standardRefund.click(); + await this.proceedButton.click(); + } + + async pickManualRefund() { + await this.manualRefund.click(); + await this.proceedButton.click(); + } +} diff --git a/playwright/pages/ordersPage.ts b/playwright/pages/ordersPage.ts index 7022ee55748..f98dc35fa8c 100644 --- a/playwright/pages/ordersPage.ts +++ b/playwright/pages/ordersPage.ts @@ -10,6 +10,8 @@ import { OrderCreateDialog } from "@pages/dialogs/orderCreateDialog"; import { ShippingAddressDialog } from "@pages/dialogs/shippingMethodDialog"; import { Page } from "@playwright/test"; +import { OrderRefundDialog } from "./dialogs/orderRefundDialog"; + export class OrdersPage extends BasePage { orderCreateDialog: OrderCreateDialog; @@ -29,6 +31,8 @@ export class OrdersPage extends BasePage { rightSideDetailsPage: RightSideDetailsPage; + orderRefundDialog: OrderRefundDialog; + constructor( page: Page, readonly createOrderButton = page.getByTestId("create-order-button"), @@ -47,8 +51,9 @@ export class OrdersPage extends BasePage { readonly salesChannel = page.getByTestId("salesChannel"), readonly addShippingCarrierLink = page.getByTestId("add-shipping-carrier"), readonly finalizeButton = page.getByTestId("button-bar-confirm"), - + readonly addRefundButton = page.getByTestId("add-new-refund-button"), readonly customerEmail = page.getByTestId("customer-email"), + readonly orderRefundModal = page.getByTestId("order-refund-dialog"), ) { super(page); this.markOrderAsPaidDialog = new MarkOrderAsPaidDialog(page); @@ -60,6 +65,7 @@ export class OrdersPage extends BasePage { this.manualTransactionDialog = new ManualTransactionDialog(page); this.addTrackingDialog = new AddTrackingDialog(page); this.rightSideDetailsPage = new RightSideDetailsPage(page); + this.orderRefundDialog = new OrderRefundDialog(page); } async clickCreateOrderButton() { @@ -104,6 +110,11 @@ export class OrdersPage extends BasePage { await console.log("Navigating to order details view: " + orderLink); await this.page.goto(orderLink); - await this.waitForGrid(); + await this.waitForDOMToFullyLoad(); + } + + async clickAddRefundButton() { + await this.addRefundButton.click(); + await this.orderRefundModal.waitFor({ state: "visible" }); } } diff --git a/playwright/pages/refundPage.ts b/playwright/pages/refundPage.ts new file mode 100644 index 00000000000..853a6a148a1 --- /dev/null +++ b/playwright/pages/refundPage.ts @@ -0,0 +1,78 @@ +import { URL_LIST } from "@data/url"; +import { AddLineRefundReasonDialog } from "@pages/dialogs/lineRefundReasonDialog"; +import { OrdersPage } from "@pages/ordersPage"; +import { expect, Page } from "@playwright/test"; + +export class RefundPage extends OrdersPage { + addLineRefundReasonDialog: AddLineRefundReasonDialog; + + constructor( + page: Page, + readonly allButton = page.getByTestId("all-button"), + readonly productQuantityInput = page.getByTestId("product-quantity-input"), + readonly maxLineRefundQuantity = page.getByTestId("max-line-refund-quantity"), + readonly refundReasonInput = page.getByTestId("refund-reason-input"), + readonly lineRefundReasonDialog = page.getByTestId("refund-reason-dialog"), + readonly lineRefundReasonButton = page.getByTestId("line-refund-reason-button"), + ) { + super(page); + this.addLineRefundReasonDialog = new AddLineRefundReasonDialog(page); + } + + async getProductRow(productName: string) { + return await this.page.locator("table tr").filter({ hasText: productName }); + } + + async expectLineItemsRefundPageOpen(orderId: string) { + const orderLink = `${URL_LIST.orders}${orderId}/refund`; + + await expect(this.page).toHaveURL(orderLink); + await this.waitForDOMToFullyLoad(); + await expect(this.pageHeader).toContainText("Create refund with line items"); + } + + async expectManualRefundPageOpen(orderId: string) { + const orderLink = `${URL_LIST.orders}${orderId}/manual-refund`; + + await expect(this.page).toHaveURL(orderLink); + await expect(this.pageHeader).toContainText("Refund with manual amount"); + } + + async pickAllProductQuantityToRefund(productName: string) { + const productRow = await this.getProductRow(productName); + + await productRow.locator(this.allButton).click(); + + const maxLineRefundQuantityText = await productRow + .locator(this.maxLineRefundQuantity) + .first() + .innerText(); + const value = maxLineRefundQuantityText.slice(-1); + + await expect(productRow.locator(this.productQuantityInput)).toHaveValue(value); + } + + async provideRefundReason(reason: string) { + await this.refundReasonInput.fill(reason); + } + + async clickLineRefundReasonButton(productName: string) { + const productRow = await this.getProductRow(productName); + + await productRow.locator(this.lineRefundReasonButton).click(); + await this.lineRefundReasonDialog.waitFor({ state: "visible" }); + } + + async inputProductLineQuantity(productName: string, amount: string) { + const productRow = await this.getProductRow(productName); + + await productRow.locator(this.productQuantityInput).fill(amount); + await expect(productRow.locator(this.productQuantityInput)).toHaveValue(amount); + } + + async saveDraft() { + await expect(this.saveButton).toHaveText("Save draft"); + await this.clickSaveButton(); + await this.waitForDOMToFullyLoad(); + } +} diff --git a/playwright/tests/orders.spec.ts b/playwright/tests/orders.spec.ts index 36be0e911c3..92a2d8f26f0 100644 --- a/playwright/tests/orders.spec.ts +++ b/playwright/tests/orders.spec.ts @@ -6,6 +6,7 @@ import { DraftOrdersPage } from "@pages/draftOrdersPage"; import { AddressForm } from "@pages/forms/addressForm"; import { FulfillmentPage } from "@pages/fulfillmentPage"; import { OrdersPage } from "@pages/ordersPage"; +import { RefundPage } from "@pages/refundPage"; import { expect, test } from "@playwright/test"; test.use({ storageState: "./playwright/.auth/admin.json" }); @@ -16,6 +17,7 @@ let fulfillmentPage: FulfillmentPage; let addressDialog: AddressDialog; let addressForm: AddressForm; let addressesListPage: AddressesListPage; +let refundPage: RefundPage; test.beforeEach(({ page }) => { ordersPage = new OrdersPage(page); @@ -24,6 +26,7 @@ test.beforeEach(({ page }) => { addressDialog = new AddressDialog(page); addressesListPage = new AddressesListPage(page); addressForm = new AddressForm(page); + refundPage = new RefundPage(page); }); const variantSKU = PRODUCTS.productAvailableWithTransactionFlow.variant1sku; @@ -252,3 +255,27 @@ test("TC: SALEOR_84 Create draft order @e2e @draft", async () => { await draftOrdersPage.clickFinalizeButton(); await draftOrdersPage.expectSuccessBannerMessage("finalized"); }); + +test("TC: SALEOR_191 Refund products from the fully paid order @e2e @refunds", async () => { + const order = ORDERS.fullyPaidOrdersWithSingleTransaction.first; + + await ordersPage.goToExistingOrderPage(order.id); + await ordersPage.clickAddRefundButton(); + await ordersPage.orderRefundDialog.pickLineItemsRefund(); + await ordersPage.orderRefundModal.waitFor({ state: "hidden" }); + await refundPage.expectLineItemsRefundPageOpen(order.id); + await refundPage.pickAllProductQuantityToRefund(order.lineItems[0].name); + + const productRow = await refundPage.getProductRow(order.lineItems[0].name); + + expect(productRow.locator(refundPage.productQuantityInput)).toHaveValue( + order.lineItems[0].quantity, + ); + + await refundPage.inputProductLineQuantity(order.lineItems[1].name, "1"); + await refundPage.clickLineRefundReasonButton(order.lineItems[0].name); + await refundPage.addLineRefundReasonDialog.provideLineRefundReason("Item is damaged"); + await refundPage.addLineRefundReasonDialog.submitLineRefundReason(); + await refundPage.provideRefundReason("Expectations not met"); + await refundPage.saveDraft(); +}); diff --git a/src/orders/components/OrderDetailsRefundTable/OrderDetailsRefundTable.tsx b/src/orders/components/OrderDetailsRefundTable/OrderDetailsRefundTable.tsx index 176937ab62f..49d27f23f40 100644 --- a/src/orders/components/OrderDetailsRefundTable/OrderDetailsRefundTable.tsx +++ b/src/orders/components/OrderDetailsRefundTable/OrderDetailsRefundTable.tsx @@ -40,6 +40,7 @@ export const OrderDetailsRefundTable: React.FC = ( -