From 94c4fd2af5bbd4000798295a92e33a4d078123f5 Mon Sep 17 00:00:00 2001 From: sazfar26 Date: Wed, 15 Jul 2026 14:55:13 -0400 Subject: [PATCH] sufian-azfar-changes --- features/login.feature | 4 +-- features/product.feature | 21 ++++++++++----- features/purchase.feature | 12 ++++----- package-lock.json | 2 +- pages/login.page.ts | 12 +++++++-- pages/product.page.ts | 55 ++++++++++++++++++++++++++++++++++++++ pages/purchase.page.ts | 56 +++++++++++++++++++++++++++++++++++++++ steps/login.steps.ts | 4 +++ steps/product.steps.ts | 22 +++++++++++++++ steps/purchase.steps.ts | 27 +++++++++++++++++++ 10 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 pages/purchase.page.ts create mode 100644 steps/purchase.steps.ts diff --git a/features/login.feature b/features/login.feature index fb9f1fa5..4e205110 100644 --- a/features/login.feature +++ b/features/login.feature @@ -5,8 +5,8 @@ Feature: Login Feature Scenario: Validate the login page title # TODO: Fix this failing scenario - Then I should see the title "Labs Swag" + 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 + 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 8a7ceab9..d12ec94f 100644 --- a/features/product.feature +++ b/features/product.feature @@ -3,11 +3,18 @@ Feature: Product Feature Background: Given I open the "https://www.saucedemo.com/" page - # Create a datatable to validate the Price (high to low) and Price (low to high) sort options (top-right) using a Scenario Outline - Scenario Outline: Validate product sort by price - Then I will login as 'standard_user' - # TODO: Sort the items by - # TODO: Validate all 6 items are sorted correctly by price + Scenario Outline: Validate product sort by price + Then I will login as 'standard_user' + Then I will sort products by + Then I should validate all products are sorted by + Examples: - # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + | sort | + | Price (low to high) | + | Price (high to low) | + | Name (A to Z) | + | Name (Z to A) | + + Scenario: Validate add backpack to cart + Then I will login as 'standard_user' + Then I will add the backpack to the cart \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..2dba4670 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -6,9 +6,9 @@ Feature: Purchase Feature 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 open the cart + Then I will checkout + Then I will fill checkout details with "Sufia" "Tester" "12345" + Then I will continue checkout + Then I will finish checkout + Then I should see the purchase confirmation "Thank you for your order!" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b90d7c6c..b5c47aa5 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/login.page.ts b/pages/login.page.ts index 5a01614b..0695c67b 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -8,9 +8,9 @@ export class Login { private readonly loginButton: string = 'input[id="login-button"]' constructor(page: Page) { - this.page = page; + this.page = page; } - + public async validateTitle(expectedTitle: string) { const pageTitle = await this.page.title(); if (pageTitle !== expectedTitle) { @@ -23,4 +23,12 @@ 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 errorElement = await this.page.locator('[data-test="error"]'); + const actualMessage = await errorElement.textContent(); + if (!actualMessage?.includes(expectedMessage)) { + throw new Error(`Expected error message "${expectedMessage}" but found "${actualMessage}"`); + } + } } \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..57e52900 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -3,6 +3,9 @@ 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 = '[data-testid="product-sort-container"]' + private readonly productPrices: string = '[data-testid="inventory-item-price"]' + private readonly productNames: string = '[data-testid="inventory-item-name"]' constructor(page: Page) { this.page = page; @@ -11,4 +14,56 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async sortProductsBy(sortOption: string) { + await this.page.locator(this.sortDropdown).selectOption(sortOption.toLowerCase().replace(/\s+/g, '_')) + // Wait for products to be resorted + await this.page.waitForTimeout(500) + } + + public async getProductPrices(): Promise { + const priceElements = await this.page.locator(this.productPrices).allTextContents() + return priceElements.map(price => parseFloat(price.replace('$', ''))) + } + + public async getProductNames(): Promise { + return await this.page.locator(this.productNames).allTextContents() + } + + public async validateProductsSortedByPrice(sortOrder: 'asc' | 'desc') { + const prices = await this.getProductPrices() + const sortedPrices = [...prices].sort((a, b) => sortOrder === 'asc' ? a - b : b - a) + + for (let i = 0; i < prices.length; i++) { + if (prices[i] !== sortedPrices[i]) { + throw new Error( + `Products not sorted correctly by price (${sortOrder}). ` + + `Expected: [${sortedPrices}] but got: [${prices}]` + ) + } + } + } + + public async validateProductsSortedByName(sortOrder: 'asc' | 'desc') { + const names = await this.getProductNames() + const sortedNames = [...names].sort((a, b) => sortOrder === 'asc' ? a.localeCompare(b) : b.localeCompare(a)) + + for (let i = 0; i < names.length; i++) { + if (names[i] !== sortedNames[i]) { + throw new Error( + `Products not sorted correctly by name (${sortOrder}). ` + + `Expected: [${sortedNames}] but got: [${names}]` + ) + } + } + } + + public async validateProductCountIs(expectedCount: number) { + const actualCount = await this.page.locator(this.productNames).count() + if (actualCount !== expectedCount) { + throw new Error( + `Expected ${expectedCount} products but found ${actualCount}` + ) + } + } } \ No newline at end of file diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 00000000..19f7e9e6 --- /dev/null +++ b/pages/purchase.page.ts @@ -0,0 +1,56 @@ +import { Page } from "@playwright/test" + +export class Purchase { + private readonly page: Page + private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + private readonly cartLink: 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 postalField: string = 'input[id="postal-code"]' + private readonly continueButton: string = 'input[id="continue"]' + private readonly finishButton: string = 'button[id="finish"]' + private readonly completeHeader: string = '.complete-header' + + constructor(page: Page) { + this.page = page; + } + + public async addBackpackToCart() { + await this.page.locator(this.addToCart).click() + } + + public async openCart() { + await this.page.locator(this.cartLink).click() + } + + public async checkout() { + await this.page.locator(this.checkoutButton).click() + } + + public async fillCheckoutDetails(firstName: string, lastName: string, postalCode: string) { + await this.page.locator(this.firstNameField).fill(firstName) + await this.page.locator(this.lastNameField).fill(lastName) + await this.page.locator(this.postalField).fill(postalCode) + } + + public async continueCheckout() { + await this.page.locator(this.continueButton).click() + } + + public async finishCheckout() { + await this.page.locator(this.finishButton).click() + } + + public async getConfirmationHeader(): Promise { + const t = await this.page.locator(this.completeHeader).textContent() + return t ? t.trim() : '' + } + + public async validateConfirmation(expected: string) { + const actual = await this.getConfirmationHeader() + if (!actual.toLowerCase().includes(expected.toLowerCase())) { + throw new Error(`Expected confirmation to include "${expected}" but found "${actual}"`) + } + } +} diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..0f146e44 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -8,4 +8,8 @@ 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 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 bb52fb98..c81a9f97 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -4,4 +4,26 @@ import { Product } from '../pages/product.page'; Then('I will add the backpack to the cart', async () => { await new Product(getPage()).addBackPackToCart(); +}); + +Then('I will sort products by {string}', async (sortOption: string) => { + await new Product(getPage()).sortProductsBy(sortOption); +}); + +Then('I should validate all products are sorted by {string}', async (sortOption: string) => { + const product = new Product(getPage()); + + // Validate 6 products are present + await product.validateProductCountIs(6); + + // Validate sorting based on the option + if (sortOption.toLowerCase().includes('price') && sortOption.toLowerCase().includes('low')) { + await product.validateProductsSortedByPrice('asc'); + } else if (sortOption.toLowerCase().includes('price') && sortOption.toLowerCase().includes('high')) { + await product.validateProductsSortedByPrice('desc'); + } else if (sortOption.toLowerCase().includes('name') && sortOption.toLowerCase().includes('z to a')) { + await product.validateProductsSortedByName('desc'); + } else if (sortOption.toLowerCase().includes('name') && sortOption.toLowerCase().includes('a to z')) { + await product.validateProductsSortedByName('asc'); + } }); \ No newline at end of file diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 00000000..5e811cfd --- /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 open the cart', async () => { + await new Purchase(getPage()).openCart(); +}); + +Then('I will checkout', async () => { + await new Purchase(getPage()).checkout(); +}); + +Then('I will fill checkout details with {string} {string} {string}', async (firstName: string, lastName: string, postalCode: string) => { + await new Purchase(getPage()).fillCheckoutDetails(firstName, lastName, postalCode); +}); + +Then('I will continue checkout', async () => { + await new Purchase(getPage()).continueCheckout(); +}); + +Then('I will finish checkout', async () => { + await new Purchase(getPage()).finishCheckout(); +}); + +Then('I should see the purchase confirmation {string}', async (expectedText: string) => { + await new Purchase(getPage()).validateConfirmation(expectedText); +});