From 0342dba1616817fa45f260f20d61f0ec6be44f73 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Thu, 18 Apr 2024 10:34:46 +0200 Subject: [PATCH 01/10] Add new test to change quantity in product page classic theme --- .../02_productPage/02_changeQuantity.ts | 141 ++++++++++++++++++ .../02_productPage/02_changeQuantity.ts | 0 2 files changed, 141 insertions(+) create mode 100644 tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/02_changeQuantity.ts create mode 100644 tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts diff --git a/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/02_changeQuantity.ts b/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/02_changeQuantity.ts new file mode 100644 index 0000000000000..cfc27142ac2b3 --- /dev/null +++ b/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/02_changeQuantity.ts @@ -0,0 +1,141 @@ +// Import utils +import helper from '@utils/helpers'; +import testContext from '@utils/testContext'; + +// Import pages +import {homePage} from '@pages/FO/classic/home'; +import {productPage} from '@pages/FO/classic/product'; +import {blockCartModal} from '@pages/FO/classic/modal/blockCart'; +import {cartPage} from '@pages/FO/classic/cart'; + +// Import data +import Products from '@data/demo/products'; + +import {expect} from 'chai'; +import type {BrowserContext, Page} from 'playwright'; + +const baseContext: string = 'functional_FO_classic_productPage_productPage_changeQuantity'; + +/* +Scenario: +- Go to FO +- Go to the third product in the list +- Click up/down on quantity input +- Set quantity input (good/bad value) + */ +describe('FO - Product page : Change quantity', async () => { + let browserContext: BrowserContext; + let page: Page; + + // before and after functions + before(async function () { + browserContext = await helper.createBrowserContext(this.browser); + page = await helper.newTab(browserContext); + }); + + after(async () => { + await helper.closeBrowserContext(browserContext); + }); + + it('should go to FO home page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToFo', baseContext); + + await homePage.goToFo(page); + + const isHomePage = await homePage.isHomePage(page); + expect(isHomePage).to.equal(true); + }); + + it('should go to the third product page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToProductPage', baseContext); + + await homePage.goToProductPage(page, 3); + + const pageTitle = await productPage.getPageTitle(page); + expect(pageTitle.toUpperCase()).to.contains(Products.demo_6.name.toUpperCase()); + }); + + it('should change the quantity by using the arrow \'UP\' button', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'incrementQuantity', baseContext); + + await productPage.setQuantityByArrowUpDown(page, 5, 'up'); + + const productQuantity = await productPage.getProductQuantity(page); + expect(productQuantity).to.equal(5); + }); + + it('should change the quantity by using the arrow \'Down\' button', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'incrementQuantity2', baseContext); + + await productPage.setQuantityByArrowUpDown(page, 1, 'down'); + + const productQuantity = await productPage.getProductQuantity(page); + expect(productQuantity).to.equal(1); + }); + + it('should add quantity of the product by setting input value', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput', baseContext); + + await productPage.setQuantity(page, 12); + await productPage.clickOnAddToCartButton(page); + + const isVisible = await blockCartModal.isBlockCartModalVisible(page); + expect(isVisible).to.equal(true); + }); + + it('should click on continue shopping and check that the modal is not visible', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnContinueShopping', baseContext); + + const isNotVisible = await blockCartModal.continueShopping(page); + expect(isNotVisible).to.equal(true); + }); + + it('should check the cart notifications number', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkNotificationsNumber', baseContext); + + const notificationsNumber = await productPage.getCartNotificationsNumber(page); + expect(notificationsNumber).to.equal(12); + }); + + it('should set \'-24\' in the quantity input', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput2', baseContext); + + await productPage.setQuantity(page, '-24'); + await productPage.clickOnAddToCartButton(page); + + const isVisible = await blockCartModal.isBlockCartModalVisible(page); + expect(isVisible).to.equal(true); + }); + + it('should click on continue shopping and check that the modal is not visible', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnContinueShopping2', baseContext); + + const isNotVisible = await blockCartModal.continueShopping(page); + expect(isNotVisible).to.equal(true); + }); + + it('should check the cart notifications number', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkNotificationsNumber2', baseContext); + + const notificationsNumber = await homePage.getCartNotificationsNumber(page); + expect(notificationsNumber).to.equal(13); + }); + + it('should set \'Prestashop\' in the quantity input and proceed to checkout', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput3', baseContext); + + await productPage.addProductToTheCart(page, 'Prestashop'); + + const notificationsNumber = await homePage.getCartNotificationsNumber(page); + expect(notificationsNumber).to.equal(14); + }); + + it('should remove product from shopping cart', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'removeProduct', baseContext); + + await cartPage.deleteProduct(page, 1); + + const notificationNumber = await cartPage.getCartNotificationsNumber(page); + expect(notificationNumber).to.equal(0); + }); +}); diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts new file mode 100644 index 0000000000000..e69de29bb2d1d From 9ebd55f884077cd87629525b08b0505a1fd48854 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Thu, 18 Apr 2024 10:35:05 +0200 Subject: [PATCH 02/10] Add new test to change quantity in product page hummingbird theme --- .../02_productPage/02_changeQuantity.ts | 169 ++++++++++++++++++ tests/UI/pages/FO/classic/product/index.ts | 56 +++++- .../UI/pages/FO/hummingbird/product/index.ts | 2 + 3 files changed, 218 insertions(+), 9 deletions(-) diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts index e69de29bb2d1d..0e164c6e18f6c 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts @@ -0,0 +1,169 @@ +// Import utils +import helper from '@utils/helpers'; +import testContext from '@utils/testContext'; + +// Import common tests +import {installHummingbird, uninstallHummingbird} from '@commonTests/BO/design/hummingbird'; + +// Import pages +import homePage from '@pages/FO/hummingbird/home'; +import productPage from '@pages/FO/hummingbird/product'; +import blockCartModal from '@pages/FO/hummingbird/modal/blockCart'; +import cartPage from '@pages/FO/hummingbird/cart'; + +// Import data +import Products from '@data/demo/products'; + +import {expect} from 'chai'; +import type {BrowserContext, Page} from 'playwright'; + +const baseContext: string = 'functional_FO_hummingbird_productPage_productPage_changeQuantity'; + +/* +Pre-condition: +- Install hummingbird theme +Scenario: +- Go to FO +- Go to the third product in the list +- Click up/down on quantity input +- Set quantity input (good/bad value) +Post-condition: +- Uninstall hummingbird theme + */ +describe('FO - Product page : Change quantity', async () => { + let browserContext: BrowserContext; + let page: Page; + + // Pre-condition : Install Hummingbird + installHummingbird(`${baseContext}_preTest`); + + // before and after functions + before(async function () { + browserContext = await helper.createBrowserContext(this.browser); + page = await helper.newTab(browserContext); + }); + + after(async () => { + await helper.closeBrowserContext(browserContext); + }); + + describe('Change quantity from product page', async () => { + it('should go to FO home page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToFo', baseContext); + + await homePage.goToFo(page); + + const isHomePage = await homePage.isHomePage(page); + expect(isHomePage).to.equal(true); + }); + + it('should go to the third product page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToProductPage', baseContext); + + await homePage.goToProductPage(page, 3); + + const pageTitle = await productPage.getPageTitle(page); + expect(pageTitle.toUpperCase()).to.contains(Products.demo_6.name.toUpperCase()); + }); + + it('should change the quantity by using the arrow \'Down\' button', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'decrement', baseContext); + + await productPage.setQuantityByArrowUpDown(page, 1, 'down'); + + const productQuantity = await productPage.getProductQuantity(page); + expect(productQuantity).to.equal(1); + }); + + it('should change the quantity by using the arrow \'UP\' button', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'incrementQuantity', baseContext); + + await productPage.setQuantityByArrowUpDown(page, 2, 'increment'); + + const productQuantity = await productPage.getProductQuantity(page); + expect(productQuantity).to.equal(2); + }); + + it('should click on add to cart button then on continue shopping button', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnAddToCartButton', baseContext); + + await productPage.clickOnAddToCartButton(page); + + const isNotVisible = await blockCartModal.continueShopping(page); + expect(isNotVisible).to.equal(true); + }); + + // @todo : https://github.com/PrestaShop/hummingbird/pull/600 + it.skip('should set the quantity 0 and check that the add to cart button is disabled', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkAddToCartButtonIsDisabled', baseContext); + + await productPage.setQuantity(page, 0); + + const isButtonDisabled = await productPage.isAddToCartButtonEnabled(page); + expect(isButtonDisabled).to.equal(false); + }); + + it('should add quantity of the product by setting input value', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput', baseContext); + + // @todo : https://github.com/PrestaShop/hummingbird/issues/615 + await productPage.reloadPage(page); + + await productPage.setQuantity(page, 12); + await productPage.clickOnAddToCartButton(page); + + const isVisible = await blockCartModal.isBlockCartModalVisible(page); + expect(isVisible).to.equal(true); + }); + + it('should click on continue shopping and check that the modal is not visible', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnContinueShopping', baseContext); + + const isNotVisible = await blockCartModal.continueShopping(page); + expect(isNotVisible).to.equal(true); + }); + + it('should check the cart notifications number', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkNotificationsNumber', baseContext); + + const notificationsNumber = await productPage.getCartNotificationsNumber(page); + expect(notificationsNumber).to.equal(14); + }); + + // @todo : https://github.com/PrestaShop/hummingbird/pull/600 + it.skip('should set \'-24\' in the quantity input', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput2', baseContext); + + await productPage.setQuantity(page, '-24'); + + const isButtonDisabled = await productPage.isAddToCartButtonEnabled(page); + expect(isButtonDisabled).to.equal(false); + }); + + it('should set \'Prestashop\' in the quantity input and proceed to checkout', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput3', baseContext); + + // @todo : https://github.com/PrestaShop/hummingbird/issues/615 + await productPage.reloadPage(page); + + await productPage.setQuantity(page, 'Prestashop'); + await productPage.clickOnAddToCartButton(page); + + const errorAlert = await productPage.getWarningMessage(page); + expect(errorAlert).to.equal('Null quantity.'); + }); + + it('should remove product from shopping cart', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'removeProduct', baseContext); + + await productPage.goToCartPage(page); + await cartPage.deleteProduct(page, 1); + + const notificationNumber = await cartPage.getCartNotificationsNumber(page); + expect(notificationNumber).to.equal(0); + }); + }); + + // Post-condition : Uninstall Hummingbird + uninstallHummingbird(`${baseContext}_postTest`); +}); diff --git a/tests/UI/pages/FO/classic/product/index.ts b/tests/UI/pages/FO/classic/product/index.ts index 2abe664f5d728..c45db6da935d7 100644 --- a/tests/UI/pages/FO/classic/product/index.ts +++ b/tests/UI/pages/FO/classic/product/index.ts @@ -21,7 +21,7 @@ class Product extends FOBasePage { public readonly messageAlertNotificationAlreadyRegistered: string; - private readonly warningMessage: string; + protected warningMessage: string; protected productFlags: string; @@ -37,6 +37,8 @@ class Product extends FOBasePage { private readonly productQuantity: string; + protected productRowQuantityUpDownButton: (direction: string) => string; + protected shortDescription: string; private readonly productDescription: string; @@ -193,6 +195,8 @@ class Product extends FOBasePage { this.thumbFirstImg = '#content li:nth-child(1) img.js-thumb'; this.thumbSecondImg = '#content li:nth-child(2) img.js-thumb'; this.productQuantity = '#quantity_wanted'; + this.productRowQuantityUpDownButton = (direction: string) => 'span.input-group-btn-vertical' + + ` button.bootstrap-touchspin-${direction}`; this.shortDescription = '#product-description-short'; this.productDescription = '#description'; this.customizationBlock = 'div.product-container div.product-information section.product-customization'; @@ -586,11 +590,11 @@ class Product extends FOBasePage { /** * Select product attributes * @param page {Page} Browser tab - * @param quantity {number} Quantity of the product that customer wants + * @param quantity {number|string} Quantity of the product that customer wants * @param attributes {ProductAttribute[]} Product's attributes data to select * @returns {Promise} */ - async selectAttributes(page: Page, quantity: number, attributes: ProductAttribute[]): Promise { + async selectAttributes(page: Page, quantity: number|string, attributes: ProductAttribute[]): Promise { if (attributes.length === 0) { return; } @@ -640,7 +644,7 @@ class Product extends FOBasePage { /** * Click on Add to cart button then on Proceed to checkout button in the modal * @param page {Page} Browser tab - * @param quantity {number} Quantity of the product that customer wants + * @param quantity {number|string} Quantity of the product that customer wants * @param combination {ProductAttribute[]} Product's combination data to add to cart * @param proceedToCheckout {boolean|null} True to click on proceed to checkout button on modal * @param customizedText {string} Value of customization @@ -648,7 +652,7 @@ class Product extends FOBasePage { */ async addProductToTheCart( page: Page, - quantity: number = 1, + quantity: number |string = 1, combination: ProductAttribute[] = [], proceedToCheckout: boolean | null = true, customizedText: string = 'text', @@ -738,11 +742,45 @@ class Product extends FOBasePage { /** * Set quantity * @param page {Page} Browser tab - * @param quantity {number} Quantity to set + * @param quantity {number|string} Quantity to set * @returns {Promise} */ - async setQuantity(page: Page, quantity: number): Promise { - await this.setValue(page, this.productQuantity, quantity.toString()); + async setQuantity(page: Page, quantity: number | string): Promise { + await this.setValue(page, this.productQuantity, quantity); + } + + /** + * Click on add to cart button + * @param page {Page} Browser tab + * @returns {Promise} + */ + async clickOnAddToCartButton(page: Page): Promise { + await this.waitForSelectorAndClick(page, this.addToCartButton); + } + + /** + * Get product quantity + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getProductQuantity(page: Page): Promise { + return parseInt(await page.locator(this.productQuantity).evaluate((node: HTMLSelectElement) => node.value), 10); + } + + /** + * Update quantity value arrow up down in quick view modal + * @param page {Page} Browser tab + * @param quantityWanted {number} Value to add/subtract from quantity + * @param direction {string} Direction to click on + * @returns {Promise} + */ + async setQuantityByArrowUpDown(page: Page, quantityWanted: number, direction: string): Promise { + const inputValue = await this.getProductQuantity(page); + const nbClick: number = Math.abs(inputValue - quantityWanted); + + for (let i = 0; i < nbClick; i++) { + await page.locator(this.productRowQuantityUpDownButton(direction)).click(); + } } /** @@ -851,7 +889,7 @@ class Product extends FOBasePage { * @returns {Promise} */ async isAddToCartButtonEnabled(page: Page): Promise { - return this.elementNotVisible(page, `${this.addToCartButton}:disabled`, 1000); + return this.elementNotVisible(page, `${this.addToCartButton}:disabled`, 3000); } /** diff --git a/tests/UI/pages/FO/hummingbird/product/index.ts b/tests/UI/pages/FO/hummingbird/product/index.ts index 2925f5268ca01..a03a79ba67709 100644 --- a/tests/UI/pages/FO/hummingbird/product/index.ts +++ b/tests/UI/pages/FO/hummingbird/product/index.ts @@ -13,6 +13,8 @@ class ProductPage extends Product { constructor() { super('hummingbird'); + this.warningMessage = '#js-toast-container div.bg-danger div.toast-body'; + this.productRowQuantityUpDownButton = (direction: string) => `div.product-actions__quantity button.js-${direction}-button`; this.proceedToCheckoutButton = '#blockcart-modal div.cart-footer-actions a'; this.productName = '#content-wrapper h1.product__name'; this.shortDescription = 'div.product__description-short'; From 8c41535bbae4be781525afaab0503d1a965b51ba Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Thu, 18 Apr 2024 10:35:18 +0200 Subject: [PATCH 03/10] Add some fixes --- .../hummingbird/09_productPage/02_productPage/01_addToCart.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/01_addToCart.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/01_addToCart.ts index 57483991612a6..00beaab48ebf6 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/01_addToCart.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/01_addToCart.ts @@ -1,7 +1,6 @@ // Import utils import helper from '@utils/helpers'; import testContext from '@utils/testContext'; -import files from '@utils/files'; // Import common tests import {installHummingbird, uninstallHummingbird} from '@commonTests/BO/design/hummingbird'; @@ -41,7 +40,6 @@ describe('FO - Product page - Product page : Add to cart', async () => { after(async () => { await helper.closeBrowserContext(browserContext); - await files.deleteFile('../../admin-dev/hummingbird.zip'); }); describe('Add to cart', async () => { From d096aa052e3f3c64216fe1ad179de41d892edf53 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Fri, 19 Apr 2024 15:37:24 +0200 Subject: [PATCH 04/10] Add new test to change image in product page classic theme --- .../02_productPage/03_changeImage.ts | 207 ++++++++++++++++++ tests/UI/pages/FO/classic/product/index.ts | 99 +++++++-- 2 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts diff --git a/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts b/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts new file mode 100644 index 0000000000000..5b7ed39947bf7 --- /dev/null +++ b/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts @@ -0,0 +1,207 @@ +// Import utils +import helper from '@utils/helpers'; +import testContext from '@utils/testContext'; +import files from '@utils/files'; + +// Import common tests +import {deleteProductTest} from '@commonTests/BO/catalog/product'; + +// Import BO pages +import loginCommon from '@commonTests/BO/loginBO'; +import dashboardPage from '@pages/BO/dashboard'; +import productsPage from '@pages/BO/catalog/products'; +import createProductsPage from '@pages/BO/catalog/products/add'; +import descriptionTab from '@pages/BO/catalog/products/add/descriptionTab'; + +// Import FO pages +import {homePage} from '@pages/FO/classic/home'; +import {productPage} from '@pages/FO/classic/product'; +import {searchResultsPage} from '@pages/FO/classic/searchResults'; + +// Import data +import ProductData from '@data/faker/product'; + +import {expect} from 'chai'; +import type {BrowserContext, Page} from 'playwright'; + +const baseContext: string = 'functional_FO_classic_productPage_quickView_changeImage'; + +/* +Pre-condition: +- Create product with 4 images +Scenario: +- Go to FO +- Go to the created product page +- Change image +- Scroll from images list ans select image +- Zoom the cover image and change image +Post-condition: +- Delete created product + */ +describe('FO - Product page - Quick view : Change image', async () => { + let browserContext: BrowserContext; + let page: Page; + + // Data to create product + const newProductData: ProductData = new ProductData({ + type: 'standard', + quantity: 2, + coverImage: 'coverImage.jpg', + thumbImage: 'thumbImage.jpg', + }); + + // before and after functions + before(async function () { + browserContext = await helper.createBrowserContext(this.browser); + page = await helper.newTab(browserContext); + await files.generateImage(newProductData.coverImage!); + await files.generateImage(newProductData.thumbImage!); + await files.generateImage('secondThumbImage.jpg'); + await files.generateImage('thirdThumbImage.jpg'); + }); + + after(async () => { + await helper.closeBrowserContext(browserContext); + await files.deleteFile(newProductData.coverImage!); + await files.deleteFile(newProductData.thumbImage!); + await files.deleteFile('secondThumbImage.jpg'); + await files.deleteFile('thirdThumbImage.jpg'); + }); + + describe(`PRE-TEST: Create new product '${newProductData.name}' with 4 images`, async () => { + it('should login in BO', async function () { + await loginCommon.loginBO(this, page); + }); + + it('should go to \'Catalog > Products\' page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToProductsPage', baseContext); + + await dashboardPage.goToSubMenu(page, dashboardPage.catalogParentLink, dashboardPage.productsLink); + await productsPage.closeSfToolBar(page); + + const pageTitle = await productsPage.getPageTitle(page); + expect(pageTitle).to.contains(productsPage.pageTitle); + }); + + it('should click on \'New product\' button and check new product modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnNewProductButton', baseContext); + + const isModalVisible = await productsPage.clickOnNewProductButton(page); + expect(isModalVisible).to.be.eq(true); + }); + + it('should choose \'Standard product\'', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'chooseStandardProduct', baseContext); + + await productsPage.selectProductType(page, newProductData.type); + + const pageTitle = await createProductsPage.getPageTitle(page); + expect(pageTitle).to.contains(createProductsPage.pageTitle); + }); + + it('should go to new product page and set product name and status', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'createStandardProduct', baseContext); + + await productsPage.clickOnAddNewProduct(page); + await createProductsPage.setProductName(page, newProductData.name); + + await createProductsPage.setProductStatus(page, newProductData.status); + + const createProductMessage = await createProductsPage.saveProduct(page); + expect(createProductMessage).to.equal(createProductsPage.successfulUpdateMessage); + }); + + it('should add 4 images', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'addImage', baseContext); + + await descriptionTab.addProductImages(page, + [newProductData.coverImage, newProductData.thumbImage, 'secondThumbImage.jpg', 'thirdThumbImage.jpg']); + + const numOfImages = await descriptionTab.getNumberOfImages(page); + expect(numOfImages).to.equal(4); + }); + }); + + describe('FO: Change image', async () => { + it('should go to FO home page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToFo', baseContext); + + await homePage.goToFo(page); + + const isHomePage = await homePage.isHomePage(page); + expect(isHomePage).to.equal(true); + }); + + it('should search for the created product', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'searchCreatedProduct', baseContext); + + await homePage.searchProduct(page, newProductData.name); + + const productsNumber = await searchResultsPage.getSearchResultsNumber(page); + expect(productsNumber).to.equal(1); + }); + + it('should go to the created product page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToCreatedProductPage', baseContext); + + await searchResultsPage.goToProductPage(page, 1); + + const pageTitle = await productPage.getPageTitle(page); + expect(pageTitle).to.equal(newProductData.name); + }); + + it('should display the second image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'displaySecondImage', baseContext); + + const firstCoverImageURL = await productPage.getCoverImage(page); + + const secondCoverImageURL = await productPage.selectThumbImage(page, 2); + expect(firstCoverImageURL).to.not.equal(secondCoverImageURL); + }); + + it('should display the first image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'displayFirstImage', baseContext); + + const firstCoverImageURL = await productPage.getCoverImage(page); + + const secondCoverImageURL = await productPage.selectThumbImage(page, 1); + expect(firstCoverImageURL).to.not.equal(secondCoverImageURL); + }); + + it('should click on the arrow right and click on the 4th product', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'display4ThImage', baseContext); + + const coverImageURL = await productPage.getCoverImage(page); + await productPage.scrollBoxArrowsImages(page, 'right'); + + const fourthCoverImageURL = await productPage.selectThumbImage(page, 4); + expect(coverImageURL).to.not.equal(fourthCoverImageURL); + }); + + it('should zoom the cover image and check the modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'zoomImage', baseContext); + + const isModalVisible = await productPage.zoomCoverImage(page); + expect(isModalVisible).to.equal(true); + }); + + it('should click on the third little image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnSecondLittleImage', baseContext); + + const coverImageURL = await productPage.getCoverImageFromProductModal(page); + + const thirdCoverImageURL = await productPage.selectThumbImageFromProductModal(page, 3); + expect(coverImageURL).to.not.equal(thirdCoverImageURL); + }); + + it('should close the product modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'closeModal', baseContext); + + const isModalNotVisible = await productPage.closeProductModal(page); + expect(isModalNotVisible).to.equal(true); + }); + }); + + // Post-condition : Delete created product + deleteProductTest(newProductData, `${baseContext}_postTest`); +}); diff --git a/tests/UI/pages/FO/classic/product/index.ts b/tests/UI/pages/FO/classic/product/index.ts index 2abe664f5d728..dd6db2979a673 100644 --- a/tests/UI/pages/FO/classic/product/index.ts +++ b/tests/UI/pages/FO/classic/product/index.ts @@ -31,9 +31,17 @@ class Product extends FOBasePage { private readonly productCoverImg: string; - private readonly thumbFirstImg: string; + private readonly thumbImg: (row: number) => string; - private readonly thumbSecondImg: string; + private readonly thumbImgProductModal: (row: number) => string; + + private readonly scrollBoxImages: (direction: string) => string; + + private readonly zoomIcon: string; + + private readonly productModal: string; + + private readonly productCoverImgProductModal: string; private readonly productQuantity: string; @@ -190,8 +198,12 @@ class Product extends FOBasePage { this.productFlag = (flag: string) => `#content li.product-flag${flag.length === 0 ? '' : `.${flag}`}`; this.productName = '#main h1'; this.productCoverImg = '#content .product-cover img'; - this.thumbFirstImg = '#content li:nth-child(1) img.js-thumb'; - this.thumbSecondImg = '#content li:nth-child(2) img.js-thumb'; + this.thumbImg = (row: number) => `#content li:nth-child(${row}) img.js-thumb`; + this.scrollBoxImages = (direction: string) => `#content div.scroll-box-arrows.scroll i.material-icons.${direction}`; + this.zoomIcon = 'div.images-container div.product-cover i.zoom-in'; + this.productModal = '#product-modal'; + this.productCoverImgProductModal = '#product-modal picture img.js-modal-product-cover'; + this.thumbImgProductModal = (row: number) => `#thumbnails li:nth-child(${row}) picture img.js-modal-thumb`; this.productQuantity = '#quantity_wanted'; this.shortDescription = '#product-description-short'; this.productDescription = '#description'; @@ -482,7 +494,7 @@ class Product extends FOBasePage { async getProductImageUrls(page: Page): Promise { return { coverImage: await this.getAttributeContent(page, this.productCoverImg, 'src'), - thumbImage: await this.getAttributeContent(page, this.thumbFirstImg, 'src'), + thumbImage: await this.getAttributeContent(page, this.thumbImg(1), 'src'), }; } @@ -566,23 +578,82 @@ class Product extends FOBasePage { return this.elementVisible(page, this.deliveryInformationSpan); } + /** + * get the URL of the cover image + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getCoverImage(page: Page): Promise { + return this.getAttributeContent(page, this.productCoverImg, 'src'); + } + /** * Select thumb image * @param page {Page} Browser tab - * @param id {number} Id for the thumb + * @param imageRow {number} Row of the image * @returns {Promise} */ - async selectThumbImage(page: Page, id: number): Promise { - if (id === 1) { - await this.waitForSelectorAndClick(page, this.thumbFirstImg); - await this.waitForVisibleSelector(page, `${this.thumbFirstImg}.selected`); - } else { - await this.waitForSelectorAndClick(page, this.thumbSecondImg); - await this.waitForVisibleSelector(page, `${this.thumbSecondImg}.selected`); - } + async selectThumbImage(page: Page, imageRow: number): Promise { + await this.waitForSelectorAndClick(page, this.thumbImg(imageRow)); + await this.waitForVisibleSelector(page, `${this.thumbImg(imageRow)}.selected`); + return this.getAttributeContent(page, this.productCoverImg, 'src'); } + /** + * Scroll box arrows images + * @param page {Page} Browser tab + * @param direction {string} Direction to scroll + * @returns {Promise} + */ + async scrollBoxArrowsImages(page: Page, direction: string): Promise { + await page.locator(this.scrollBoxImages(direction)).click(); + } + + /** + * Zoom cover image + * @param page {Page} Browser tab + * @returns {Promise} + */ + async zoomCoverImage(page: Page): Promise { + await page.locator(this.zoomIcon).click({force: true}); + + return this.elementVisible(page, this.productModal, 1000); + } + + /** + * Select thumb image + * @param page {Page} Browser tab + * @param imageRow {number} Row of the image + * @returns {Promise} + */ + async selectThumbImageFromProductModal(page: Page, imageRow: number): Promise { + await this.waitForSelectorAndClick(page, this.thumbImgProductModal(imageRow)); + await this.waitForVisibleSelector(page, `${this.thumbImgProductModal(imageRow)}.selected`); + + return this.getAttributeContent(page, this.productCoverImgProductModal, 'src'); + } + + /** + * get the URL of the cover image + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getCoverImageFromProductModal(page: Page): Promise { + return this.getAttributeContent(page, this.productCoverImg, 'src'); + } + + /** + * Close product modal + * @param page {Page} Browser tab + * @returns {Promise} + */ + async closeProductModal(page: Page): Promise { + await page.mouse.click(5, 5); + + return this.elementNotVisible(page, '#product-modal', 2000); + } + /** * Select product attributes * @param page {Page} Browser tab From 468a0a6858d22056f7ad93b514ff713ae4f17331 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Mon, 22 Apr 2024 16:31:43 +0200 Subject: [PATCH 05/10] Add new test to change image in product page hummingbird theme --- .../02_productPage/03_changeImage.ts | 226 ++++++++++++++++++ tests/UI/pages/FO/classic/product/index.ts | 11 +- .../UI/pages/FO/hummingbird/product/index.ts | 47 ++++ 3 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts new file mode 100644 index 0000000000000..dde1648123b32 --- /dev/null +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts @@ -0,0 +1,226 @@ +// Import utils +import helper from '@utils/helpers'; +import testContext from '@utils/testContext'; +import files from '@utils/files'; + +// Import common tests +import {deleteProductTest} from '@commonTests/BO/catalog/product'; +import {installHummingbird, uninstallHummingbird} from '@commonTests/BO/design/hummingbird'; + +// Import BO pages +import loginCommon from '@commonTests/BO/loginBO'; +import dashboardPage from '@pages/BO/dashboard'; +import productsPage from '@pages/BO/catalog/products'; +import createProductsPage from '@pages/BO/catalog/products/add'; +import descriptionTab from '@pages/BO/catalog/products/add/descriptionTab'; + +// Import FO pages +import homePage from '@pages/FO/hummingbird/home'; +import productPage from '@pages/FO/hummingbird/product'; +import searchResultsPage from '@pages/FO/hummingbird/searchResults'; + +// Import data +import ProductData from '@data/faker/product'; + +import {expect} from 'chai'; +import type {BrowserContext, Page} from 'playwright'; + +const baseContext: string = 'functional_FO_hummingbird_productPage_quickView_changeImage'; + +/* +Pre-condition: +- Install the theme hummingbird +- Create product with 4 images +Scenario: +- Go to FO +- Go to the created product page +- Change image +- Scroll from images list ans select image +- Zoom the cover image and change image +Post-condition: +- Delete created product +- Uninstall the theme hummingbird + */ +describe('FO - Product page - Quick view : Change image', async () => { + let browserContext: BrowserContext; + let page: Page; + + // Data to create product + const newProductData: ProductData = new ProductData({ + type: 'standard', + quantity: 2, + coverImage: 'coverImage.jpg', + thumbImage: 'thumbImage.jpg', + }); + + // Pre-condition : Install Hummingbird + installHummingbird(`${baseContext}_preTest`); + + // before and after functions + before(async function () { + browserContext = await helper.createBrowserContext(this.browser); + page = await helper.newTab(browserContext); + await files.generateImage(newProductData.coverImage!); + await files.generateImage(newProductData.thumbImage!); + await files.generateImage('secondThumbImage.jpg'); + await files.generateImage('thirdThumbImage.jpg'); + }); + + after(async () => { + await helper.closeBrowserContext(browserContext); + await files.deleteFile(newProductData.coverImage!); + await files.deleteFile(newProductData.thumbImage!); + await files.deleteFile('secondThumbImage.jpg'); + await files.deleteFile('thirdThumbImage.jpg'); + }); + + describe(`PRE-TEST: Create new product '${newProductData.name}' with 4 images`, async () => { + it('should login in BO', async function () { + await loginCommon.loginBO(this, page); + }); + + it('should go to \'Catalog > Products\' page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToProductsPage', baseContext); + + await dashboardPage.goToSubMenu(page, dashboardPage.catalogParentLink, dashboardPage.productsLink); + await productsPage.closeSfToolBar(page); + + const pageTitle = await productsPage.getPageTitle(page); + expect(pageTitle).to.contains(productsPage.pageTitle); + }); + + it('should click on \'New product\' button and check new product modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnNewProductButton', baseContext); + + const isModalVisible = await productsPage.clickOnNewProductButton(page); + expect(isModalVisible).to.be.eq(true); + }); + + it('should choose \'Standard product\'', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'chooseStandardProduct', baseContext); + + await productsPage.selectProductType(page, newProductData.type); + + const pageTitle = await createProductsPage.getPageTitle(page); + expect(pageTitle).to.contains(createProductsPage.pageTitle); + }); + + it('should go to new product page and set product name and status', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'createStandardProduct', baseContext); + + await productsPage.clickOnAddNewProduct(page); + await createProductsPage.setProductName(page, newProductData.name); + + await createProductsPage.setProductStatus(page, newProductData.status); + + const createProductMessage = await createProductsPage.saveProduct(page); + expect(createProductMessage).to.equal(createProductsPage.successfulUpdateMessage); + }); + + it('should add 4 images', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'addImage', baseContext); + + await descriptionTab.addProductImages(page, + [newProductData.coverImage, newProductData.thumbImage, 'secondThumbImage.jpg', 'thirdThumbImage.jpg']); + + const numOfImages = await descriptionTab.getNumberOfImages(page); + expect(numOfImages).to.equal(4); + }); + }); + + describe('FO: Change image', async () => { + it('should go to FO home page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToFo', baseContext); + + await homePage.goToFo(page); + + const isHomePage = await homePage.isHomePage(page); + expect(isHomePage).to.equal(true); + }); + + it('should search for the created product', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'searchCreatedProduct', baseContext); + + await homePage.searchProduct(page, newProductData.name); + + const productsNumber = await searchResultsPage.getSearchResultsNumber(page); + expect(productsNumber).to.equal(1); + }); + + it('should go to the created product page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToCreatedProductPage', baseContext); + + await searchResultsPage.goToProductPage(page, 1); + + const pageTitle = await productPage.getPageTitle(page); + expect(pageTitle).to.equal(newProductData.name); + }); + + it('should display the third image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'displayThirdImage', baseContext); + + const coverImageURL = await productPage.getCoverImage(page); + + const thirdCoverImageURL = await productPage.selectThumbImage(page, 3); + expect(coverImageURL).to.not.equal(thirdCoverImageURL); + }); + + it('should display the first image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'displayFirstImage', baseContext); + + const coverImagePosition = await productPage.getCoverImage(page); + + const secondCoverImagePosition = await productPage.selectThumbImage(page, 1); + expect(coverImagePosition).to.not.equal(secondCoverImagePosition); + }); + + it('should click on the arrow right and check the cover image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickRight', baseContext); + + const coverImagePosition = await productPage.getCoverImage(page); + await productPage.scrollBoxArrowsImages(page, 'next'); + + const secondCoverImagePosition = await productPage.getCoverImage(page); + expect(coverImagePosition).to.not.equal(secondCoverImagePosition); + }); + + it('should zoom the cover image and check the modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'zoomImage', baseContext); + + const isModalVisible = await productPage.zoomCoverImage(page); + expect(isModalVisible).to.equal(true); + }); + + it('should click on the arrow right in the modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickOnSecondLittleImage', baseContext); + + const coverImagePosition = await productPage.getCoverImageFromProductModal(page); + + const thirdCoverImagePosition = await productPage.clickOnArrowNextPrevInProductModal(page, 'next'); + expect(coverImagePosition).to.not.equal(thirdCoverImagePosition); + }); + + it('should close the product modal', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'closeModal', baseContext); + + const isModalNotVisible = await productPage.closeProductModal(page); + expect(isModalNotVisible).to.equal(true); + }); + + it('should click on the arrow left and check the cover image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickLeft', baseContext); + + const coverImagePosition = await productPage.getCoverImage(page); + await productPage.scrollBoxArrowsImages(page, 'prev'); + + const secondCoverImagePosition = await productPage.getCoverImage(page); + expect(coverImagePosition).to.not.equal(secondCoverImagePosition); + }); + }); + + // Post-condition : Delete created product + deleteProductTest(newProductData, `${baseContext}_postTest`); + + // Post-condition : Uninstall Hummingbird + uninstallHummingbird(`${baseContext}_postTest2`); +}); diff --git a/tests/UI/pages/FO/classic/product/index.ts b/tests/UI/pages/FO/classic/product/index.ts index dd6db2979a673..595279091556c 100644 --- a/tests/UI/pages/FO/classic/product/index.ts +++ b/tests/UI/pages/FO/classic/product/index.ts @@ -29,19 +29,19 @@ class Product extends FOBasePage { protected productName: string; - private readonly productCoverImg: string; + protected productCoverImg: string; - private readonly thumbImg: (row: number) => string; + protected thumbImg: (row: number) => string; private readonly thumbImgProductModal: (row: number) => string; - private readonly scrollBoxImages: (direction: string) => string; + protected scrollBoxImages: (direction: string) => string; - private readonly zoomIcon: string; + protected zoomIcon: string; private readonly productModal: string; - private readonly productCoverImgProductModal: string; + protected productCoverImgProductModal: string; private readonly productQuantity: string; @@ -608,6 +608,7 @@ class Product extends FOBasePage { */ async scrollBoxArrowsImages(page: Page, direction: string): Promise { await page.locator(this.scrollBoxImages(direction)).click(); + await page.waitForTimeout(1000); } /** diff --git a/tests/UI/pages/FO/hummingbird/product/index.ts b/tests/UI/pages/FO/hummingbird/product/index.ts index 2925f5268ca01..d6939c2045ff8 100644 --- a/tests/UI/pages/FO/hummingbird/product/index.ts +++ b/tests/UI/pages/FO/hummingbird/product/index.ts @@ -1,5 +1,6 @@ // Import FO pages import {Product} from '@pages/FO/classic/product'; +import type {Page} from 'playwright'; /** * @class @@ -10,10 +11,21 @@ class ProductPage extends Product { * @constructs * Setting up texts and selectors to use on checkout page */ + private readonly carouselControlProductModal: (direction: string) => string; + + private readonly productImageRow: (row: number) => string; + constructor() { super('hummingbird'); this.proceedToCheckoutButton = '#blockcart-modal div.cart-footer-actions a'; + this.productCoverImg = '#product-images div.carousel-item.active'; + this.scrollBoxImages = (direction: string) => `#product-images button.carousel-control-${direction}`; + this.productCoverImgProductModal = '#product-images-modal div.carousel-item.active picture img'; + this.carouselControlProductModal = (direction: string) => `#product-images-modal button.carousel-control-${direction}`; + this.productImageRow = (row: number) => `#content-wrapper div.thumbnails__container li:nth-child(${row})`; + this.thumbImg = (row: number) => `#content-wrapper div.thumbnails__container li:nth-child(${row}) picture img.js-thumb`; + this.zoomIcon = '#product-images div.carousel-item.active i.zoom-in'; this.productName = '#content-wrapper h1.product__name'; this.shortDescription = 'div.product__description-short'; this.productFlags = '#product-images ul.product-flags'; @@ -25,6 +37,41 @@ class ProductPage extends Product { this.productPricesBlock = 'div.product__prices'; this.productPrice = `${this.productPricesBlock} .product__current-price`; } + + /** + * Get the position in the slide of the cover image + * @param page {Page} Browser tab + * @returns {Promise} + */ + async getCoverImage(page: Page): Promise { + return this.getAttributeContent(page, this.productCoverImg, 'data-bs-slide-to'); + } + + /** + * Select thumb image + * @param page {Page} Browser tab + * @param imageRow {number} Row of the image + * @returns {Promise} + */ + async selectThumbImage(page: Page, imageRow: number): Promise { + await this.waitForSelectorAndClick(page, this.thumbImg(imageRow)); + await this.waitForVisibleSelector(page, `${this.productImageRow(imageRow)}.active`); + await page.waitForTimeout(2000); + + return this.getAttributeContent(page, this.productCoverImg, 'data-bs-slide-to'); + } + + /** + * Click on arrow next/previous in product modal + * @param page {Page} Browser tab + * @param direction {string} Direction Next/Prev + * @returns {Promise} + */ + async clickOnArrowNextPrevInProductModal(page: Page, direction: string): Promise { + await page.locator(this.carouselControlProductModal(direction)).click(); + + return this.getAttributeContent(page, this.productCoverImgProductModal, 'src'); + } } export default new ProductPage(); From 00270d0190b358ad19e148f54496db5d3639b3e0 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Mon, 22 Apr 2024 17:45:59 +0200 Subject: [PATCH 06/10] Fix base context --- .../FO/classic/09_productPage/02_productPage/03_changeImage.ts | 2 +- .../hummingbird/09_productPage/02_productPage/03_changeImage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts b/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts index 5b7ed39947bf7..9d70c7750143a 100644 --- a/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts +++ b/tests/UI/campaigns/functional/FO/classic/09_productPage/02_productPage/03_changeImage.ts @@ -24,7 +24,7 @@ import ProductData from '@data/faker/product'; import {expect} from 'chai'; import type {BrowserContext, Page} from 'playwright'; -const baseContext: string = 'functional_FO_classic_productPage_quickView_changeImage'; +const baseContext: string = 'functional_FO_classic_productPage_productPage_changeImage'; /* Pre-condition: diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts index dde1648123b32..a6eff95f6de0d 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts @@ -25,7 +25,7 @@ import ProductData from '@data/faker/product'; import {expect} from 'chai'; import type {BrowserContext, Page} from 'playwright'; -const baseContext: string = 'functional_FO_hummingbird_productPage_quickView_changeImage'; +const baseContext: string = 'functional_FO_hummingbird_productPage_productPage_changeImage'; /* Pre-condition: From 2ef68464f4635bcfec97c2c8a68037118d633e14 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Mon, 22 Apr 2024 17:54:53 +0200 Subject: [PATCH 07/10] Fix review --- .../09_productPage/02_productPage/02_changeQuantity.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts index 0e164c6e18f6c..16f59869d188a 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/02_changeQuantity.ts @@ -94,9 +94,10 @@ describe('FO - Product page : Change quantity', async () => { }); // @todo : https://github.com/PrestaShop/hummingbird/pull/600 - it.skip('should set the quantity 0 and check that the add to cart button is disabled', async function () { + it('should set the quantity 0 and check that the add to cart button is disabled', async function () { await testContext.addContextItem(this, 'testIdentifier', 'checkAddToCartButtonIsDisabled', baseContext); + this.skip(); await productPage.setQuantity(page, 0); const isButtonDisabled = await productPage.isAddToCartButtonEnabled(page); @@ -131,9 +132,10 @@ describe('FO - Product page : Change quantity', async () => { }); // @todo : https://github.com/PrestaShop/hummingbird/pull/600 - it.skip('should set \'-24\' in the quantity input', async function () { + it('should set \'-24\' in the quantity input', async function () { await testContext.addContextItem(this, 'testIdentifier', 'updateQuantityByInput2', baseContext); + this.skip(); await productPage.setQuantity(page, '-24'); const isButtonDisabled = await productPage.isAddToCartButtonEnabled(page); From deb82c5e63dba6e90ea8cd30f4fc5b5809efe7df Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Tue, 23 Apr 2024 11:01:23 +0200 Subject: [PATCH 08/10] Functional Tests : API : GET /product/{productId}/images --- .../10_product/06_getProductIdImages.ts | 234 ++++++++++++++++++ .../BO/catalog/products/add/descriptionTab.ts | 8 +- 2 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 tests/UI/campaigns/functional/API/02_endpoints/10_product/06_getProductIdImages.ts diff --git a/tests/UI/campaigns/functional/API/02_endpoints/10_product/06_getProductIdImages.ts b/tests/UI/campaigns/functional/API/02_endpoints/10_product/06_getProductIdImages.ts new file mode 100644 index 0000000000000..24373a1218f80 --- /dev/null +++ b/tests/UI/campaigns/functional/API/02_endpoints/10_product/06_getProductIdImages.ts @@ -0,0 +1,234 @@ +// Import utils +import api from '@utils/api'; +import helper from '@utils/helpers'; +import testContext from '@utils/testContext'; + +// Import commonTests +import {deleteAPIClientTest} from '@commonTests/BO/advancedParameters/authServer'; +import loginCommon from '@commonTests/BO/loginBO'; + +// Import pages +import apiClientPage from 'pages/BO/advancedParameters/APIClient'; +import addNewApiClientPage from '@pages/BO/advancedParameters/APIClient/add'; +import productsPage from '@pages/BO/catalog/products'; +import createProductsPage from '@pages/BO/catalog/products/add'; +import descriptionTab from '@pages/BO/catalog/products/add/descriptionTab'; +import dashboardPage from '@pages/BO/dashboard'; + +// Import data +import Languages from '@data/demo/languages'; +import Products from '@data/demo/products'; +import APIClientData from '@data/faker/APIClient'; + +import {expect} from 'chai'; +import type {APIRequestContext, BrowserContext, Page} from 'playwright'; + +const baseContext: string = 'functional_API_endpoints_product_getProductIdImages'; + +describe('API : GET /product/{productId}/images', async () => { + let apiContext: APIRequestContext; + let browserContext: BrowserContext; + let page: Page; + let accessToken: string; + let clientSecret: string; + let jsonResponse: any; + + const clientScope: string = 'product_read'; + const clientData: APIClientData = new APIClientData({ + enabled: true, + scopes: [ + clientScope, + ], + }); + + describe('GET /product/{productId}/images', async () => { + before(async function () { + browserContext = await helper.createBrowserContext(this.browser); + page = await helper.newTab(browserContext); + + apiContext = await helper.createAPIContext(global.API.URL); + }); + + after(async () => { + await helper.closeBrowserContext(browserContext); + }); + + describe('BackOffice : Fetch the access token', async () => { + it('should login in BO', async function () { + await loginCommon.loginBO(this, page); + }); + + it('should go to \'Advanced Parameters > API Client\' page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToAdminAPIPage', baseContext); + + await dashboardPage.goToSubMenu( + page, + dashboardPage.advancedParametersLink, + dashboardPage.adminAPILink, + ); + + const pageTitle = await apiClientPage.getPageTitle(page); + expect(pageTitle).to.eq(apiClientPage.pageTitle); + }); + + it('should check that no records found', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkThatNoRecordFound', baseContext); + + const noRecordsFoundText = await apiClientPage.getTextForEmptyTable(page); + expect(noRecordsFoundText).to.contains('warning No records found'); + }); + + it('should go to add New API Client page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToNewAPIClientPage', baseContext); + + await apiClientPage.goToNewAPIClientPage(page); + + const pageTitle = await addNewApiClientPage.getPageTitle(page); + expect(pageTitle).to.eq(addNewApiClientPage.pageTitleCreate); + }); + + it('should create API Client', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'createAPIClient', baseContext); + + const textResult = await addNewApiClientPage.addAPIClient(page, clientData); + expect(textResult).to.contains(addNewApiClientPage.successfulCreationMessage); + + const textMessage = await addNewApiClientPage.getAlertInfoBlockParagraphContent(page); + expect(textMessage).to.contains(addNewApiClientPage.apiClientGeneratedMessage); + }); + + it('should copy client secret', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'copyClientSecret', baseContext); + + await addNewApiClientPage.copyClientSecret(page); + + clientSecret = await addNewApiClientPage.getClipboardText(page); + expect(clientSecret.length).to.be.gt(0); + }); + + it('should request the endpoint /access_token', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'requestOauth2Token', baseContext); + + const apiResponse = await apiContext.post('access_token', { + form: { + client_id: clientData.clientId, + client_secret: clientSecret, + grant_type: 'client_credentials', + scope: clientScope, + }, + }); + expect(apiResponse.status()).to.eq(200); + expect(api.hasResponseHeader(apiResponse, 'Content-Type')).to.eq(true); + expect(api.getResponseHeader(apiResponse, 'Content-Type')).to.contains('application/json'); + + const jsonResponse = await apiResponse.json(); + expect(jsonResponse).to.have.property('access_token'); + expect(jsonResponse.token_type).to.be.a('string'); + + accessToken = jsonResponse.access_token; + }); + }); + + describe('API : Create the Product Image', async () => { + it('should request the endpoint /product/{productId}/image', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'requestEndpoint', baseContext); + + const apiResponse = await apiContext.get(`product/${Products.demo_1.id}/images`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + expect(apiResponse.status()).to.eq(200); + expect(api.hasResponseHeader(apiResponse, 'Content-Type')).to.eq(true); + expect(api.getResponseHeader(apiResponse, 'Content-Type')).to.contains('application/json'); + + jsonResponse = await apiResponse.json(); + }); + + it('should check the JSON Response keys', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkResponseKeys', baseContext); + + expect(jsonResponse.length).to.be.gt(0); + + for (let i:number = 0; i < jsonResponse.length; i++) { + expect(jsonResponse[i]).to.have.all.keys( + 'imageId', + 'imageUrl', + 'thumbnailUrl', + 'legends', + 'cover', + 'position', + 'shopIds', + ); + + expect(jsonResponse[i].imageId).to.be.gt(0); + expect(jsonResponse[i].imageUrl).to.be.a('string'); + expect(jsonResponse[i].thumbnailUrl).to.be.a('string'); + expect(jsonResponse[i].legends[Languages.english.id]).to.be.a('string'); + expect(jsonResponse[i].legends[Languages.french.id]).to.be.a('string'); + expect(jsonResponse[i].cover).to.be.a('boolean'); + expect(jsonResponse[i].position).to.be.a('number'); + expect(jsonResponse[i].shopIds).to.be.a('array'); + } + }); + }); + + describe('BackOffice : Check the Product Images', async () => { + it('should go to \'Catalog > Products\' page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToProductsPage', baseContext); + + await dashboardPage.goToSubMenu(page, dashboardPage.catalogParentLink, dashboardPage.productsLink); + await productsPage.closeSfToolBar(page); + + const pageTitle = await productsPage.getPageTitle(page); + expect(pageTitle).to.contains(productsPage.pageTitle); + }); + + it('should filter list by name', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'filterProduct', baseContext); + + await productsPage.resetFilter(page); + await productsPage.filterProducts(page, 'product_name', Products.demo_1.name); + + const numProducts = await productsPage.getNumberOfProductsFromList(page); + expect(numProducts).to.be.equal(1); + + const productName = await productsPage.getTextColumn(page, 'product_name', 1); + expect(productName).to.contains(Products.demo_1.name); + }); + + it('should go to edit product page', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'goToEditProductPage', baseContext); + + await productsPage.goToProductPage(page, 1); + + const pageTitle: string = await createProductsPage.getPageTitle(page); + expect(pageTitle).to.contains(createProductsPage.pageTitle); + + const numImages = await descriptionTab.getNumberOfImages(page); + expect(numImages).to.be.equals(jsonResponse.length); + }); + + it('should fetch images informations', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'checkJSONItems', baseContext); + + for (let idxItem: number = 0; idxItem < jsonResponse.length; idxItem++) { + const productImageInformation = await descriptionTab.getProductImageInformation(page, idxItem + 1); + + expect(productImageInformation.id).to.equal(jsonResponse[idxItem].imageId); + + expect(productImageInformation.caption.en).to.equal(jsonResponse[idxItem].legends[Languages.english.id]); + expect(productImageInformation.caption.fr).to.equal(jsonResponse[idxItem].legends[Languages.french.id]); + + expect(productImageInformation.isCover).to.equal(jsonResponse[idxItem].cover); + + expect(productImageInformation.position).to.equal(jsonResponse[idxItem].position); + } + }); + }); + }); + + // Post-condition: Delete an API Client + deleteAPIClientTest(`${baseContext}_postTest_0`); +}); diff --git a/tests/UI/pages/BO/catalog/products/add/descriptionTab.ts b/tests/UI/pages/BO/catalog/products/add/descriptionTab.ts index 9ea2b804464be..4d12b82f93804 100644 --- a/tests/UI/pages/BO/catalog/products/add/descriptionTab.ts +++ b/tests/UI/pages/BO/catalog/products/add/descriptionTab.ts @@ -232,12 +232,16 @@ class DescriptionTab extends BOBasePage { await page.locator(this.productImageDropZoneBtnLang).click(); await this.elementVisible(page, this.productImageDropZoneDropdown); await page.locator(this.productImageDropZoneDropdownItem('en')).click(); - const captionEN = await page.locator(this.productImageDropZoneCaption).innerText(); + const captionEN = await page + .locator(this.productImageDropZoneCaption) + .evaluate((node: HTMLTextAreaElement): string => node.value); await page.locator(this.productImageDropZoneBtnLang).click(); await this.elementVisible(page, this.productImageDropZoneDropdown); await page.locator(this.productImageDropZoneDropdownItem('fr')).click(); - const captionFR = await page.locator(this.productImageDropZoneCaption).innerText(); + const captionFR = await page + .locator(this.productImageDropZoneCaption) + .evaluate((node: HTMLTextAreaElement): string => node.value); await page.locator(this.productImageDropZoneCloseButton).click(); From f8946f86d9bf2ffebe31282fc2ebb0cd22cac7a8 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Tue, 23 Apr 2024 11:05:35 +0200 Subject: [PATCH 09/10] Fix review and add 2 steps to the scenario --- .../02_productPage/03_changeImage.ts | 24 +++++++++++++++---- tests/UI/pages/FO/classic/product/index.ts | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts index a6eff95f6de0d..f38de905b9a59 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts @@ -64,6 +64,9 @@ describe('FO - Product page - Quick view : Change image', async () => { await files.generateImage(newProductData.thumbImage!); await files.generateImage('secondThumbImage.jpg'); await files.generateImage('thirdThumbImage.jpg'); + await files.generateImage('fourthThumbImage.jpg'); + await files.generateImage('fifthThumbImage.jpg'); + await files.generateImage('sixthThumbImage.jpg'); }); after(async () => { @@ -72,9 +75,12 @@ describe('FO - Product page - Quick view : Change image', async () => { await files.deleteFile(newProductData.thumbImage!); await files.deleteFile('secondThumbImage.jpg'); await files.deleteFile('thirdThumbImage.jpg'); + await files.deleteFile('fourthThumbImage.jpg'); + await files.deleteFile('fifthThumbImage.jpg'); + await files.deleteFile('sixthThumbImage.jpg'); }); - describe(`PRE-TEST: Create new product '${newProductData.name}' with 4 images`, async () => { + describe(`PRE-TEST: Create new product '${newProductData.name}' with 7 images`, async () => { it('should login in BO', async function () { await loginCommon.loginBO(this, page); }); @@ -117,14 +123,15 @@ describe('FO - Product page - Quick view : Change image', async () => { expect(createProductMessage).to.equal(createProductsPage.successfulUpdateMessage); }); - it('should add 4 images', async function () { + it('should add 7 images', async function () { await testContext.addContextItem(this, 'testIdentifier', 'addImage', baseContext); await descriptionTab.addProductImages(page, - [newProductData.coverImage, newProductData.thumbImage, 'secondThumbImage.jpg', 'thirdThumbImage.jpg']); + [newProductData.coverImage, newProductData.thumbImage, 'secondThumbImage.jpg', 'thirdThumbImage.jpg', + 'fourthThumbImage.jpg', 'fifthThumbImage.jpg', 'sixthThumbImage.jpg']); const numOfImages = await descriptionTab.getNumberOfImages(page); - expect(numOfImages).to.equal(4); + expect(numOfImages).to.equal(7); }); }); @@ -216,6 +223,15 @@ describe('FO - Product page - Quick view : Change image', async () => { const secondCoverImagePosition = await productPage.getCoverImage(page); expect(coverImagePosition).to.not.equal(secondCoverImagePosition); }); + + it('should click on the last image and check it', async function () { + await testContext.addContextItem(this, 'testIdentifier', 'clickLastImage', baseContext); + + const coverImagePosition = await productPage.getCoverImage(page); + + const lastCoverImagePosition = await productPage.selectThumbImage(page, 7); + expect(coverImagePosition).to.not.equal(lastCoverImagePosition); + }); }); // Post-condition : Delete created product diff --git a/tests/UI/pages/FO/classic/product/index.ts b/tests/UI/pages/FO/classic/product/index.ts index 595279091556c..fd8ad841e34c8 100644 --- a/tests/UI/pages/FO/classic/product/index.ts +++ b/tests/UI/pages/FO/classic/product/index.ts @@ -652,7 +652,7 @@ class Product extends FOBasePage { async closeProductModal(page: Page): Promise { await page.mouse.click(5, 5); - return this.elementNotVisible(page, '#product-modal', 2000); + return this.elementNotVisible(page, this.productModal, 2000); } /** From cfc76a2bae73ae45aeb20ea421f81b5efd09dc55 Mon Sep 17 00:00:00 2001 From: nesrineabdmouleh Date: Tue, 23 Apr 2024 11:06:33 +0200 Subject: [PATCH 10/10] Fix comment --- .../hummingbird/09_productPage/02_productPage/03_changeImage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts index f38de905b9a59..d17fb45f01cb5 100644 --- a/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts +++ b/tests/UI/campaigns/functional/FO/hummingbird/09_productPage/02_productPage/03_changeImage.ts @@ -30,7 +30,7 @@ const baseContext: string = 'functional_FO_hummingbird_productPage_productPage_c /* Pre-condition: - Install the theme hummingbird -- Create product with 4 images +- Create product with 7 images Scenario: - Go to FO - Go to the created product page