From 90c8addc2fb072026adc59c09c623dccbed09849 Mon Sep 17 00:00:00 2001 From: AshwithaReddy1 Date: Fri, 20 Feb 2026 23:03:08 -0500 Subject: [PATCH 1/2] Completed all 3 tasks with additional test coverage --- features/cart.feature | 15 +++++++++++++ features/login.feature | 8 ++++--- features/product.feature | 6 ++++- features/purchase.feature | 8 ++++++- package-lock.json | 2 +- pages/cart.page.ts | 29 ++++++++++++++++++++++++ pages/login.page.ts | 7 ++++++ pages/product.page.ts | 22 +++++++++++++++++++ pages/purchase.page.ts | 46 +++++++++++++++++++++++++++++++++++++++ steps/cart.steps.ts | 15 +++++++++++++ steps/common.steps.ts | 11 ++++++++-- steps/login.steps.ts | 9 +++++--- steps/product.steps.ts | 10 ++++++++- steps/purchase.steps.ts | 27 +++++++++++++++++++++++ 14 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 features/cart.feature create mode 100644 pages/cart.page.ts create mode 100644 pages/purchase.page.ts create mode 100644 steps/cart.steps.ts create mode 100644 steps/purchase.steps.ts diff --git a/features/cart.feature b/features/cart.feature new file mode 100644 index 0000000..60cdee9 --- /dev/null +++ b/features/cart.feature @@ -0,0 +1,15 @@ +Feature: Cart Feature + + Background: + Given I open the "https://www.saucedemo.com/" page + + Scenario: Validate cart item count after adding product + Then I will login as 'standard_user' + Then I will add the backpack to the cart + Then I should see "1" item in the cart badge + + Scenario: Validate cart is empty after removing product + Then I will login as 'standard_user' + Then I will add the backpack to the cart + Then I will remove the backpack from the cart + Then the cart badge should not be visible diff --git a/features/login.feature b/features/login.feature index fb9f1fa..d71ab43 100644 --- a/features/login.feature +++ b/features/login.feature @@ -4,9 +4,11 @@ Feature: Login Feature Given I open the "https://www.saucedemo.com/" page Scenario: Validate the login page title - # TODO: Fix this failing scenario - Then I should see the title "Labs Swag" + # TODO: Fix this failing scenario -fixed + Then I should see the title "Swag Labs" + Scenario: Validate login error message Then I will login as 'locked_out_user' - # TODO: Add a step to validate the error message received \ No newline at end of file + # TODO: Add a step to validate the error message received-added + Then I should see the error message "Epic sadface: Sorry, this user has been locked out." \ No newline at end of file diff --git a/features/product.feature b/features/product.feature index 8a7ceab..8932a79 100644 --- a/features/product.feature +++ b/features/product.feature @@ -7,7 +7,11 @@ Feature: Product Feature Scenario Outline: Validate product sort by price Then I will login as 'standard_user' # TODO: Sort the items by + Then I will sort the items by "" # TODO: Validate all 6 items are sorted correctly by price + Then I should see all 6 items sorted by price "" Examples: # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + | 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 2863478..c3b75fb 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -7,8 +7,14 @@ Feature: Purchase Feature Then I will login as 'standard_user' Then I will add the backpack to the cart # TODO: Select the cart (top-right) + Then I will select the cart # TODO: Select Checkout + Then I will select checkout # TODO: Fill in the First Name, Last Name, and Zip/Postal Code + Then I will fill in checkout info with first name "Ashwitha" last name "Vollem" and zip "12345" # TODO: Select Continue + Then I will select continue # TODO: Select Finish - # TODO: Validate the text 'Thank you for your order!' \ No newline at end of file + Then I will select finish + # TODO: Validate the text 'Thank you for your order!' + Then I should see the order confirmation text "Thank you for your order!" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b90d7c6..b5c47aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "Playwright-Project", + "name": "Playwright-Cucumber-Exercise", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/pages/cart.page.ts b/pages/cart.page.ts new file mode 100644 index 0000000..4a1bcf0 --- /dev/null +++ b/pages/cart.page.ts @@ -0,0 +1,29 @@ +import { Page } from "@playwright/test" + +export class Cart { + private readonly page: Page + private readonly cartBadge: string = '.shopping_cart_badge' + private readonly removeBackpack: string = 'button[id="remove-sauce-labs-backpack"]' + + constructor(page: Page) { + this.page = page; + } + + public async validateCartBadgeCount(expectedCount: string) { + const badgeText = await this.page.locator(this.cartBadge).textContent(); + if (badgeText?.trim() !== expectedCount) { + throw new Error(`Expected cart badge to show "${expectedCount}" but found "${badgeText?.trim()}"`); + } + } + + public async removeBackpackFromCart() { + await this.page.locator(this.removeBackpack).click(); + } + + public async validateCartBadgeNotVisible() { + const isVisible = await this.page.locator(this.cartBadge).isVisible(); + if (isVisible) { + throw new Error('Expected cart badge to not be visible but it is visible'); + } + } +} diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614..a9d8cad 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -6,6 +6,7 @@ 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"]' constructor(page: Page) { this.page = page; @@ -23,4 +24,10 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } + public async validateErrorMessage(expectedMessage: string) { + const errorText = await this.page.locator(this.errorMessage).textContent(); + if (errorText?.trim() !== expectedMessage) { + throw new Error(`Expected error message to be "${expectedMessage}" but found "${errorText?.trim()}"`); + } + } } \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1..bdf2515 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -3,6 +3,8 @@ import { 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 sortDropdown: string = 'select.product_sort_container' + private readonly itemPrices: string = '.inventory_item_price' constructor(page: Page) { this.page = page; @@ -11,4 +13,24 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async sortItemsBy(sortOption: string) { + await this.page.locator(this.sortDropdown).selectOption({ label: sortOption }); + } + + public async validateItemsSortedByPrice(order: string) { + const priceElements = await this.page.locator(this.itemPrices).allTextContents(); + if (priceElements.length !== 6) { + throw new Error(`Expected 6 items but found ${priceElements.length}`); + } + const prices = priceElements.map(p => parseFloat(p.replace('$', ''))); + for (let i = 0; i < prices.length - 1; i++) { + if (order === 'asc' && prices[i] > prices[i + 1]) { + throw new Error(`Items are not sorted by price (low to high). Found ${prices[i]} before ${prices[i + 1]}`); + } + if (order === 'desc' && prices[i] < prices[i + 1]) { + throw new Error(`Items are not sorted by price (high to low). Found ${prices[i]} before ${prices[i + 1]}`); + } + } + } } \ No newline at end of file diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 0000000..b345060 --- /dev/null +++ b/pages/purchase.page.ts @@ -0,0 +1,46 @@ +import { Page } from "@playwright/test" + +export class Purchase { + private readonly page: Page + private readonly cartIcon: string = 'a.shopping_cart_link' + private readonly checkoutButton: string = 'button[id="checkout"]' + private readonly firstNameField: string = 'input[id="first-name"]' + private readonly lastNameField: string = 'input[id="last-name"]' + private readonly zipCodeField: string = 'input[id="postal-code"]' + private readonly continueButton: string = 'input[id="continue"]' + private readonly finishButton: string = 'button[id="finish"]' + private readonly confirmationText: string = '.complete-header' + + constructor(page: Page) { + this.page = page; + } + + public async selectCart() { + await this.page.locator(this.cartIcon).click(); + } + + public async selectCheckout() { + await this.page.locator(this.checkoutButton).click(); + } + + public async fillCheckoutInfo(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.zipCodeField).fill(zip); + } + + public async selectContinue() { + await this.page.locator(this.continueButton).click(); + } + + public async selectFinish() { + await this.page.locator(this.finishButton).click(); + } + + public async validateConfirmationText(expectedText: string) { + const text = await this.page.locator(this.confirmationText).textContent(); + if (text?.trim() !== expectedText) { + throw new Error(`Expected confirmation text to be "${expectedText}" but found "${text?.trim()}"`); + } + } +} diff --git a/steps/cart.steps.ts b/steps/cart.steps.ts new file mode 100644 index 0000000..88217ef --- /dev/null +++ b/steps/cart.steps.ts @@ -0,0 +1,15 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Cart } from '../pages/cart.page'; + +Then('I should see {string} item in the cart badge', async (expectedCount) => { + await new Cart(getPage()).validateCartBadgeCount(expectedCount); +}); + +Then('I will remove the backpack from the cart', async () => { + await new Cart(getPage()).removeBackpackFromCart(); +}); + +Then('the cart badge should not be visible', async () => { + await new Cart(getPage()).validateCartBadgeNotVisible(); +}); diff --git a/steps/common.steps.ts b/steps/common.steps.ts index c4bee79..b7d3462 100644 --- a/steps/common.steps.ts +++ b/steps/common.steps.ts @@ -1,6 +1,13 @@ -import { Given } from "@cucumber/cucumber"; +import { Given, Then} from "@cucumber/cucumber"; import { getPage } from "../playwrightUtilities"; Given('I open the {string} page', async (url) => { await getPage().goto(url); - }); \ No newline at end of file + }); + +Then('I should see the title {string}', async (expectedTitle: string) => { + const pageTitle = await getPage().title(); + if (pageTitle !== expectedTitle) { + throw new Error(`Expected title to be "${expectedTitle}" but found "${pageTitle}"`); + } +}); \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d8..fca88c4 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -2,10 +2,13 @@ import { Then } from '@cucumber/cucumber'; import { getPage } from '../playwrightUtilities'; import { Login } from '../pages/login.page'; -Then('I should see the title {string}', async (expectedTitle) => { - await new Login(getPage()).validateTitle(expectedTitle); -}); +// Then('I should see the title {string}', async (expectedTitle) => { +// await new Login(getPage()).validateTitle(expectedTitle); +// }); Then('I will login as {string}', async (userName) => { await new Login(getPage()).loginAsUser(userName); +}); +Then('I should see the error message {string}', async (expectedMessage) => { + await new Login(getPage()).validateErrorMessage(expectedMessage); }); \ No newline at end of file diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb9..3ea9172 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 the items by {string}', async (sortOption) => { + await new Product(getPage()).sortItemsBy(sortOption); +}); + +Then('I should see all 6 items sorted by price {string}', async (order) => { + await new Product(getPage()).validateItemsSortedByPrice(order); +}); diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 0000000..ca60e56 --- /dev/null +++ b/steps/purchase.steps.ts @@ -0,0 +1,27 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Purchase } from '../pages/purchase.page'; + +Then('I will select the cart', async () => { + await new Purchase(getPage()).selectCart(); +}); + +Then('I will select checkout', async () => { + await new Purchase(getPage()).selectCheckout(); +}); + +Then('I will fill in checkout info with first name {string} last name {string} and zip {string}', async (firstName, lastName, zip) => { + await new Purchase(getPage()).fillCheckoutInfo(firstName, lastName, zip); +}); + +Then('I will select continue', async () => { + await new Purchase(getPage()).selectContinue(); +}); + +Then('I will select finish', async () => { + await new Purchase(getPage()).selectFinish(); +}); + +Then('I should see the order confirmation text {string}', async (expectedText) => { + await new Purchase(getPage()).validateConfirmationText(expectedText); +}); From 70605f6ea70c372ab13e97218bf46e41ab05aa83 Mon Sep 17 00:00:00 2001 From: AshwithaReddy1 Date: Fri, 20 Feb 2026 23:48:12 -0500 Subject: [PATCH 2/2] updated --- pages/cart.page.ts | 4 ++-- pages/product.page.ts | 4 ++-- pages/purchase.page.ts | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pages/cart.page.ts b/pages/cart.page.ts index 4a1bcf0..29e8164 100644 --- a/pages/cart.page.ts +++ b/pages/cart.page.ts @@ -2,8 +2,8 @@ import { Page } from "@playwright/test" export class Cart { private readonly page: Page - private readonly cartBadge: string = '.shopping_cart_badge' - private readonly removeBackpack: string = 'button[id="remove-sauce-labs-backpack"]' + private readonly cartBadge: string = '[data-test="shopping-cart-badge"]' + private readonly removeBackpack: string = '[data-test="remove-sauce-labs-backpack"]'; constructor(page: Page) { this.page = page; diff --git a/pages/product.page.ts b/pages/product.page.ts index bdf2515..1f20597 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -3,8 +3,8 @@ import { 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 sortDropdown: string = 'select.product_sort_container' - private readonly itemPrices: string = '.inventory_item_price' + private readonly sortDropdown: string = '[data-test="product-sort-container"]'; + private readonly itemPrices: string = '[data-test="inventory-item-price"]'; constructor(page: Page) { this.page = page; diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts index b345060..a501bb4 100644 --- a/pages/purchase.page.ts +++ b/pages/purchase.page.ts @@ -2,14 +2,14 @@ import { Page } from "@playwright/test" export class Purchase { private readonly page: Page - private readonly cartIcon: string = 'a.shopping_cart_link' - private readonly checkoutButton: string = 'button[id="checkout"]' - private readonly firstNameField: string = 'input[id="first-name"]' - private readonly lastNameField: string = 'input[id="last-name"]' - private readonly zipCodeField: string = 'input[id="postal-code"]' - private readonly continueButton: string = 'input[id="continue"]' - private readonly finishButton: string = 'button[id="finish"]' - private readonly confirmationText: string = '.complete-header' + private readonly cartIcon: string = '[data-test="shopping-cart-link"]' + private readonly checkoutButton: string = '[data-test="checkout"]' + private readonly firstNameField: string = '[data-test="firstName"]' + private readonly lastNameField: string = '[data-test="lastName"]' + private readonly zipCodeField: string = '[data-test="postalCode"]' + private readonly continueButton: string = '[data-test="continue"]' + private readonly finishButton: string = '[data-test="finish"]' + private readonly confirmationText: string = '[data-test="complete-header"]' constructor(page: Page) { this.page = page;