From 81ab5564435bcb05cc46738d95be929b5160503a Mon Sep 17 00:00:00 2001 From: Nishi Mewada Date: Tue, 21 Jul 2026 15:10:34 -0400 Subject: [PATCH 1/2] Nishi Playwright Cucumber Solution --- .DS_Store | Bin 0 -> 8196 bytes features/login.feature | 20 ++++++++++++++++---- features/product.feature | 19 ++++++++++++------- features/purchase.feature | 24 ++++++++++++++++-------- pages/cart.page.ts | 24 ++++++++++++++++++++++++ pages/checkout.page.ts | 36 ++++++++++++++++++++++++++++++++++++ pages/login.page.ts | 18 +++++++++++++++++- pages/menu.page.ts | 26 ++++++++++++++++++++++++++ pages/product.page.ts | 34 ++++++++++++++++++++++++++++++++-- playwrightUtilities.ts | 3 ++- steps/checkout.steps.ts | 32 ++++++++++++++++++++++++++++++++ steps/login.steps.ts | 17 +++++++++++++++++ steps/product.steps.ts | 10 +++++++++- 13 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 .DS_Store create mode 100644 pages/cart.page.ts create mode 100644 pages/checkout.page.ts create mode 100644 pages/menu.page.ts create mode 100644 steps/checkout.steps.ts diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..79b2f3658a39cc4bf0235d7941f0a380094b5aa7 GIT binary patch literal 8196 zcmeHMJ&zMH5FH=8Bq)FmgwQoe+k$ivS0LKu*efV#IMD)^50XG~8{H*BRHOm{5(O0! z5)yv^8hRQEO2ogQp@8ZN1aE9#cDx^f3L(ypyyMt2w%?nv9dC$8txr2^L@Pv8M`pR) zz@sE_JLf`LvNh)*73^u)Tia}>qj5pgS9k_I1D*lTfM>un@UJj{Gn-Sr;@mf_{^=R; z3>-)Xcz=kHS;kh*jMQ5P9{LIZ8K7Dxl$W^x8Ce-yIWrP1NXnQ(8dGJj7|NKVKC*hT zl`|uaIVpSjPo>msT~NF{O}`GA`7a9JUaiO!2v! zXF)x!)^zc!%g_}4NaUSwbL5UjhPhnBMXOJUqd=Nc2PkjC+gzH`5Z`j%Q+v62SK3Jw z;f;5Aw?>t^64S-+L1@>~0{sl(pXf`xhkty+ROkea5uz2jHo%Abt3yZLf2>m zoF;S?vWZyjxPH - Then I will login as 'standard_user' - # TODO: Sort the items by - # TODO: Validate all 6 items are sorted correctly by price - Examples: - # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + + Scenario: Validate product sort by price + Then I will login as "standard_user" + # TODO: Sort the items by - done below + Then I will sort products by "" + # TODO: Validate all 6 items are sorted correctly by price- done below + Then I should see all items sorted by "" + # TODO: extend the datatable to paramterize this test- done below + Examples: + | sort | order | + | Price (low to high) | asc | + | Price (high to low) | desc | \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..2e6069bc 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -4,11 +4,19 @@ Feature: Purchase Feature Given I open the "https://www.saucedemo.com/" page Scenario: Validate successful purchase text - Then I will login as 'standard_user' - Then I will add the backpack to the cart - # TODO: Select the cart (top-right) - # TODO: Select Checkout - # TODO: Fill in the First Name, Last Name, and Zip/Postal Code - # TODO: Select Continue - # TODO: Select Finish - # TODO: Validate the text 'Thank you for your order!' \ No newline at end of file + Then I will login as "standard_user" + Then I will add the backpack to the cart + # TODO: Select the cart (top-right)- done below + Then I will open the cart + # TODO: Extending the testing coverage- done below + Then I should see the product "Sauce Labs Backpack" in the cart + # TODO: Select Checkout- done below + Then I will proceed to checkout + # TODO: Fill in the First Name, Last Name, and Zip/Postal Code- done below + Then I will fill in checkout information "Nishi" "Mewada" "28262" + # TODO: Select Continue- done below + Then I will continue to overview + # TODO: Select Finish- done below + Then I will finish the purchase + # TODO: Validate the text "Thank you for your order!"- done below + Then I should see the purchase confirmation text "Thank you for your order!" diff --git a/pages/cart.page.ts b/pages/cart.page.ts new file mode 100644 index 00000000..685c9000 --- /dev/null +++ b/pages/cart.page.ts @@ -0,0 +1,24 @@ +import { expect, Page } from "@playwright/test" + +export class Cart { + private readonly page: Page + private readonly cartIcon: string = '.shopping_cart_link'; + private readonly checkoutButton: string = '#checkout'; + private readonly cartItems: string = '.cart_item'; + + constructor(page: Page) { + this.page = page; + } + + public async openCart() { + await this.page.locator(this.cartIcon).click(); + } + + public async validateProductInCart(expectedProduct: string) { + await expect(this.page.locator(this.cartItems)).toContainText(expectedProduct); + } + + public async proceedToCheckout() { + await this.page.locator(this.checkoutButton).click(); + } +} \ No newline at end of file diff --git a/pages/checkout.page.ts b/pages/checkout.page.ts new file mode 100644 index 00000000..abfeb5c5 --- /dev/null +++ b/pages/checkout.page.ts @@ -0,0 +1,36 @@ +import { Page } from "@playwright/test" + +export class Checkout { + private readonly page: Page + private readonly firstNameField: string = '#first-name'; + private readonly lastNameField: string = '#last-name'; + private readonly zipField: string = '#postal-code'; + private readonly continueButton: string = '#continue'; + private readonly finishButton: string = '#finish'; + private readonly confirmationText: string = '.complete-header'; + + constructor(page: Page) { + this.page = page; + } + + public async fillInformation(firstName: string, lastName: string, zip: string) { + await this.page.locator(this.firstNameField).fill(firstName); + await this.page.locator(this.lastNameField).fill(lastName); + await this.page.locator(this.zipField).fill(zip); + } + + public async continueToOverview() { + await this.page.locator(this.continueButton).click(); + } + + public async finishPurchase() { + await this.page.locator(this.finishButton).click(); + } + + public async validateConfirmationText(expectedText: string) { + const actualText = await this.page.locator(this.confirmationText).textContent(); + if (actualText?.trim() !== expectedText) { + throw new Error(`Expected confirmation text "${expectedText}" but found "${actualText}"`); + } + } +} \ No newline at end of file diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614b..4c794ca9 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -6,6 +6,9 @@ export class Login { private readonly passwordField: string = 'input[id="password"]' private readonly userNameField: string = 'input[id="user-name"]' private readonly loginButton: string = 'input[id="login-button"]' + private readonly errorMessage: string = '[data-test="error"]' + private readonly menuButton: string = '#react-burger-menu-btn' + private readonly logoutLink: string = '#logout_sidebar_link' constructor(page: Page) { this.page = page; @@ -23,4 +26,17 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } -} \ No newline at end of file + + public async loginWithCredentials(userName: string, password: string) { + await this.page.locator(this.userNameField).fill(userName); + await this.page.locator(this.passwordField).fill(password); + await this.page.locator(this.loginButton).click(); + } + + public async validateErrorMessage(expectedMessage: string) { + const actualMessage = (await this.page.locator(this.errorMessage).textContent())?.trim(); + if (actualMessage !== expectedMessage) { + throw new Error(`Expected error message to be ${expectedMessage} but found ${actualMessage}`); + } + } +} diff --git a/pages/menu.page.ts b/pages/menu.page.ts new file mode 100644 index 00000000..4ef6cd6d --- /dev/null +++ b/pages/menu.page.ts @@ -0,0 +1,26 @@ +import { Page } from "@playwright/test" + +export class Menu { + private readonly page: Page + + private readonly menuButton: string = '#react-burger-menu-btn' + private readonly logoutLink: string = '#logout_sidebar_link' + private readonly loginButton: string = '#login-button' + + constructor(page: Page) { + this.page = page; + } + + public async logout() { + await this.page.locator(this.menuButton).click(); + await this.page.locator(this.logoutLink).click(); + } + + public async validateLoginPage() { + const isLoginButtonVisible = await this.page.locator(this.loginButton).isVisible(); + + if (!isLoginButtonVisible) { + throw new Error('Login page is not displayed after logout'); + } + } +} \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..4ced56bd 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -1,9 +1,13 @@ -import { Page } from "@playwright/test" + +import { expect, Page } from "@playwright/test" export class Product { private readonly page: Page - private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + private readonly sortDropdown: string = 'select[data-test="product-sort-container"]' + private readonly itemPrices: string = '.inventory_item_price' + constructor(page: Page) { this.page = page; } @@ -11,4 +15,30 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async sortByPrice(sortOption: string) { + const valueMap: Record = { + 'Price (low to high)': 'lohi', + 'Price (high to low)': 'hilo', + }; + const optionValue = valueMap[sortOption]; + if (!optionValue) { + throw new Error(`Unsupported sort option: ${sortOption}`); + } + await this.page.locator(this.sortDropdown).selectOption(optionValue); + } + + public async validateSortedByPrice(order: 'asc' | 'desc') { + const priceTexts = await this.page + .locator(this.itemPrices) + .allTextContents(); + const prices = priceTexts.map((price) => + parseFloat(price.replace('$', '')) + ); + expect(prices).toHaveLength(6) + const expected = [...prices].sort((a, b) => + order === 'asc' ? a - b : b - a + ); + expect(prices).toEqual(expected); + } } \ No newline at end of file diff --git a/playwrightUtilities.ts b/playwrightUtilities.ts index 93244a29..8a50b042 100644 --- a/playwrightUtilities.ts +++ b/playwrightUtilities.ts @@ -6,7 +6,7 @@ const DEFAULT_TIMEOUT = 30000; export const initializeBrowser = async () => { if (!browser) { - browser = await chromium.launch({ headless: false }); + browser = await chromium.launch({ headless: false, channel:"chrome" }); } }; @@ -17,6 +17,7 @@ export const initializePage = async () => { } }; + export const getPage = (): Page => { if (!page) { throw new Error('Page has not been initialized. Please call initializePage first.'); diff --git a/steps/checkout.steps.ts b/steps/checkout.steps.ts new file mode 100644 index 00000000..d5654286 --- /dev/null +++ b/steps/checkout.steps.ts @@ -0,0 +1,32 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Cart } from '../pages/cart.page'; +import { Checkout } from '../pages/checkout.page'; + +Then('I will open the cart', async () => { + await new Cart(getPage()).openCart(); +}); + +Then('I should see the product {string} in the cart', async (productName) => { + await new Cart(getPage()).validateProductInCart(productName); +}); + +Then('I will proceed to checkout', async () => { + await new Cart(getPage()).proceedToCheckout(); +}); + +Then('I will fill in checkout information {string} {string} {string}', async (firstName, lastName, zip) => { + await new Checkout(getPage()).fillInformation(firstName, lastName, zip); +}); + +Then('I will continue to overview', async () => { + await new Checkout(getPage()).continueToOverview(); +}); + +Then('I will finish the purchase', async () => { + await new Checkout(getPage()).finishPurchase(); +}); + +Then('I should see the purchase confirmation text {string}', async (expectedText) => { + await new Checkout(getPage()).validateConfirmationText(expectedText); +}); \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..4aacccf4 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -1,6 +1,7 @@ import { Then } from '@cucumber/cucumber'; import { getPage } from '../playwrightUtilities'; import { Login } from '../pages/login.page'; +import { Menu } from '../pages/menu.page'; Then('I should see the title {string}', async (expectedTitle) => { await new Login(getPage()).validateTitle(expectedTitle); @@ -8,4 +9,20 @@ Then('I should see the title {string}', async (expectedTitle) => { Then('I will login as {string}', async (userName) => { await new Login(getPage()).loginAsUser(userName); +}); + +Then('I will login with username {string} and password {string}', async (userName, password) => { + await new Login(getPage()).loginWithCredentials(userName, password); +}); + +Then('I should see the error message {string}', async (expectedMessage) => { + await new Login(getPage()).validateErrorMessage(expectedMessage); +}); + +Then('I will logout', async () => { + await new Menu(getPage()).logout(); +}); + +Then('I should see the login page', async () => { + await new Menu(getPage()).validateLoginPage(); }); \ No newline at end of file diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb98..a2ba7492 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -4,4 +4,12 @@ import { Product } from '../pages/product.page'; Then('I will add the backpack to the cart', async () => { await new Product(getPage()).addBackPackToCart(); -}); \ No newline at end of file +}); + +Then('I will sort products by {string}', async (sortOption: string) => { + await new Product(getPage()).sortByPrice(sortOption); +}); + +Then('I should see all items sorted by {string}', async (order) => { + await new Product(getPage()).validateSortedByPrice(order as 'asc' | 'desc'); +}); From 7400f4376110037652849a1e3a0ff64a18b4cdbf Mon Sep 17 00:00:00 2001 From: Nishi Mewada Date: Tue, 21 Jul 2026 15:38:58 -0400 Subject: [PATCH 2/2] Updated Nishi Playwright Cucumber Solution --- features/product.feature | 2 +- features/purchase.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/product.feature b/features/product.feature index d4e3d942..16c390e9 100644 --- a/features/product.feature +++ b/features/product.feature @@ -5,7 +5,7 @@ Feature: Product Feature # Create a datatable to validate the Price (high to low) and Price (low to high) sort options (top-right) using a Scenario Outline - Scenario: Validate product sort by price + Scenario Outline: Validate product sort by price Then I will login as "standard_user" # TODO: Sort the items by - done below Then I will sort products by "" diff --git a/features/purchase.feature b/features/purchase.feature index 2e6069bc..a9647cc2 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -3,7 +3,7 @@ Feature: Purchase Feature Background: Given I open the "https://www.saucedemo.com/" page - Scenario: Validate successful purchase text + Scenario: Validate successful purchase text Then I will login as "standard_user" Then I will add the backpack to the cart # TODO: Select the cart (top-right)- done below