diff --git a/README.md b/README.md index 546d304..aeac3fe 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ ## Overview -This repository contains a test automation framework for the Luma web application using Playwright and Cucumber. The framework is designed with Page Object Model (POM) and Facade design patterns to ensure maintainability and scalability. +This repository contains a test automation framework for the Luma web application using Playwright and Cucumber. +The framework is designed with Page Object Model (POM) and Facade design patterns to ensure maintainability and scalability. ## Features @@ -34,13 +35,13 @@ This repository contains a test automation framework for the Luma web applicatio ### Installation -1. Clone the repository: +1. Clone the repository: git clone https://github.com/najeeb1023/magento-webapp.git -2. Install dependencies: +2. Install dependencies: npm i -3. To run the project: - cucumber:luma - To run all the test scenarios. - cucumber:luma:tags - Add your scenario tag in the end using -> @tag. - cucumber:luma:debug - After attatching the debugger you can easily debug the desired scenario. \ No newline at end of file +3. To run the project: + * cucumber:luma - To run all the test scenarios. + * cucumber:luma:tags - Add your scenario tag in the end using -> @tag. + * cucumber:luma:debug - After attatching the debugger you can easily debug the desired scenario. \ No newline at end of file diff --git a/src/test/features/Registration.feature b/src/test/features/Registration.feature index 451d5b4..a1d065a 100644 --- a/src/test/features/Registration.feature +++ b/src/test/features/Registration.feature @@ -14,5 +14,4 @@ Feature: Verify that the user is able to register an account. Examples: | FirstName | LastName | Email | Password | - | TestZulfi | ZulfiTest | zulfiTest@gmail.com | testsss123! | | FacadeUser | UserFace | facade23@gmail.com | facading123!@ | diff --git a/src/test/features/ShopBySearch.feature b/src/test/features/ShopBySearch.feature new file mode 100644 index 0000000..db617bb --- /dev/null +++ b/src/test/features/ShopBySearch.feature @@ -0,0 +1,17 @@ +@ShopyBySearch + +Feature: Verify that the user is able to purchase an item by searching. + + User wants to buy something via search. + + Background: User is landed on the webpage. + Given The user lands at the webpage. + + Scenario: User navigates to any random product to get its title. + When The user clicks on the "
" section and the user clicks on "" option. + And The products are shown and user navigates to a product. + And User searches that product. + + Examples: + | Section | Attire | + | Men | Shorts | \ No newline at end of file diff --git a/src/test/features/UserShopping.feature b/src/test/features/UserShoppingByWear.feature similarity index 89% rename from src/test/features/UserShopping.feature rename to src/test/features/UserShoppingByWear.feature index 4f3eaca..242d6ad 100644 --- a/src/test/features/UserShopping.feature +++ b/src/test/features/UserShoppingByWear.feature @@ -14,8 +14,8 @@ Feature: Verify that the user is able to purchase some item. And The details of the product are shown. Examples: - | Section | Attire | - | Men | Hoodies & Sweatshirts | + | Section | Attire | + | Men | Shorts | @WomenShopping Scenario: User shops for Women Jackets. diff --git a/src/test/pages/ShopBySearch.ts b/src/test/pages/ShopBySearch.ts new file mode 100644 index 0000000..9910027 --- /dev/null +++ b/src/test/pages/ShopBySearch.ts @@ -0,0 +1,24 @@ +import { UserShoppingByWear } from "./UserShoppingByWear"; +import * as shopBySearchPage from "../../../src/test/resources/shopBySearchPage.json"; +import { PageElement } from "../resources/interfaces/iPageElement"; +import { pageFixture } from "../hooks/pageFixture"; + + function getResource(resourceName: string){ + return shopBySearchPage.webElements.find((elementName: PageElement) => elementName.elementName == resourceName) as PageElement; + }; + + export class ShopBySearch extends UserShoppingByWear { + + shopBySearchLocators = { + lumaLogo:() => pageFixture.page.locator(getResource('lumaLogo').selectorValue), + searchBar:() => pageFixture.page.locator(getResource('searchBar').selectorValue) + }; + + + public async searchProduct():Promise{ + const product: string = await this.saveSelectedProduct(); + console.log('Selected product:', product); + await this.shopBySearchLocators.searchBar().fill(product); // Fill searchBarInput with the actual product name + await pageFixture.page.keyboard.press('Enter'); + }; + }; \ No newline at end of file diff --git a/src/test/pages/UserShopping.ts b/src/test/pages/UserShoppingByWear.ts similarity index 64% rename from src/test/pages/UserShopping.ts rename to src/test/pages/UserShoppingByWear.ts index c6bb7a4..f06c209 100644 --- a/src/test/pages/UserShopping.ts +++ b/src/test/pages/UserShoppingByWear.ts @@ -1,11 +1,11 @@ import { pageFixture } from "../hooks/pageFixture"; -import * as userShoppingPage from "../resources/userShoppingPage.json"; +import * as userShoppingByWearPage from "../resources/userShoppingPageByWear.json"; import * as registrationPage from "../resources/registrationPage.json"; import { PageElement } from "../resources/interfaces/iPageElement"; import { Page, expect } from "@playwright/test"; function getResource(resourceName: string){ - return userShoppingPage.webElements.find((element: PageElement) => element.elementName == resourceName) as PageElement; + return userShoppingByWearPage.webElements.find((element: PageElement) => element.elementName == resourceName) as PageElement; } function getResourceRegisterPage(resourceName: string){ @@ -13,34 +13,36 @@ import { Page, expect } from "@playwright/test"; } export class CategoryAndProductSelectionFacade{ - private userShopping: UserShopping; + private userShoppingByWear: UserShoppingByWear; - constructor(userShopping: UserShopping){ - this.userShopping = userShopping; + constructor(userShoppingByWear: UserShoppingByWear){ + this.userShoppingByWear = userShoppingByWear; }; public async productSelection(section: string, attire: string){ - await this.userShopping.goSectionAndAttire(section, attire); + await this.userShoppingByWear.goSectionAndAttire(section, attire); }; public async selectRandomItem(){ - await this.userShopping.showItems(); - await this.userShopping.selectRandomProduct(); + await this.userShoppingByWear.showItems(); + await this.userShoppingByWear.selectRandomProduct(); }; public async showProductDetails(){ - await this.userShopping.getProductPriceAndSizes(); - await this.userShopping.selectAndGetProductColors(); - await this.userShopping.addToCartProduct(); + await this.userShoppingByWear.getProductPriceAndSizes(); + await this.userShoppingByWear.selectAndGetProductColors(); + await this.userShoppingByWear.addToCartProduct(); }; }; - export class UserShopping { + export class UserShoppingByWear { constructor(public page: Page){ pageFixture.page = page; }; - userShoppingLocators = { + // public static savedProductName:string; + + userShoppingByWearByWearLocators = { shoppingSectionHeader:() => pageFixture.page.locator(getResource('shoppingSectionHeader').selectorValue), attireSectionBtn:() => pageFixture.page.locator(getResource('attireSectionBtn').selectorValue), itemsShown:() => pageFixture.page.locator(getResource('itemsShown').selectorValue), @@ -55,7 +57,9 @@ import { Page, expect } from "@playwright/test"; getColorSwatches:() => pageFixture.page.locator(getResource('getColorSwatches').selectorValue), getSelectedProductSize:() => pageFixture.page.locator(getResource('getSelectedProductSize').selectorValue), pageMessage:() => pageFixture.page.locator(getResourceRegisterPage('pageMessage').selectorValue), - addToCartBtn:() => pageFixture.page.locator(getResource('addToCartBtn').selectorValue) + addToCartBtn:() => pageFixture.page.locator(getResource('addToCartBtn').selectorValue), + pageTitle:() => pageFixture.page.locator(getResourceRegisterPage('createAccHeading').selectorValue), + productTitle:() => pageFixture.page.locator(getResourceRegisterPage('productTitle').selectorValue) }; @@ -69,7 +73,7 @@ import { Page, expect } from "@playwright/test"; }; public async showItems():Promise{ - const getNumberOfProducts = await this.userShoppingLocators.productShown().count(); + const getNumberOfProducts = await this.userShoppingByWearByWearLocators.productShown().count(); console.log(' Products shown -> ' + getNumberOfProducts + '\n'); for(let i=1;i<=getNumberOfProducts;i++){ const getEl = await pageFixture.page.locator(getResource('itemsShown').selectorValue.replace('FLAG', i.toString())).allTextContents(); @@ -79,39 +83,43 @@ import { Page, expect } from "@playwright/test"; }; }; - public async selectRandomProduct():Promise{ - const getNumberOfProducts = await this.userShoppingLocators.productShown().count(); + public async selectRandomProduct():Promise{ + const getNumberOfProducts = await this.userShoppingByWearByWearLocators.productShown().count(); let ind: number = Math.floor(Math.random() * (getNumberOfProducts - 1))+ 1; - if (ind == 0) { Math.floor(Math.random() * getNumberOfProducts); } else { const el = (pageFixture.page.locator(getResource('itemsShown').selectorValue.replace('FLAG', `${ind}`))); await expect(el).toBeVisible(); await el.dblclick({force: true, timeout: 3000}); - const list = await this.userShoppingLocators.shoppingList().isVisible(); - const listCount = await this.userShoppingLocators.shoppingList().count(); + const list = await this.userShoppingByWearByWearLocators.shoppingList().isVisible(); + const listCount = await this.userShoppingByWearByWearLocators.shoppingList().count(); if (list == true){ - pageFixture.logger.warn('User not navigated, retrying click'); + await pageFixture.logger.warn('User not navigated, retrying click'); await pageFixture.page.waitForLoadState('networkidle'); for(let i=0;i{ + const productName = (await (this.userShoppingByWearByWearLocators.pageTitle().textContent())).trim(); + return productName; + }; + public async getProductPriceAndSizes():Promise{ - const priceText = (await this.userShoppingLocators.productPrice().textContent()).trim(); - if(await this.userShoppingLocators.productPrice().isVisible() == true){ + const priceText = (await this.userShoppingByWearByWearLocators.productPrice().textContent()).trim(); + if(await this.userShoppingByWearByWearLocators.productPrice().isVisible() == true){ pageFixture.logger.warn('Product price is not visible, attempting to click product again.') const regEx = /\$\d+\.\d{2}/; const matchPriceText = priceText.match(regEx); console.log("The price of the product -> "+matchPriceText[0]); - const sizeText = (await this.userShoppingLocators.productSize().textContent()).trim(); - const getSizes = await this.userShoppingLocators.getProductSizesAvailable().count(); + const sizeText = (await this.userShoppingByWearByWearLocators.productSize().textContent()).trim(); + const getSizes = await this.userShoppingByWearByWearLocators.getProductSizesAvailable().count(); console.log('\x1b[36m%s\x1b[0m',sizeText+'s' + ' available are: '); for(let i=1;i<=getSizes;i++){ const el = await pageFixture.page.locator(getResource('getProductSize').selectorValue.replace('FLAG', i.toString())).allTextContents(); @@ -122,15 +130,15 @@ import { Page, expect } from "@playwright/test"; } else { return this.selectRandomProduct(); }; - const getSizes = await this.userShoppingLocators.getProductSizesAvailable().count(); + const getSizes = await this.userShoppingByWearByWearLocators.getProductSizesAvailable().count(); let ind: number = Math.floor(Math.random() * (getSizes - 1))+ 1; const sizeEl = (pageFixture.page.locator(getResource('getProductSize').selectorValue.replace('FLAG', `${ind}`))); await sizeEl.click(); - console.log("Size selected: "+await this.userShoppingLocators.getSelectedProductSize().textContent()); + console.log("Size selected: "+await this.userShoppingByWearByWearLocators.getSelectedProductSize().textContent()); }; public async selectAndGetProductColors():Promise{ - const getColorSwatch = await this.userShoppingLocators.getColorSwatches().count(); + const getColorSwatch = await this.userShoppingByWearByWearLocators.getColorSwatches().count(); console.log('Color found: ' + getColorSwatch); let ind: number = Math.floor(Math.random() * (getColorSwatch - 1)) + 1; if (ind == 0) { @@ -138,15 +146,15 @@ import { Page, expect } from "@playwright/test"; } else { const colorToBeSelect = await pageFixture.page.locator(getResource('colorSwatch').selectorValue.replace('FLAG', `${ind}`)); await colorToBeSelect.click(); - await this.userShoppingLocators.getCurrentSelectedColor().isVisible(); - const getColor = await this.userShoppingLocators.getCurrentSelectedColor().textContent(); + await this.userShoppingByWearByWearLocators.getCurrentSelectedColor().isVisible(); + const getColor = await this.userShoppingByWearByWearLocators.getCurrentSelectedColor().textContent(); console.log('Selected color: ' +getColor); }; }; public async addToCartProduct():Promise{ - await this.userShoppingLocators.addToCartBtn().click(); - await expect(this.userShoppingLocators.pageMessage()).toBeVisible(); + await this.userShoppingByWearByWearLocators.addToCartBtn().click(); + await expect(this.userShoppingByWearByWearLocators.pageMessage()).toBeVisible(); }; diff --git a/src/test/resources/registrationPage.json b/src/test/resources/registrationPage.json index 2017a93..8dc14e7 100644 --- a/src/test/resources/registrationPage.json +++ b/src/test/resources/registrationPage.json @@ -36,6 +36,10 @@ { "elementName": "pageMessage", "selectorValue": "//div[contains(@class,'page messages')]" + }, + { + "elementName": "productTitle", + "selectorValue": "//h1[contains(@class,'page-title')]" } ] } \ No newline at end of file diff --git a/src/test/resources/shopBySearchPage.json b/src/test/resources/shopBySearchPage.json new file mode 100644 index 0000000..f762236 --- /dev/null +++ b/src/test/resources/shopBySearchPage.json @@ -0,0 +1,13 @@ +{ + "name": "Search Locator", + "webElements": [ + { + "elementName": "lumaLogo", + "selectorValue": "//div[contains(@class,'header content')]//a[contains(@class,'logo')]" + }, + { + "elementName": "searchBar", + "selectorValue": "//div[contains(@class,'field search')]//input" + } + ] +} diff --git a/src/test/resources/userShoppingPage.json b/src/test/resources/userShoppingPageByWear.json similarity index 100% rename from src/test/resources/userShoppingPage.json rename to src/test/resources/userShoppingPageByWear.json diff --git a/src/test/steps/shopBySearch.ts b/src/test/steps/shopBySearch.ts new file mode 100644 index 0000000..5e45c26 --- /dev/null +++ b/src/test/steps/shopBySearch.ts @@ -0,0 +1,10 @@ +import { When, setDefaultTimeout } from "@cucumber/cucumber"; +import { ShopBySearch } from "../pages/ShopBySearch"; +import { pageFixture } from "../hooks/pageFixture"; + +setDefaultTimeout(60000); +let shopBySearch = new ShopBySearch(pageFixture.page); + +When('User searches that product.', async function (){ + await shopBySearch.searchProduct(); +}); \ No newline at end of file diff --git a/src/test/steps/userShopping.ts b/src/test/steps/userShoppingByWear.ts similarity index 78% rename from src/test/steps/userShopping.ts rename to src/test/steps/userShoppingByWear.ts index 1937733..7c74a06 100644 --- a/src/test/steps/userShopping.ts +++ b/src/test/steps/userShoppingByWear.ts @@ -1,10 +1,10 @@ import { When, Then, setDefaultTimeout } from "@cucumber/cucumber"; -import { CategoryAndProductSelectionFacade, UserShopping } from "../pages/UserShopping"; +import { CategoryAndProductSelectionFacade, UserShoppingByWear } from "../pages/UserShoppingByWear"; import { pageFixture } from "../hooks/pageFixture"; setDefaultTimeout(60000); -let userShopping = new UserShopping(pageFixture.page); -let categoryAndProductSectionFacade = new CategoryAndProductSelectionFacade(userShopping); +let userShoppingByWear = new UserShoppingByWear(pageFixture.page); +let categoryAndProductSectionFacade = new CategoryAndProductSelectionFacade(userShoppingByWear); When("The user clicks on the {string} section and the user clicks on {string} option.", async function (section: string, attire: string){ await categoryAndProductSectionFacade.productSelection(section, attire);