diff --git a/src/data/demo/orders.ts b/src/data/demo/orders.ts new file mode 100644 index 00000000..6150b8be --- /dev/null +++ b/src/data/demo/orders.ts @@ -0,0 +1,58 @@ +import OrderData from '@data/faker/order'; + +import dataCustomers from '@data/demo/customers'; +import dataOrderStatuses from '@data/demo/orderStatuses'; +import dataPaymentMethods from '@data/demo/paymentMethods'; + +export default { + order_1: new OrderData({ + id: 1, + reference: 'XKBKNABJK', + newClient: true, + delivery: 'United States', + customer: dataCustomers.johnDoe, + totalPaid: 61.80, + paymentMethod: dataPaymentMethods.checkPayment, + status: dataOrderStatuses.canceled, + }), + order_2: new OrderData({ + id: 2, + reference: 'OHSATSERP', + newClient: false, + delivery: 'United States', + customer: dataCustomers.johnDoe, + totalPaid: 69.90, + paymentMethod: dataPaymentMethods.checkPayment, + status: dataOrderStatuses.awaitingCheckPayment, + }), + order_3: new OrderData({ + id: 3, + reference: 'UOYEVOLI', + newClient: false, + delivery: 'United States', + customer: dataCustomers.johnDoe, + totalPaid: 14.90, + paymentMethod: dataPaymentMethods.checkPayment, + status: dataOrderStatuses.paymentError, + }), + order_4: new OrderData({ + id: 4, + reference: 'FFATNOMMJ', + newClient: false, + delivery: 'United States', + customer: dataCustomers.johnDoe, + totalPaid: 14.90, + paymentMethod: dataPaymentMethods.checkPayment, + status: dataOrderStatuses.awaitingCheckPayment, + }), + order_5: new OrderData({ + id: 5, + reference: 'KHWLILZLL', + newClient: false, + delivery: 'United States', + customer: dataCustomers.johnDoe, + totalPaid: 20.90, + paymentMethod: dataPaymentMethods.wirePayment, + status: dataOrderStatuses.awaitingCheckPayment, + }), +}; diff --git a/src/data/faker/order.ts b/src/data/faker/order.ts new file mode 100644 index 00000000..05050afd --- /dev/null +++ b/src/data/faker/order.ts @@ -0,0 +1,103 @@ +import FakerCustomer from '@data/faker/customer'; +import FakerPaymentMethod from '@data/faker/paymentMethod'; +import FakerOrderStatus from '@data/faker/orderStatus'; +import FakerAddress from '@data/faker/address'; +import dataCustomers from '@data/demo/customers'; +import dataPaymentMethods from '@data/demo/paymentMethods'; +import dataOrderStatuses from '@data/demo/orderStatuses'; + +import type {OrderCreator, OrderDeliveryOption, OrderProduct} from '@data/types/order'; + +import {faker} from '@faker-js/faker'; + +/** + * Create new order to use on creation form order page on BO + * @class + */ +export default class OrderData { + public readonly id: number; + + public readonly reference: string; + + public readonly newClient: boolean; + + public readonly delivery: string; + + public readonly customer: FakerCustomer; + + public readonly totalPaid: number; + + public readonly paymentMethod: FakerPaymentMethod; + + public readonly status: FakerOrderStatus; + + public readonly deliveryAddress: FakerAddress; + + public readonly invoiceAddress: FakerAddress; + + public readonly products: OrderProduct[]; + + public readonly discountGiftValue: number; + + public readonly discountPercentValue: number; + + public readonly totalPrice: number; + + public readonly deliveryOption: OrderDeliveryOption; + + /** + * Constructor for class Order + * @param orderToCreate {OrderCreator} Could be used to force the value of some members + */ + constructor(orderToCreate: OrderCreator = {}) { + /** @type {number} */ + this.id = orderToCreate.id || 0; + /** @type {string} */ + this.reference = orderToCreate.reference || faker.string.alpha({ + casing: 'upper', + length: 9, + }); + + /** @type {boolean} */ + this.newClient = orderToCreate.newClient === undefined ? true : orderToCreate.newClient; + + /** @type {string} */ + this.delivery = orderToCreate.delivery || 'France'; + + /** @type {FakerCustomer} */ + this.customer = orderToCreate.customer || dataCustomers.johnDoe; + + /** @type {number} */ + this.totalPaid = orderToCreate.totalPaid || 0; + + /** @type {FakerPaymentMethod} */ + this.paymentMethod = orderToCreate.paymentMethod || dataPaymentMethods.checkPayment; + + /** @type {FakerOrderStatus|null} */ + this.status = orderToCreate.status || dataOrderStatuses.paymentAccepted; + + /** @type {FakerCustomer} */ + this.deliveryAddress = orderToCreate.deliveryAddress || new FakerAddress(); + + /** @type {FakerCustomer} */ + this.invoiceAddress = orderToCreate.invoiceAddress || new FakerAddress(); + + /** @type {OrderProduct[]} */ + this.products = orderToCreate.products || []; + + /** @type {number} */ + this.discountGiftValue = orderToCreate.discountGiftValue || 0; + + /** @type {number} */ + this.discountPercentValue = orderToCreate.discountPercentValue || 0; + + /** @type {number} */ + this.totalPrice = orderToCreate.totalPrice || 0; + + /** @type {OrderDeliveryOption} */ + this.deliveryOption = orderToCreate.deliveryOption || { + name: '', + freeShipping: false, + }; + } +} diff --git a/src/data/faker/product.ts b/src/data/faker/product.ts new file mode 100644 index 00000000..ce7348b2 --- /dev/null +++ b/src/data/faker/product.ts @@ -0,0 +1,371 @@ +import { + ProductAttributes, + ProductCombination, + ProductCreator, + ProductCustomization, + ProductPackItem, + ProductSpecificPrice, + ProductFeatures, + ProductFiles, + ProductCustomizations, +} from '@data/types/product'; + +import {faker} from '@faker-js/faker'; + +/** + * Create new product to use on creation form on product page on BO + * @class + */ +export default class ProductData { + public id: number; + + public name: string; + + public nameFR: string; + + public defaultImage: string | null; + + public coverImage: string | null; + + public thumbImage: string | null; + + public thumbImageFR: string | null; + + public category: string; + + public type: string; + + public status: boolean; + + public applyChangesToAllStores: boolean; + + public summary: string; + + public description: string; + + public descriptionFR: string; + + public reference: string; + + public mpn: string | null; + + public upc: string | null; + + public ean13: string | null; + + public isbn: string | null; + + public features: ProductFeatures[]; + + public files: ProductFiles[]; + + public displayCondition: boolean; + + public condition: string; + + public quantity: number; + + public tax: number; + + public price: number; + + public retailPrice: number; + + public priceTaxExcluded: number; + + public finalPrice: number; + + public onSale: boolean; + + public productHasCombinations: boolean; + + public attributes: ProductAttributes[]; + + public pack: ProductPackItem[]; + + public taxRule: string; + + public ecoTax: number; + + public specificPrice: ProductSpecificPrice; + + public minimumQuantity: number; + + public stockLocation: string; + + public lowStockLevel: number; + + public labelWhenInStock: string; + + public labelWhenOutOfStock: string; + + public behaviourOutOfStock: string; + + public customization: ProductCustomization; + + public customizations: ProductCustomizations[]; + + public downloadFile: boolean; + + public fileName: string; + + public allowedDownload: number; + + public expirationDate: string | null; + + public numberOfDays: number | null; + + public metaTitle: string | null; + + public metaDescription: string | null; + + public friendlyUrl: string | null; + + public combinations: ProductCombination[]; + + public packageDimensionWidth: number; + + public packageDimensionHeight: number; + + public packageDimensionDepth: number; + + public packageDimensionWeight: number; + + public deliveryTime: string; + + /** + * Constructor for class ProductData + * @param productToCreate {Object} Could be used to force the value of some members + * @todo Replace taxRule & tax by TaxData object + * @todo Rename price to priceTaxIncluded + * @todo Check if retailPrice & finalPrice can be removed + */ + constructor(productToCreate: ProductCreator = {}) { + /** @type {number} ID of the product */ + this.id = productToCreate.id || 0; + + /** @type {string} Name of the product (in English) */ + this.name = productToCreate.name || faker.commerce.productName(); + + /** @type {string} Name of the product (in French) */ + this.nameFR = productToCreate.nameFR || `${this.name} (FRENCH)`; + + /** @type {string|null} Default image path for the product */ + this.defaultImage = productToCreate.defaultImage || null; + + /** @type {string|null} Cover image path for the product */ + this.coverImage = productToCreate.coverImage || null; + + /** @type {string|null} Thumb image path for the product */ + this.thumbImage = productToCreate.thumbImage || null; + + /** @type {string|null} Thumb image path for the product */ + this.thumbImageFR = productToCreate.thumbImageFR || this.thumbImage; + + /** @type {string} Category for the product */ + this.category = productToCreate.category || 'Root'; + + /** @type {string} Type of the product */ + this.type = productToCreate.type || 'standard'; + + /** @type {boolean} Status of the product */ + this.status = productToCreate.status === undefined ? true : productToCreate.status; + + /** @type {boolean} Apply changes to all stores */ + this.applyChangesToAllStores = productToCreate.applyChangesToAllStores || false; + + /** @type {string} Summary of the product */ + this.summary = productToCreate.summary === undefined ? faker.lorem.sentence() : productToCreate.summary; + + /** @type {string} Description of the product (in English) */ + this.description = productToCreate.description === undefined ? faker.lorem.sentence() : productToCreate.description; + + /** @type {string} Description of the product (in French) */ + this.descriptionFR = productToCreate.descriptionFR || `${this.description} (FRENCH)`; + + /** @type {string} Reference of the product */ + this.reference = productToCreate.reference || faker.string.alphanumeric(7); + + /** @type {string|null} mpn of the product */ + this.mpn = productToCreate.mpn || null; + + /** @type {string|null} upc of the product */ + this.upc = productToCreate.upc || null; + + /** @type {string|null} ean13 of the product */ + this.ean13 = productToCreate.ean13 || null; + + /** @type {string|null} isbn of the product */ + this.isbn = productToCreate.isbn || null; + + /** @type {number} Quantity available of the product */ + this.quantity = productToCreate.quantity === undefined + ? faker.number.int({min: 1, max: 9}) + : productToCreate.quantity; + + /** @type {number} Tax for the product */ + this.tax = productToCreate.tax === undefined + ? faker.number.int({min: 1, max: 100}) + : productToCreate.tax; + + /** @type {number} Price tax included of the product */ + this.price = productToCreate.price === undefined + ? faker.number.int({min: 10, max: 20}) : productToCreate.price; + + /** @type {number} Retail price of the product */ + this.retailPrice = productToCreate.retailPrice === undefined + ? this.price : productToCreate.retailPrice; + + /** @type {number} Price tax excluded of the product */ + this.priceTaxExcluded = productToCreate.priceTaxExcluded || (this.price * 100) / (100 + this.tax); + + /** @type {number} Final Price of the product */ + this.finalPrice = productToCreate.finalPrice || this.price; + + /** @type {boolean} True to enable on sale flag */ + this.onSale = productToCreate.onSale || false; + + /** @type {boolean} True to create product with combination */ + this.productHasCombinations = productToCreate.productHasCombinations || false; + + /** @type {Object|{color: Array, size: Array}} Combinations of the product */ + this.attributes = productToCreate.attributes || [ + { + name: 'color', + values: ['White', 'Black'], + }, + { + name: 'size', + values: ['S', 'M'], + }, + ]; + + /** @type {ProductFeatures[]} Features of the product */ + this.features = productToCreate.features || [ + { + featureName: 'Composition', + preDefinedValue: 'Cotton', + }, + ]; + + /** @type {ProductFiles[]} Files attached to the product in details tab*/ + this.files = productToCreate.files || [ + { + fileName: 'test', + description: 'test', + file: 'test.txt', + }, + ]; + + /** @type {boolean} Boolean to choose if we need to display condition or not */ + this.displayCondition = productToCreate.displayCondition || false; + + /** @type {string} Condition to choose */ + this.condition = productToCreate.condition || 'Use'; + + /** @type {ProductPackItem[]} Pack of products to add to the product */ + this.pack = productToCreate.pack || [ + { + reference: 'demo_1', + quantity: faker.number.int({min: 10, max: 100}), + }, + { + reference: 'demo_2', + quantity: faker.number.int({min: 10, max: 100}), + }, + ]; + + /** @type {string} Tac rule to apply the product */ + this.taxRule = productToCreate.taxRule || 'FR Taux standard (20%)'; + + /** @type {number} EcoTax tax included of the product */ + this.ecoTax = productToCreate.ecoTax === undefined + ? faker.number.int({min: 1, max: 5}) + : productToCreate.ecoTax; + + /** @type {ProductSpecificPrice} Specific price of the product */ + this.specificPrice = productToCreate.specificPrice || { + attributes: null, + discount: faker.number.int({min: 10, max: 100}), + startingAt: faker.number.int({min: 2, max: 5}), + reductionType: '', + }; + + /** @type {number} Minimum quantity to buy for the product */ + this.minimumQuantity = productToCreate.minimumQuantity || 1; + + /** @type {string} Stock location of the product */ + this.stockLocation = productToCreate.stockLocation || 'stock 1'; + + /** @type {number} Low stock level of the product */ + this.lowStockLevel = productToCreate.lowStockLevel === undefined + ? faker.number.int({min: 1, max: 9}) + : productToCreate.lowStockLevel; + + /** @type {string} Label to add if product is in stock */ + this.labelWhenInStock = productToCreate.labelWhenInStock || ''; + + /** @type {string} Label to add if product is out of stock */ + this.labelWhenOutOfStock = productToCreate.labelWhenOutOfStock || ''; + + /** @type {string} Product behavior when it's out of stock */ + this.behaviourOutOfStock = productToCreate.behaviourOutOfStock || 'Default behavior'; + + /** @type {ProductCustomization} Customized value of the product */ + this.customization = productToCreate.customization || { + label: 'Type your text here', + type: 'Text', + required: true, + }; + + /** @type {ProductCustomization} Customized value of the product */ + this.customizations = productToCreate.customizations || [ + { + label: 'Type your text here', + type: 'Text', + required: true, + }]; + + /** @type {boolean} True to download file */ + this.downloadFile = productToCreate.downloadFile || false; + + /** @type {string} File name to put it in virtual tab */ + this.fileName = productToCreate.fileName || 'virtual.jpg'; + + /** @type {number} Number of allowed downloads */ + this.allowedDownload = productToCreate.allowedDownload || faker.number.int({min: 1, max: 20}); + + /** @type {string} Expiration date */ + this.expirationDate = productToCreate.expirationDate || null; + + /** @type {number} Number of days limit */ + this.numberOfDays = productToCreate.numberOfDays || null; + + /** @type {string} Meta title */ + this.metaTitle = productToCreate.metaTitle || null; + + /** @type {string} Meta description */ + this.metaDescription = productToCreate.metaDescription || null; + + /** @type {string} Friendly URL */ + this.friendlyUrl = productToCreate.friendlyUrl || null; + + /** @type {number} Weight of the package */ + this.packageDimensionWeight = productToCreate.packageDimensionWeight || faker.number.int({min: 1, max: 20}); + + /** @type {number} Width of the package */ + this.packageDimensionWidth = productToCreate.packageDimensionWidth || faker.number.int({min: 1, max: 20}); + + /** @type {number} Height of the package */ + this.packageDimensionHeight = productToCreate.packageDimensionHeight || faker.number.int({min: 1, max: 20}); + + /** @type {number} Depth of the package */ + this.packageDimensionDepth = productToCreate.packageDimensionDepth || faker.number.int({min: 1, max: 20}); + + /** @type {string} Delivery time */ + this.deliveryTime = productToCreate.deliveryTime || 'Default delivery time'; + + /** @type {ProductCombination[]} */ + this.combinations = productToCreate.combinations || []; + } +} diff --git a/src/data/types/order.ts b/src/data/types/order.ts new file mode 100644 index 00000000..3c35ac1a --- /dev/null +++ b/src/data/types/order.ts @@ -0,0 +1,84 @@ +import FakerCustomer from '@data/faker/customer'; +import FakerPaymentMethod from '@data/faker/paymentMethod'; +import FakerOrderStatus from '@data/faker/orderStatus'; +import FakerAddress from '@data/faker/address'; +import FakerProductData from '@data/faker/product'; + +type OrderCreator = { + id?: number + reference?: string + newClient?: boolean + delivery?: string + customer?: FakerCustomer + totalPaid?: number + paymentMethod?: FakerPaymentMethod + status?: FakerOrderStatus + deliveryAddress?: FakerAddress + invoiceAddress?: FakerAddress + products?: OrderProduct[] + discountPercentValue?: number + discountGiftValue?: number + totalPrice?: number + deliveryOption?: OrderDeliveryOption +} + +type OrderDeliveryOption = { + name: string + freeShipping: boolean +} + +type OrderProduct = { + product: FakerProductData + quantity: number +} + +type OrderHistory = { + reference: string + date: string + price: string + paymentType: string + status: string + invoice: string +} + +type OrderHistoryMessage = { + product: string + message : string +} + +type OrderMessage = { + orderMessage: string + displayToCustomer: boolean + message : string +} + +type OrderPayment = { + date: string + paymentMethod: string + transactionID : number + amount : number + currency : string +} + +type MerchandiseReturns = { + orderReference: string + fileName: string + status: string + dateIssued: string +} + +type MerchandiseProductReturn = { + quantity: number +} + +export type{ + MerchandiseProductReturn, + MerchandiseReturns, + OrderCreator, + OrderDeliveryOption, + OrderHistory, + OrderHistoryMessage, + OrderMessage, + OrderPayment, + OrderProduct, +}; diff --git a/src/data/types/product.ts b/src/data/types/product.ts index 502176a5..99f9b21c 100644 --- a/src/data/types/product.ts +++ b/src/data/types/product.ts @@ -22,6 +22,7 @@ type ProductCreator = { applyChangesToAllStores?: boolean summary?: string description?: string + descriptionFR?: string reference?: string mpn?: string | null upc?: string | null diff --git a/src/index.ts b/src/index.ts index 6a74a37e..71b55563 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ export type { GlobalMaildevConfig, GlobalKeycloakConfig, } from '@data/types/globals'; +export type {OrderCreator} from '@data/types/order'; export type { PageWaitForSelectorOptionsState, WaitForNavigationWaitUntil, @@ -27,6 +28,7 @@ export {default as dataCurrencies} from '@data/demo/currencies'; export {default as dataCustomers} from '@data/demo/customers'; export {default as dataGroups} from '@data/demo/groups'; export {default as dataModules} from '@data/demo/modules'; +export {default as dataOrders} from '@data/demo/orders'; export {default as dataOrderStatuses} from '@data/demo/orderStatuses'; export {default as dataPaymentMethods} from '@data/demo/paymentMethods'; export {default as dataSocialTitles} from '@data/demo/socialTitles'; @@ -44,8 +46,10 @@ export {default as FakerCurrency} from '@data/faker/currency'; export {default as FakerCustomer} from '@data/faker/customer'; export {default as FakerGroup} from '@data/faker/group'; export {default as FakerModule} from '@data/faker/module'; +export {default as FakerOrder} from '@data/faker/order'; export {default as FakerOrderStatus} from '@data/faker/orderStatus'; export {default as FakerPaymentMethod} from '@data/faker/paymentMethod'; +export {default as FakerProduct} from '@data/faker/product'; export {default as FakerState} from '@data/faker/state'; export {default as FakerTax} from '@data/faker/tax'; export {default as FakerTaxRule} from '@data/faker/taxRule'; @@ -59,6 +63,8 @@ export * as BOBasePage from '@pages/BO/BOBasePage'; export {default as boLoginPage} from '@pages/BO/login'; export {default as boDashboardPage} from '@pages/BO/dashboard'; export {default as boOrdersPage} from '@pages/BO/orders'; +export {default as boOrdersViewBasePage} from '@pages/BO/orders/view/viewOrderBasePage'; +export {default as boOrdersViewProductsBlockPage} from '@pages/BO/orders/view/productsBlock'; export {default as boModuleManagerPage} from '@pages/BO/modules/moduleManager'; export {default as boModuleManagerUninstalledModulesPage} from '@pages/BO/modules/moduleManager/uninstalledModules'; export {default as boDesignPositionsPage} from '@pages/BO/design/positions/index'; diff --git a/src/interfaces/BO/orders/index.ts b/src/interfaces/BO/orders/index.ts index 28c736f0..045f71dc 100644 --- a/src/interfaces/BO/orders/index.ts +++ b/src/interfaces/BO/orders/index.ts @@ -1,10 +1,55 @@ import {BOBasePagePageInterface} from '@interfaces/BO'; import type {Page} from '@playwright/test'; +import type OrderStatusData from '@data/faker/orderStatus'; export interface BOOrdersPageInterface extends BOBasePagePageInterface { readonly pageTitle: string; + bulkOpenInNewTabs(page:Page, isAllOrders:boolean, row:number[]):Promise; + bulkUpdateOrdersStatus(page:Page, status:string, isAllOrders:boolean, rows:number[]):Promise; + clickOnBulkActionsButton(page:Page):Promise; + clickOnMoreLink(page: Page, row: number): Promise; + downloadDeliverySlip(page:Page, row:number):Promise; + downloadInvoice(page:Page, row:number):Promise; + exportDataToCsv(page:Page):Promise; + filterOrders(page: Page, filterType: string, filterBy: string, value: string): Promise; + filterOrdersByDate(page:Page, dateFrom:string, dateTo:string):Promise; + getAllRowsColumnContent(page:Page, column:string):Promise; + getCustomerEmail(page: Page): Promise; + getCustomerInvoiceAddressDetails(page: Page): Promise; + getNumberOfElementInGrid(page:Page):Promise; + getNumberOfOrdersInPage(page:Page):Promise; + getOrderATIPrice(page:Page, row:number):Promise; + getOrderFromTable(page:Page, row:number):Promise<{ + id: number, + reference: string, + newClient:string, + delivery: string, + customer: string, + totalPaid: string, + payment: string, + status: string, + }> + getOrderIDNumber(page:Page, row:number):Promise; + getOrderInCsvFormat(page:Page, row:number):Promise; + getPaginationLabel(page: Page): Promise; + getProductDetailsFromTable(page: Page, row: number): Promise; + getProductsNumberFromTable(page: Page): Promise; + getShippingDetails(page: Page): Promise; getTextColumn(page: Page, columnName: string, row: number): Promise; - resetAndGetNumberOfLines(page: Page): Promise; + goToCreateOrderPage(page:Page):Promise; + goToOrder(page:Page, row:number):Promise; + openOrderDetails(page: Page): Promise; + paginationNext(page: Page): Promise; + paginationPrevious(page: Page): Promise; + previewOrder(page: Page, row: number): Promise; + resetAndGetNumberOfLines(page:Page):Promise; + resetFilter(page:Page):Promise; + selectAllOrders(page:Page):Promise; + selectOrdersRows(page:Page, rows:number[]):Promise; + selectPaginationLimit(page: Page, number: number): Promise; + setOrderStatus(page:Page, row:number, status:OrderStatusData):Promise; + sortTable(page: Page, sortBy: string, sortDirection: string): Promise; + viewCustomer(page:Page, row:number):Promise; } diff --git a/src/interfaces/BO/orders/view/productsBlock.ts b/src/interfaces/BO/orders/view/productsBlock.ts new file mode 100644 index 00000000..fb2b48e0 --- /dev/null +++ b/src/interfaces/BO/orders/view/productsBlock.ts @@ -0,0 +1,57 @@ +import {BOBasePagePageInterface} from '@interfaces/BO'; + +import type {Frame, Page} from '@playwright/test'; + +import type {ProductDiscount} from '@data/types/product'; + +export interface BOProductBlockPageInterface extends BOBasePagePageInterface { + addDiscount(page: Page, discountData: ProductDiscount): Promise; + addPartialRefundProduct(page: Page, productRow: number, quantity: number, amount: number, shipping: number): Promise + addProductToCart(page: Page, quantity: number, createNewInvoice: boolean): Promise; + addQuantity(page: Page, quantity: number): Promise; + cancelAddProductToCart(page: Page): Promise; + checkGenerateVoucher(page: Page, toEnable: boolean): Promise; + checkReturnedQuantity(page: Page, row: number): Promise; + clickOnReturnProducts(page: Page): Promise; + deleteDiscount(page: Page, row: number): Promise; + deleteProduct(page: Page, row: number): Promise; + getInvoicesFromSelectOptions(page: Page): Promise; + getNewInvoiceCarrierName(page: Page): Promise; + getOrderTotalDiscounts(page: Page): Promise; + getOrderTotalPrice(page: Page): Promise; + getOrderTotalProducts(page: Page): Promise; + getOrderTotalShipping(page: Page): Promise + getOrderWrappingTotal(page: Page): Promise; + getPaginationLabel(page: Page): Promise; + getProductDetails(page: Frame | Page, row: number):Promise<{ + reference: string, + total: number, + quantity: number, + productId: string, + name: string, + available: number, + orderDetailId: string, + basePrice: number, + }>; + getProductNameFromTable(page: Page, row: number): Promise; + getProductsNumber(page: Frame | Page): Promise; + getSearchedProductDetails(page: Page): Promise<{ stockLocation: string, available: number, price: number}>; + getSearchedProductInformation(page: Page): Promise<{ available: number, price: number}>; + getTextColumnFromDiscountTable(page: Page, column: string, row: number): Promise; + isAddButtonDisabled(page: Page): Promise; + isAddProductTableRowVisible(page: Page): Promise; + isDiscountListTableVisible(page: Page): Promise; + isFreeShippingSelected(page: Page): Promise; + isRefundedColumnVisible(page: Page): Promise; + modifyProductPrice(page: Page, row: number, price: number): Promise; + modifyProductPriceForMultiInvoice(page: Page, row: number, price: number): Promise; + modifyProductQuantity(page: Page, row: number, quantity: number): Promise; + paginationNext(page: Page): Promise; + paginationPrevious(page: Page): Promise; + searchProduct(page: Page, name: string): Promise; + selectFreeShippingCheckbox(page: Page): Promise; + selectInvoice(page: Page, invoice: string): Promise; + selectPaginationLimit(page: Page, number: number): Promise; + setReturnedProductQuantity(page: Page, row: number, quantity: number): Promise; + updateProductPrice(page: Page, price: number): Promise; +} diff --git a/src/interfaces/BO/orders/view/viewOrderBasePage.ts b/src/interfaces/BO/orders/view/viewOrderBasePage.ts new file mode 100644 index 00000000..9c6075e5 --- /dev/null +++ b/src/interfaces/BO/orders/view/viewOrderBasePage.ts @@ -0,0 +1,25 @@ +import {BOBasePagePageInterface} from '@interfaces/BO'; + +import type {Page} from '@playwright/test'; + +export interface BOViewOrderBasePageInterface extends BOBasePagePageInterface { + readonly pageTitle: string; + + clickOnPartialRefund(page: Page): Promise; + clickOnReturnProductsButton(page: Page): Promise; + doesStatusExist(page: Page, statusName: string): Promise; + getOrderID(page: Page): Promise; + getOrderReference(page: Page): Promise; + getOrderStatus(page: Page): Promise; + getOrderStatusID(page: Page): Promise; + isDeliverySlipButtonVisible(page: Page): Promise; + isPartialRefundButtonVisible(page: Page): Promise; + isReturnProductsButtonDisabled(page: Page): Promise; + isReturnProductsButtonVisible(page: Page): Promise; + isUpdateStatusButtonDisabled(page: Page): Promise; + isViewInvoiceButtonVisible(page: Page): Promise; + modifyOrderStatus(page: Page, status: string): Promise; + selectOrderStatus(page: Page, status: string): Promise; + viewDeliverySlip(page: Page): Promise; + viewInvoice(page: Page): Promise; +} diff --git a/src/pages/BO/orders/view/productsBlock.ts b/src/pages/BO/orders/view/productsBlock.ts new file mode 100644 index 00000000..1e33a239 --- /dev/null +++ b/src/pages/BO/orders/view/productsBlock.ts @@ -0,0 +1,9 @@ +import type {BOProductBlockPageInterface} from '@interfaces/BO/orders/view/productsBlock'; + +/* eslint-disable global-require, @typescript-eslint/no-var-requires */ +function requirePage(): BOProductBlockPageInterface { + return require('@versions/develop/pages/BO/orders/view/productsBlock'); +} +/* eslint-enable global-require, @typescript-eslint/no-var-requires */ + +export default requirePage(); diff --git a/src/pages/BO/orders/view/viewOrderBasePage.ts b/src/pages/BO/orders/view/viewOrderBasePage.ts new file mode 100644 index 00000000..61de5aa6 --- /dev/null +++ b/src/pages/BO/orders/view/viewOrderBasePage.ts @@ -0,0 +1,10 @@ +import type {BOViewOrderBasePageInterface} from '@interfaces/BO/orders/view/viewOrderBasePage'; + +/* eslint-disable global-require, @typescript-eslint/no-var-requires */ +function requirePage(): BOViewOrderBasePageInterface { + return require('@versions/develop/pages/BO/orders/view/viewOrderBasePage'); +} + +/* eslint-enable global-require, @typescript-eslint/no-var-requires */ + +export default requirePage(); diff --git a/src/versions/develop/pages/BO/orders/view/productsBlock.ts b/src/versions/develop/pages/BO/orders/view/productsBlock.ts new file mode 100644 index 00000000..2fccd5f8 --- /dev/null +++ b/src/versions/develop/pages/BO/orders/view/productsBlock.ts @@ -0,0 +1,781 @@ +import {BOProductBlockPageInterface} from '@interfaces/BO/orders/view/productsBlock'; +import BOBasePage from '@pages/BO/BOBasePage'; + +import type {ProductDiscount} from '@data/types/product'; + +import type {Frame, Page} from 'playwright'; + +/** + * Products block, contains functions that can be used on view/edit products block on view order page + * @class + * @extends BOBasePage + */ +class ProductsBlock extends BOBasePage implements BOProductBlockPageInterface { + private readonly productsCountSpan: string; + + private readonly orderProductsLoading: string; + + private readonly orderProductsTable: string; + + private readonly generateVoucherCheckbox: string; + + private readonly returnProductButton: string; + + private readonly returnQuantityInput: (row: number) => string; + + private readonly returnQuantityCheckbox: (row: number) => string; + + private readonly orderProductsRowTable: (row: number) => string; + + private readonly orderProductsTableNameColumn: (row: number) => string; + + private readonly orderProductsTableProductName: (row: number) => string; + + private readonly orderProductsTableProductReference: (row: number) => string; + + private readonly orderProductsTableProductBasePrice: (row: number) => string; + + private readonly orderProductsTableProductQuantity: (row: number) => string; + + private readonly orderProductsTableProductAvailable: (row: number) => string; + + private readonly orderProductsTableProductPrice: (row: number) => string; + + private readonly deleteProductButton: (row: number) => string; + + private readonly editProductButton: (row: number) => string; + + private readonly productQuantitySpan: (row: number) => string; + + private readonly orderProductsEditRowTable: string; + + private readonly editProductQuantityInput: string; + + private readonly editProductPriceInput: string; + + private readonly updateProductButton: string; + + private readonly modalConfirmNewPrice: string; + + private readonly modalConfirmNewPriceSubmitButton: string; + + private readonly orderTotalPriceSpan: string; + + private readonly orderWrappingTotal: string; + + private readonly orderTotalProductsSpan: string; + + private readonly orderTotalDiscountsSpan: string; + + private readonly orderTotalShippingSpan: string; + + private readonly addProductButton: string; + + private readonly addProductTableRow: string; + + private readonly addProductRowSearch: string; + + private readonly addProductRowQuantity: string; + + private readonly addProductRowPrice: string; + + private readonly addProductRowStockLocation: string; + + private readonly addProductAvailable: string; + + private readonly addProductTotalPrice: string; + + private readonly addProductInvoiceSelect: string; + + private readonly addProductNewInvoiceCarrierName: string; + + private readonly addProductNewInvoiceFreeShippingCheckbox: string; + + private readonly addProductNewInvoiceFreeShippingDiv: string; + + private readonly addProductAddButton: string; + + private readonly addProductCancelButton: string; + + private readonly addProductModalConfirmNewInvoice: string; + + private readonly addProductCreateNewInvoiceButton: string; + + private readonly addDiscountButton: string; + + private readonly orderDiscountModal: string; + + private readonly addOrderCartRuleNameInput: string; + + private readonly addOrderCartRuleTypeSelect: string; + + private readonly addOrderCartRuleValueInput: string; + + private readonly addOrderCartRuleAddButton: string; + + private readonly discountListTable: string; + + private readonly discountListRowTable: (row: number) => string; + + private readonly discountListNameColumn: (row: number) => string; + + private readonly discountListDiscountColumn: (row: number) => string; + + private readonly discountDeleteIcon: (row: number) => string; + + private readonly refundProductQuantity: (row: number) => string; + + private readonly refundProductAmount: (row: number) => string; + + private readonly refundShippingCost: (row: number) => string; + + private readonly partialRefundSubmitButton: string; + + private readonly paginationLimitSelect: string; + + private readonly paginationLabel: string; + + private readonly paginationNextLink: string; + + private readonly paginationPreviousLink: string; + + private readonly refundProductColumn: string; + + /** + * @constructs + * Setting up texts and selectors to use on products block + */ + constructor() { + super(); + + // Products block header + this.productsCountSpan = '#orderProductsPanelCount'; + this.orderProductsLoading = '#orderProductsLoading'; + + // Return block + this.returnQuantityInput = (row: number) => `[id*=cancel_product_quantity]:nth-child(${row})`; + this.returnQuantityCheckbox = (row: number) => `tr:nth-child(${row}) div.cancel-product-selector i`; + this.generateVoucherCheckbox = '#orderProductsPanel div.refund-voucher i'; + this.returnProductButton = '#cancel_product_save'; + + // Products table + this.orderProductsTable = '#orderProductsTable'; + this.orderProductsRowTable = (row: number) => `${this.orderProductsTable} tbody tr:nth-child(${row})`; + this.orderProductsTableNameColumn = (row: number) => `${this.orderProductsRowTable(row)} td.cellProductName`; + this.orderProductsTableProductName = (row: number) => `${this.orderProductsTableNameColumn(row)} p.productName`; + this.orderProductsTableProductReference = (row: number) => `${this.orderProductsTableNameColumn(row)} p.productReference`; + this.orderProductsTableProductBasePrice = (row: number) => `${this.orderProductsRowTable(row)} td.cellProductUnitPrice`; + this.orderProductsTableProductQuantity = (row: number) => `${this.orderProductsRowTable(row)} td.cellProductQuantity`; + this.orderProductsTableProductAvailable = (row: number) => `${this.orderProductsRowTable(row)} + td.cellProductAvailableQuantity`; + this.orderProductsTableProductPrice = (row: number) => `${this.orderProductsRowTable(row)} td.cellProductTotalPrice`; + this.deleteProductButton = (row: number) => `${this.orderProductsRowTable(row)} button.js-order-product-delete-btn`; + this.editProductButton = (row: number) => `${this.orderProductsRowTable(row)} button.js-order-product-edit-btn`; + this.productQuantitySpan = (row: number) => `${this.orderProductsRowTable(row)} td.cellProductQuantity span`; + this.refundProductColumn = `${this.orderProductsTable} th.cellProductRefunded`; + + // Edit row table + this.orderProductsEditRowTable = `${this.orderProductsTable} tbody tr.editProductRow`; + this.editProductQuantityInput = `${this.orderProductsEditRowTable} input.editProductQuantity`; + this.editProductPriceInput = `${this.orderProductsEditRowTable} input.editProductPriceTaxIncl`; + this.updateProductButton = `${this.orderProductsEditRowTable} button.productEditSaveBtn`; + this.modalConfirmNewPrice = '#modal-confirm-new-price'; + this.modalConfirmNewPriceSubmitButton = `${this.modalConfirmNewPrice} button.btn-confirm-submit`; + + // Total order + this.orderTotalPriceSpan = '#orderTotal'; + this.orderWrappingTotal = '#orderWrappingTotal'; + + // Add discount + this.orderTotalProductsSpan = '#orderProductsTotal'; + this.orderTotalDiscountsSpan = '#orderDiscountsTotal'; + this.orderTotalShippingSpan = '#orderShippingTotal'; + + // Add product + this.addProductButton = '#addProductBtn'; + this.addProductTableRow = '#addProductTableRow'; + this.addProductRowSearch = '#add_product_row_search'; + this.addProductRowQuantity = '#add_product_row_quantity'; + this.addProductRowPrice = '#add_product_row_price_tax_included'; + this.addProductRowStockLocation = '#addProductLocation'; + this.addProductAvailable = '#addProductAvailable'; + this.addProductTotalPrice = '#addProductTotalPrice'; + this.addProductInvoiceSelect = '#add_product_row_invoice'; + this.addProductNewInvoiceCarrierName = '#addProductNewInvoiceInfo div p[data-role=\'carrier-name\']'; + this.addProductNewInvoiceFreeShippingCheckbox = '#add_product_row_free_shipping'; + this.addProductNewInvoiceFreeShippingDiv = '#addProductNewInvoiceInfo td div.md-checkbox'; + + this.addProductAddButton = '#add_product_row_add'; + this.addProductCancelButton = '#add_product_row_cancel'; + this.addProductModalConfirmNewInvoice = '#modal-confirm-new-invoice'; + this.addProductCreateNewInvoiceButton = `${this.addProductModalConfirmNewInvoice} .btn-confirm-submit`; + + // Add discount + this.addDiscountButton = 'button[data-target=\'#addOrderDiscountModal\']'; + this.orderDiscountModal = '#addOrderDiscountModal'; + this.addOrderCartRuleNameInput = '#add_order_cart_rule_name'; + this.addOrderCartRuleTypeSelect = '#add_order_cart_rule_type'; + this.addOrderCartRuleValueInput = '#add_order_cart_rule_value'; + this.addOrderCartRuleAddButton = '#add_order_cart_rule_submit'; + + // Discount table + this.discountListTable = 'table.table.discountList'; + this.discountListRowTable = (row: number) => `${this.discountListTable} tbody tr:nth-child(${row})`; + this.discountListNameColumn = (row: number) => `${this.discountListRowTable(row)} td.discountList-name`; + this.discountListDiscountColumn = (row: number) => `${this.discountListRowTable(row)} td[data-role='discountList-value']`; + this.discountDeleteIcon = (row: number) => `${this.discountListRowTable(row)} a.delete-cart-rule`; + + // Refund form + this.refundProductQuantity = (row: number) => `${this.orderProductsRowTable(row)} input[id*='cancel_product_quantity']`; + this.refundProductAmount = (row: number) => `${this.orderProductsRowTable(row)} input[id*='cancel_product_amount']`; + this.refundShippingCost = (row: number) => `${this.orderProductsRowTable(row)} input[id*='cancel_product_shipping_amount']`; + this.partialRefundSubmitButton = 'button#cancel_product_save'; + + // Pagination selectors + this.paginationLimitSelect = '#orderProductsTablePaginationNumberSelector'; + this.paginationLabel = '#orderProductsNavPagination .page-item.active'; + this.paginationNextLink = '#orderProductsTablePaginationNext'; + this.paginationPreviousLink = '#orderProductsTablePaginationPrev'; + } + + /* + Methods + */ + + // Methods for create partial refund + /** + * Add partial refund product + * @param page {Page} Browser tab + * @param productRow {number} Product row on table + * @param quantity {number} Quantity value to set + * @param amount {number} Amount value to set + * @param shipping {number} Shipping cost to set + * @returns {Promise} + */ + async addPartialRefundProduct( + page: Page, + productRow: number, + quantity: number = 0, + amount: number = 0, + shipping: number = 0, + ): Promise { + await this.waitForVisibleSelector(page, this.refundProductQuantity(1)); + await this.setValue(page, this.refundProductQuantity(productRow), quantity); + if (amount !== 0) { + await this.setValue(page, this.refundProductAmount(productRow), amount); + } + if (shipping !== 0) { + await this.setValue(page, this.refundShippingCost(productRow), shipping); + } + await this.clickAndWaitForLoadState(page, this.partialRefundSubmitButton); + + return this.getAlertSuccessBlockParagraphContent(page); + } + + // Methods for product block + /** + * Get products number + * @param page {Frame|Page} Browser tab + * @returns {Promise} + */ + async getProductsNumber(page: Frame | Page): Promise { + return this.getNumberFromText(page, this.productsCountSpan); + } + + /** + * Get product name from products table + * @param page {Page} Browser tab + * @param row {number} Product row on table + * @returns {Promise} + */ + async getProductNameFromTable(page: Page, row: number): Promise { + return this.getTextContent(page, this.orderProductsTableProductName(row)); + } + + /** + * Modify product quantity + * @param page {Page} Browser tab + * @param row {number} Product row on table + * @param quantity {number} Quantity to edit + * @returns {Promise} + */ + async modifyProductQuantity(page: Page, row: number, quantity: number): Promise { + await this.dialogListener(page); + await Promise.all([ + page.locator(this.editProductButton(row)).click(), + this.waitForVisibleSelector(page, this.editProductQuantityInput), + ]); + await this.setValue(page, `${this.editProductQuantityInput}:visible`, quantity); + await Promise.all([ + page.locator(`${this.updateProductButton}:visible`).first().click(), + this.waitForVisibleSelector(page, this.editProductQuantityInput), + ]); + if (await this.elementVisible(page, this.orderProductsLoading, 2000)) { + await this.waitForHiddenSelector(page, this.orderProductsLoading); + } + await this.waitForVisibleSelector(page, this.productQuantitySpan(row)); + + return parseFloat(await this.getTextContent(page, this.productQuantitySpan(row))); + } + + /** + * Modify product price + * @param page {Page} Browser tab + * @param row {number} Product row on table + * @param price {number} Price to edit + * @returns {Promise} + */ + async modifyProductPrice(page: Page, row: number, price: number): Promise { + await this.dialogListener(page); + + await this.waitForSelectorAndClick(page, this.editProductButton(row)); + await this.setValue(page, `${this.editProductPriceInput}:visible`, price); + + await Promise.all([ + page.locator(this.updateProductButton).first().click(), + this.waitForHiddenSelector(page, this.editProductPriceInput), + ]); + + if (await this.elementVisible(page, this.orderProductsLoading, 1000)) { + await this.waitForHiddenSelector(page, this.orderProductsLoading); + } + await this.waitForVisibleSelector(page, this.orderProductsTableProductBasePrice(row)); + } + + /** + * Modify product price for multi invoice + * @param page {Page} Browser tab + * @param row {number} Product row on table + * @param price {number} Price to edit + * @returns {Promise} + */ + async modifyProductPriceForMultiInvoice(page: Page, row: number, price: number): Promise { + await this.dialogListener(page); + + await Promise.all([ + page.locator(this.editProductButton(row)).click(), + this.waitForVisibleSelector(page, this.editProductPriceInput), + ]); + await this.setValue(page, `${this.editProductPriceInput}:visible`, price); + + await Promise.all([ + page.locator(this.updateProductButton).first().click(), + this.waitForVisibleSelector(page, this.modalConfirmNewPrice), + ]); + + await page.locator(this.modalConfirmNewPriceSubmitButton).click(); + + if (await this.elementVisible(page, this.orderProductsLoading, 1000)) { + await this.waitForHiddenSelector(page, this.orderProductsLoading); + } + + await this.waitForVisibleSelector(page, this.orderProductsTableProductName(row)); + } + + /** + * Delete product + * @param page {Page} Browser tab + * @param row {number} Product row on table + * @returns {Promise} + */ + async deleteProduct(page: Page, row: number): Promise { + await this.dialogListener(page); + await this.closeGrowlMessage(page); + await Promise.all([ + page.waitForResponse((response) => response.url().includes('/products?_token')), + this.waitForSelectorAndClick(page, this.deleteProductButton(row)), + ]); + + return this.getGrowlMessageContent(page); + } + + /** + * Get total price from products tab + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderTotalPrice(page: Page): Promise { + return this.getPriceFromText(page, this.orderTotalPriceSpan, 1000); + } + + /** + * Get order wrapping total + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderWrappingTotal(page: Page): Promise { + return this.getPriceFromText(page, this.orderWrappingTotal); + } + + /** + * Get order total discounts + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderTotalProducts(page: Page): Promise { + return this.getPriceFromText(page, this.orderTotalProductsSpan); + } + + /** + * Get order total discounts + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderTotalDiscounts(page: Page): Promise { + return this.getPriceFromText(page, this.orderTotalDiscountsSpan, 0, false); + } + + /** + * Get order total shipping + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderTotalShipping(page: Page): Promise { + return this.getPriceFromText(page, this.orderTotalShippingSpan, 0, false); + } + + /** + * Create new invoice + * @param page {Page} Browser tab + * @param invoice {string} The invoice to select from dropdown list + * @returns {Promise} + */ + async selectInvoice(page: Page, invoice: string = 'Create a new invoice'): Promise { + await this.selectByVisibleText(page, this.addProductInvoiceSelect, invoice); + } + + /** + * Get invoices list from select options + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getInvoicesFromSelectOptions(page: Page): Promise { + return this.getTextContent(page, this.addProductInvoiceSelect); + } + + /** + * Get carrier name when creating new invoice + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getNewInvoiceCarrierName(page: Page): Promise { + return this.getTextContent(page, this.addProductNewInvoiceCarrierName); + } + + /** + * Is free shipping selected + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isFreeShippingSelected(page: Page): Promise { + return this.isChecked(page, this.addProductNewInvoiceFreeShippingCheckbox); + } + + /** + * Select free shipping checkbox + * @param page {Page} Browser tab + * @returns {Promise} + */ + async selectFreeShippingCheckbox(page: Page): Promise { + await this.waitForSelectorAndClick(page, this.addProductNewInvoiceFreeShippingDiv); + } + + /** + * Add product quantity from add product input + * @param page {Page} Browser tab + * @param quantity {number} Product quantity to add + * @returns {Promise} + */ + async addQuantity(page: Page, quantity: number): Promise { + await this.setValue(page, this.addProductRowQuantity, quantity); + } + + /** + * Set new product price from add product input + * @param page {Page} Browser tab + * @param price {float} Value of price to update + * @returns {Promise} + */ + async updateProductPrice(page: Page, price: number): Promise { + await this.setValue(page, this.addProductRowPrice, price); + } + + /** + * Add product to cart + * @param page {Page} Browser tab + * @param quantity {number} Product quantity to add + * @param createNewInvoice {boolean} True if we need to create new invoice + * @returns {Promise} + */ + async addProductToCart(page: Page, quantity: number = 1, createNewInvoice: boolean = false): Promise { + await this.closeGrowlMessage(page); + if (quantity !== 1) { + await this.addQuantity(page, quantity); + } + await this.waitForSelectorAndClick(page, this.addProductAddButton, 1000); + if (createNewInvoice) { + await this.waitForSelectorAndClick(page, this.addProductCreateNewInvoiceButton); + } + + return this.getGrowlMessageContent(page); + } + + /** + * Cancel add product to cart + * @param page {Page} Browser tab + * @returns {Promise} + */ + async cancelAddProductToCart(page: Page): Promise { + await this.waitForSelectorAndClick(page, this.addProductCancelButton); + } + + /** + * Is add button disabled + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isAddButtonDisabled(page: Page): Promise { + return this.elementVisible(page, `${this.addProductAddButton}[disabled]`, 1000); + } + + /** + * Is add product table row visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isAddProductTableRowVisible(page: Page): Promise { + return this.elementVisible(page, this.addProductTableRow, 1000); + } + + /** + * Get product details + * @param page {Frame|Page} Browser tab + * @param row {number} Product row on table + * @returns {Promise<{orderDetailId:string, productId:number, name: string, reference:string, basePrice: number, quantity: number, available: number, total: number}>} + */ + async getProductDetails(page: Frame | Page, row: number) :Promise<{ + reference: string, + total: number, + quantity: number, + productId: string, + name: string, + available: number, + orderDetailId: string, + basePrice: number, + }> { + return { + orderDetailId: await this.getAttributeContent(page, this.editProductButton(row), 'data-order-detail-id'), + productId: await this.getAttributeContent(page, this.editProductButton(row), 'data-product-id'), + name: await this.getTextContent(page, this.orderProductsTableProductName(row)), + reference: await this.getTextContent(page, this.orderProductsTableProductReference(row)), + basePrice: parseFloat((await this.getTextContent( + page, + this.orderProductsTableProductBasePrice(row))).replace('€', ''), + ), + quantity: parseInt(await this.getTextContent(page, this.orderProductsTableProductQuantity(row)), 10), + available: parseInt(await this.getTextContent(page, this.orderProductsTableProductAvailable(row)), 10), + total: parseFloat((await this.getTextContent(page, this.orderProductsTableProductPrice(row))).replace('€', '')), + }; + } + + /** + * Is refunded column visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isRefundedColumnVisible(page: Page): Promise { + return this.elementVisible(page, this.refundProductColumn, 2000); + } + + /** + * Search product + * @param page {Page} Browser tab + * @param name {string} Product name to search + * @returns {Promise} + */ + async searchProduct(page: Page, name: string): Promise { + await this.waitForSelectorAndClick(page, this.addProductButton); + await this.setValue(page, this.addProductRowSearch, name); + await this.waitForSelectorAndClick(page, `${this.addProductTableRow} a`); + } + + /** + * Get searched product details + * @param page {Page} Browser tab + * @returns {Promise<{stockLocation: string, available: number, price:number;}>} + */ + async getSearchedProductDetails(page: Page): Promise<{ stockLocation: string, available: number, price: number}> { + return { + stockLocation: await this.getTextContent(page, this.addProductRowStockLocation), + available: parseInt(await this.getTextContent(page, this.addProductAvailable), 10), + price: parseFloat(await this.getTextContent(page, this.addProductTotalPrice)), + }; + } + + /** + * Get searched product information + * @param page {Page} Browser tab + * @returns {Promise<{available: number, price: number}>} + */ + async getSearchedProductInformation(page: Page): Promise<{ available: number, price: number}> { + return { + available: parseInt(await this.getTextContent(page, this.addProductAvailable), 10), + price: parseFloat(await this.getTextContent(page, this.addProductTotalPrice)), + }; + } + + /** + * Add discount + * @param page {Page} Browser tab + * @param discountData {ProductDiscount} Data to set on discount form + * @returns {Promise} + */ + async addDiscount(page: Page, discountData: ProductDiscount): Promise { + await this.waitForSelectorAndClick(page, this.addDiscountButton); + await this.waitForVisibleSelector(page, this.orderDiscountModal); + await this.waitForSelectorAndClick(page, this.addOrderCartRuleNameInput); + await this.setValue(page, this.addOrderCartRuleNameInput, discountData.name); + + if (discountData.type !== 'Free shipping') { + await this.setValue(page, this.addOrderCartRuleValueInput, discountData.value); + } + await this.selectByVisibleText(page, this.addOrderCartRuleTypeSelect, discountData.type); + + await this.waitForVisibleSelector(page, `${this.addOrderCartRuleAddButton}:not([disabled])`); + await page.locator(this.addOrderCartRuleAddButton).click(); + await this.waitForVisibleSelector(page, this.alertBlock); + + return this.getTextContent(page, this.alertBlock); + } + + /** + * Is discount table visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isDiscountListTableVisible(page: Page): Promise { + return this.elementVisible(page, this.discountListTable, 2000); + } + + /** + * Get text column from discount table + * @param page {Page} Browser tab + * @param column {string} Column name on the table + * @param row {number} Row on table + * @returns {Promise} + */ + async getTextColumnFromDiscountTable(page: Page, column: string, row: number = 1): Promise { + switch (column) { + case 'name': + return this.getTextContent(page, this.discountListNameColumn(row)); + case 'value': + return this.getTextContent(page, this.discountListDiscountColumn(row)); + default: + throw new Error(`The column ${column} is not visible in discount table`); + } + } + + /** + * Delete discount + * @param page {Page} Browser tab + * @param row {number} Row on table + * @returns {Promise} + */ + async deleteDiscount(page: Page, row: number = 1): Promise { + await this.waitForSelectorAndClick(page, this.discountDeleteIcon(row)); + + return this.getTextContent(page, this.alertBlock); + } + + // Methods for product list pagination + /** + * Get pagination label + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getPaginationLabel(page: Page): Promise { + return this.getTextContent(page, this.paginationLabel); + } + + /** + * Click on next + * @param page {Page} Browser tab + * @returns {Promise} + */ + async paginationNext(page: Page): Promise { + await this.waitForSelectorAndClick(page, this.paginationNextLink); + + return this.getPaginationLabel(page); + } + + /** + * Click on previous + * @param page {Page} Browser tab + * @returns {Promise} + */ + async paginationPrevious(page: Page): Promise { + await this.scrollTo(page, this.productsCountSpan); + await this.waitForSelectorAndClick(page, this.paginationPreviousLink); + + return this.getPaginationLabel(page); + } + + /** + * Select pagination limit + * @param page {Page} Browser tab + * @param number {number} Pagination number to select + * @returns {Promise} + */ + async selectPaginationLimit(page: Page, number: number): Promise { + await this.selectByVisibleText(page, this.paginationLimitSelect, number); + await this.waitForVisibleSelector(page, this.orderProductsTableProductName(1)); + + return this.elementVisible(page, this.paginationNextLink, 1000); + } + + // Methods to return products + + /** + * Set returned product quantity + * @param page {Page} Browser tab + * @param row {number} Row in return product table + * @param quantity {number} Quantity to return + * @returns {Promise} + */ + async setReturnedProductQuantity(page: Page, row: number = 1, quantity: number = 1): Promise { + await this.setValue(page, this.returnQuantityInput(row), quantity); + } + + /** + * Check returned quantity + * @param page {Page} Browser tab + * @param row {number} Row in return product table + * @returns {Promise} + */ + async checkReturnedQuantity(page: Page, row: number = 1): Promise { + await this.setChecked(page, this.returnQuantityCheckbox(row), true, true); + } + + /** + * Check generate voucher + * @param page {Page} Browser tab + * @param toEnable {boolean} True if we need to enable generate voucher + * @returns {Promise} + */ + async checkGenerateVoucher(page: Page, toEnable: boolean): Promise { + await this.setChecked(page, this.generateVoucherCheckbox, toEnable, true); + } + + /** + * Click on return products + * @param page {Page} Browser tab + * @returns {Promise} + */ + async clickOnReturnProducts(page: Page): Promise { + await this.clickAndWaitForURL(page, this.returnProductButton); + + return this.getAlertBlockContent(page); + } +} + +module.exports = new ProductsBlock(); diff --git a/src/versions/develop/pages/BO/orders/view/viewOrderBasePage.ts b/src/versions/develop/pages/BO/orders/view/viewOrderBasePage.ts new file mode 100644 index 00000000..9a75bd6d --- /dev/null +++ b/src/versions/develop/pages/BO/orders/view/viewOrderBasePage.ts @@ -0,0 +1,278 @@ +import BOBasePage from '@pages/BO/BOBasePage'; + +import type {Page} from 'playwright'; + +import {BOViewOrderBasePageInterface} from '@interfaces/BO/orders/view/viewOrderBasePage'; + +/** + * View order base page, contains functions that can be used on view/edit order page + * @class + * @extends BOBasePage + */ +class ViewOrderBasePage extends BOBasePage implements BOViewOrderBasePageInterface { + public readonly pageTitle: string; + + public readonly partialRefundValidationMessage: string; + + public readonly successfulAddProductMessage: string; + + public readonly successfulDeleteProductMessage: string; + + public readonly errorMinimumQuantityMessage: string; + + public readonly errorAddSameProduct: string; + + public readonly errorAddSameProductInInvoice: (invoice: string) => string; + + public readonly noAvailableDocumentsMessage: string; + + public readonly updateSuccessMessage: string; + + public readonly commentSuccessMessage: string; + + public readonly validationSendMessage: string; + + public readonly errorAssignSameStatus: string; + + public readonly discountMustBeNumberErrorMessage: string; + + public readonly invalidPercentValueErrorMessage: string; + + public readonly percentValueNotPositiveErrorMessage: string; + + public readonly discountCannotExceedTotalErrorMessage: string; + + private readonly orderID: string; + + private readonly orderReference: string; + + private readonly orderStatusesSelect: string; + + private readonly orderStatusesOptionSelect: string; + + private readonly updateStatusButton: string; + + private readonly viewInvoiceButton: string; + + private readonly viewDeliverySlipButton: string; + + private readonly partialRefundButton: string; + + private readonly returnProductsButton: string; + + /** + * @constructs + * Setting up texts and selectors to use on view/edit order page + */ + constructor() { + super(); + + this.pageTitle = 'Order'; + this.partialRefundValidationMessage = 'A partial refund was successfully created.'; + this.successfulAddProductMessage = 'The product was successfully added.'; + this.successfulDeleteProductMessage = 'The product was successfully removed.'; + this.errorMinimumQuantityMessage = 'Minimum quantity of "3" must be added'; + this.errorAddSameProduct = 'This product is already in your order, please edit the quantity instead.'; + this.errorAddSameProductInInvoice = (invoice: string) => `This product is already in the invoice #${invoice}, ` + + 'please edit the quantity instead.'; + this.noAvailableDocumentsMessage = 'There is no available document'; + this.updateSuccessMessage = 'Update successful'; + this.commentSuccessMessage = 'Comment successfully added.'; + this.validationSendMessage = 'The message was successfully sent to the customer.'; + this.errorAssignSameStatus = 'The order has already been assigned this status.'; + this.discountMustBeNumberErrorMessage = 'Discount value must be a number.'; + this.invalidPercentValueErrorMessage = 'Percent value cannot exceed 100.'; + this.percentValueNotPositiveErrorMessage = 'Percent value must be greater than 0.'; + this.discountCannotExceedTotalErrorMessage = 'Discount value cannot exceed the total price of this order.'; + + // Header selectors + this.alertBlock = 'div.alert[role=\'alert\'] div.alert-text'; + this.orderID = '.title-content strong[data-role=order-id]'; + this.orderReference = '.title-content strong[data-role=order-reference]'; + this.orderStatusesSelect = '#update_order_status_action_input'; + this.orderStatusesOptionSelect = `${this.orderStatusesSelect} option`; + this.updateStatusButton = '#update_order_status_action_btn'; + this.viewInvoiceButton = 'form.order-actions-invoice a[data-role=view-invoice]'; + this.viewDeliverySlipButton = 'form.order-actions-delivery a[data-role=view-delivery-slip]'; + this.partialRefundButton = 'button.partial-refund-display'; + this.returnProductsButton = '#order-view-page button.return-product-display'; + } + + /* + Methods + */ + /** + * Get order ID + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderID(page: Page): Promise { + return this.getNumberFromText(page, this.orderID); + } + + /** + * Get order reference + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderReference(page: Page): Promise { + return this.getTextContent(page, this.orderReference); + } + + // Methods for order actions + /** + * Does status exist + * @param page {Page} Browser tab + * @param statusName {string} Status to check + * @returns {Promise} + */ + async doesStatusExist(page: Page, statusName: string): Promise { + const options = await page + .locator(this.orderStatusesOptionSelect) + .allTextContents(); + + return options.indexOf(statusName) !== -1; + } + + /** + * Is update status button disabled + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isUpdateStatusButtonDisabled(page: Page): Promise { + return this.elementVisible(page, `${this.updateStatusButton}[disabled]`, 1000); + } + + /** + * Select order status + * @param page {Page} Browser tab + * @param status {string} Status to edit + * @returns {Promise} + */ + async selectOrderStatus(page: Page, status: string): Promise { + await this.selectByVisibleText(page, this.orderStatusesSelect, status); + } + + /** + * Get order status + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderStatus(page: Page): Promise { + return this.getTextContent(page, `${this.orderStatusesOptionSelect}[selected='selected']`, false); + } + + /** + * Get order status ID + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getOrderStatusID(page: Page): Promise { + return parseInt( + await this.getAttributeContent(page, `${this.orderStatusesOptionSelect}[selected='selected']`, 'value'), + 10, + ); + } + + /** + * Modify the order status + * @param page {Page} Browser tab + * @param status {string} Status to edit + * @returns {Promise} + */ + async modifyOrderStatus(page: Page, status: string): Promise { + const actualStatus = await this.getOrderStatus(page); + + if (status !== actualStatus) { + await this.selectByVisibleText(page, this.orderStatusesSelect, status); + await this.clickAndWaitForURL(page, this.updateStatusButton); + return this.getOrderStatus(page); + } + + return actualStatus; + } + + /** + * Is view invoice button visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isViewInvoiceButtonVisible(page: Page): Promise { + return this.elementVisible(page, this.viewInvoiceButton, 1000); + } + + /** + * Click on view invoice button to download the invoice + * @param page {Page} Browser tab + * @returns {Promise} + */ + async viewInvoice(page: Page): Promise { + return this.clickAndWaitForDownload(page, this.viewInvoiceButton); + } + + /** + * Is partial refund button visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isPartialRefundButtonVisible(page: Page): Promise { + return this.elementVisible(page, this.partialRefundButton, 1000); + } + + /** + * Click on partial refund button + * @param page {Page} Browser tab + * @returns {Promise} + */ + async clickOnPartialRefund(page: Page): Promise { + await page.locator(this.partialRefundButton).click(); + } + + /** + * Is delivery slip button visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isDeliverySlipButtonVisible(page: Page): Promise { + return this.elementVisible(page, this.viewDeliverySlipButton, 1000); + } + + /** + * Click on view delivery slip button to download the invoice + * @param page {Page} Browser tab + * @returns {Promise} + */ + async viewDeliverySlip(page: Page): Promise { + return this.clickAndWaitForDownload(page, this.viewDeliverySlipButton); + } + + /** + * Is return products button visible + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isReturnProductsButtonVisible(page: Page): Promise { + return this.elementVisible(page, this.returnProductsButton, 2000); + } + + /** + * Is return product button disabled + * @param page {Page} Browser tab + * @returns {Promise} + */ + async isReturnProductsButtonDisabled(page: Page): Promise { + return this.elementVisible(page, `${this.returnProductsButton}[disabled]`, 2000); + } + + /** + * Click on return product button + * @param page {Page} Browser tab + * @returns {Promise} + */ + async clickOnReturnProductsButton(page: Page): Promise { + await page.locator(this.returnProductsButton).click(); + } +} + +const viewOrderBasePage = new ViewOrderBasePage(); +export {viewOrderBasePage, ViewOrderBasePage};