diff --git a/package.json b/package.json index 2c6d5fd54..a0b934ef7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@prestashop-core/ui-testing", - "version": "0.0.12", + "version": "0.0.13", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/interfaces/BO/catalog/products/create/tabCombinations.ts b/src/interfaces/BO/catalog/products/create/tabCombinations.ts index be11c2c37..9351cb6dd 100644 --- a/src/interfaces/BO/catalog/products/create/tabCombinations.ts +++ b/src/interfaces/BO/catalog/products/create/tabCombinations.ts @@ -34,6 +34,9 @@ export interface BOProductsCreateTabCombinationsPageInterface extends BOBasePage clickOnPreviousCombinationButton(page: Page): Promise; closeEditCombinationModal(page: Page): Promise; countCombinations(page: Page): Promise; + getCombinationVirtualProductFileName(page: Page): Promise; + setCombinationIsVirtual(page: Page, isVirtual: boolean): Promise; + setCombinationVirtualProductFile(page: Page, filePath: string): Promise; editCombination(page: Page, combinationData: ProductCombinationOptions, row?: number): Promise; editCombinationFromModal(page: Page, combinationData: ProductCombinationOptions): Promise; editCombinationRowQuantity(page: Page, row: number, quantity: number): Promise; diff --git a/src/interfaces/FO/product/index.ts b/src/interfaces/FO/product/index.ts index b7b38402c..01d194874 100644 --- a/src/interfaces/FO/product/index.ts +++ b/src/interfaces/FO/product/index.ts @@ -92,6 +92,7 @@ export interface FoProductPageInterface extends FOBasePagePageInterface { isQuantityDisplayed(page: Page): Promise; isUnavailableProductColorDisplayed(page: Page, color: string): Promise; isUnavailableProductSizeDisplayed(page: Page, size: string): Promise; + getProductDownloadFileName(page: Page): Promise; notifyEmailAlert(page: Page, email?: string | null): Promise; scrollBoxArrowsImages(page: Page, direction: string): Promise; selectAttributes(page: Page, type: string, attributes: ProductAttribute[], itemNumber?: number): Promise; diff --git a/src/versions/develop/pages/BO/catalog/products/create/tabCombinations.ts b/src/versions/develop/pages/BO/catalog/products/create/tabCombinations.ts index 877592805..60d11989b 100644 --- a/src/versions/develop/pages/BO/catalog/products/create/tabCombinations.ts +++ b/src/versions/develop/pages/BO/catalog/products/create/tabCombinations.ts @@ -176,6 +176,14 @@ class CombinationsTab extends BOBasePage implements BOProductsCreateTabCombinati private readonly editCombinationModalDiscardButton: string; + private readonly editCombinationModalIsVirtualSwitch: (toEnable: number) => string; + + private readonly editCombinationModalVirtualProductFileHasFile: (toCheck: number) => string; + + private readonly editCombinationModalVirtualProductFileInput: string; + + private readonly editCombinationModalVirtualProductFileNameInput: string; + private readonly combinationStockMovementsDate: (row: number) => string; private readonly combinationStockMovementsEmployeeName: (row: number) => string; @@ -332,6 +340,11 @@ class CombinationsTab extends BOBasePage implements BOProductsCreateTabCombinati this.editCombinationModalCloseButton = `${this.editCombinationModal} footer button.btn-close`; this.editCombinationCloseModal = `${this.editCombinationEditModal} div.modal-prevent-close div.modal.show`; this.editCombinationModalDiscardButton = `${this.editCombinationCloseModal} button.btn-primary`; + this.editCombinationModalIsVirtualSwitch = (toEnable: number) => `#combination_form_is_virtual_${toEnable} +i`; + this.editCombinationModalVirtualProductFileHasFile = (toCheck: number) => '#combination_form_virtual_product_file_has_file' + + `_${toCheck}`; + this.editCombinationModalVirtualProductFileInput = '#combination_form_virtual_product_file_file'; + this.editCombinationModalVirtualProductFileNameInput = '#combination_form_virtual_product_file_name'; this.combinationStockMovementsDate = (row: number) => `#combination_form_stock_quantities_stock_movements_${row - 1}_` + 'date + span'; this.combinationStockMovementsEmployeeName = (row: number) => '#combination_form_stock_quantities_stock_movements_' @@ -705,6 +718,61 @@ class CombinationsTab extends BOBasePage implements BOProductsCreateTabCombinati return this.elementVisible(combinationFrame!, this.editCombinationModalQuantityInput, 2000); } + /** + * Toggle the "Is virtual" switch inside the edit combination modal and save. + * @param page {Page} Browser tab + * @param isVirtual {boolean} Target value for the switch + * @returns {Promise} Success message from the growl banner + */ + async setCombinationIsVirtual(page: Page, isVirtual: boolean): Promise { + const combinationFrame: Frame|null = page.frame({url: /sell\/catalog\/products\/combinations/gmi}); + + await this.setChecked(combinationFrame!, this.editCombinationModalIsVirtualSwitch(isVirtual ? 1 : 0)); + await this.waitForSelectorAndClick(page, this.editCombinationModalSaveButton); + + return this.getAlertSuccessBlockParagraphContent(combinationFrame!); + } + + /** + * Upload a downloadable file for the current combination and save. + * The file is attached to combination's own virtual_product_file, independently + * from the product-level virtual product file. + * @param page {Page} Browser tab + * @param filePath {string} Path of the file to upload + * @returns {Promise} Success message from the growl banner + */ + async setCombinationVirtualProductFile(page: Page, filePath: string): Promise { + const combinationFrame: Frame|null = page.frame({url: /sell\/catalog\/products\/combinations/gmi}); + + await this.setChecked(combinationFrame!, this.editCombinationModalVirtualProductFileHasFile(1)); + await this.waitForVisibleSelector(combinationFrame!, this.editCombinationModalVirtualProductFileInput); + await this.uploadFile(combinationFrame!, this.editCombinationModalVirtualProductFileInput, filePath); + await this.waitForSelectorAndClick(page, this.editCombinationModalSaveButton); + + return this.getAlertSuccessBlockParagraphContent(combinationFrame!); + } + + /** + * Read the current downloadable filename attached to the combination. + * @param page {Page} Browser tab + * @returns {Promise} File name, or empty string when no file is attached + */ + async getCombinationVirtualProductFileName(page: Page): Promise { + const combinationFrame: Frame|null = page.frame({url: /sell\/catalog\/products\/combinations/gmi}); + + if (!combinationFrame) { + return ''; + } + + const fileNameSelector = this.editCombinationModalVirtualProductFileNameInput; + + if (!(await this.elementVisible(combinationFrame, fileNameSelector, 2000))) { + return ''; + } + + return this.getAttributeContent(combinationFrame, this.editCombinationModalVirtualProductFileNameInput, 'value'); + } + // Methods for sort /** * Sort table by clicking on column name diff --git a/src/versions/develop/pages/FO/classic/product/index.ts b/src/versions/develop/pages/FO/classic/product/index.ts index d30d75f2d..d327d763a 100644 --- a/src/versions/develop/pages/FO/classic/product/index.ts +++ b/src/versions/develop/pages/FO/classic/product/index.ts @@ -128,6 +128,8 @@ class ProductPage extends FOBasePage implements FoProductPageInterface { protected productInformationBlock: string; + protected productDownloadFileName: string; + protected productMailAlertsBlock: string; protected productMailAlertsEmailInput: string; @@ -287,6 +289,9 @@ class ProductPage extends FOBasePage implements FoProductPageInterface { // Product information block this.productInformationBlock = 'div.product-information'; + // Downloadable file offered on the product page for virtual products (product-level or per-combination). + // Falls back gracefully when the theme does not render the block. + this.productDownloadFileName = 'div.product-download-file, .product__additional-info .product-download__filename'; this.productMailAlertsBlock = `${this.productInformationBlock} div.js-mailalert`; this.productMailAlertsEmailInput = `${this.productMailAlertsBlock} input[type="email"]`; this.productMailAlertsGDPRLabel = `${this.productMailAlertsBlock} div.gdpr_consent label.psgdpr_consent_message ` @@ -1347,6 +1352,22 @@ class ProductPage extends FOBasePage implements FoProductPageInterface { async getNotificationMessage(page: Page): Promise { return this.getTextContent(page, this.notificationsContainer); } + + /** + * Read the downloadable filename displayed for the currently selected + * combination (or the product itself when there is no combination). + * Returns an empty string when the theme does not render a download block — + * used to assert "no download offered" for physical combinations. + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getProductDownloadFileName(page: Page): Promise { + if (!(await this.elementVisible(page, this.productDownloadFileName, 1000))) { + return ''; + } + + return (await this.getTextContent(page, this.productDownloadFileName)).trim(); + } } const productPage = new ProductPage();