From 74c499a3fd9243fc020a8b1d9f8c75db6130e9d1 Mon Sep 17 00:00:00 2001 From: gregapostle Date: Wed, 11 Mar 2026 22:07:05 -0400 Subject: [PATCH] Completed Playwright Cucumber assessment --- features/login.feature | 9 +++++--- features/product.feature | 15 +++++++------- features/purchase.feature | 23 +++++++++++++-------- pages/login.page.ts | 38 ++++++++++++++++++++-------------- pages/product.page.ts | 31 +++++++++++++++++++++++----- pages/purchase.page.ts | 43 +++++++++++++++++++++++++++++++++++++++ steps/login.steps.ts | 10 ++++++++- steps/product.steps.ts | 14 ++++++++++++- steps/purchase.steps.ts | 30 +++++++++++++++++++++++++++ 9 files changed, 172 insertions(+), 41 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 fb9f1fa..67e00fb 100644 --- a/features/login.feature +++ b/features/login.feature @@ -4,9 +4,12 @@ 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" + 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 login error message "Epic sadface: Sorry, this user has been locked out." + + Scenario: Validate successful login redirects to inventory + Then I will login as 'standard_user' + Then I should be on the products page diff --git a/features/product.feature b/features/product.feature index 8a7ceab..346626e 100644 --- a/features/product.feature +++ b/features/product.feature @@ -3,11 +3,12 @@ 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 sort the products by "" + Then I should see all products sorted by price in "" order + Examples: - # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + | sort | direction | + | Price (low to high) | ascending | + | Price (high to low) | descending | diff --git a/features/purchase.feature b/features/purchase.feature index 2863478..7da080f 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -3,12 +3,17 @@ Feature: Purchase Feature Background: 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 + Scenario: Validate successful purchase text + Then I will login as 'standard_user' + Then I will add the backpack to the cart + Then I open the cart + Then I start the checkout + Then I fill the checkout form with first name "Greg", last name "Tester", and zip code "10001" + Then I continue the checkout + Then I finish the checkout + Then I should see the purchase confirmation text "Thank you for your order!" + + Scenario: Validate cart badge after adding a product + Then I will login as 'standard_user' + Then I will add the backpack to the cart + Then I should see the cart badge count "1" diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614..c4a428c 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -1,26 +1,34 @@ -import { Page } from "@playwright/test" +import { expect, Page } from '@playwright/test'; export class Login { - private readonly page: Page - private readonly password: string = 'secret_sauce' - 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 page: Page; + private readonly defaultPassword = 'secret_sauce'; + private readonly passwordField = '[data-test="password"]'; + private readonly userNameField = '[data-test="username"]'; + private readonly loginButton = '[data-test="login-button"]'; + private readonly loginErrorMessage = '[data-test="error"]'; + private readonly productsTitle = '[data-test="title"]'; constructor(page: Page) { this.page = page; } public async validateTitle(expectedTitle: string) { - const pageTitle = await this.page.title(); - if (pageTitle !== expectedTitle) { - throw new Error(`Expected title to be ${expectedTitle} but found ${pageTitle}`); - } + await expect(this.page).toHaveTitle(expectedTitle); } - public async loginAsUser(userName: string) { - await this.page.locator(this.userNameField).fill(userName) - await this.page.locator(this.passwordField).fill(this.password) - await this.page.locator(this.loginButton).click() + public async loginAsUser(userName: string, password: string = this.defaultPassword) { + await this.page.locator(this.userNameField).fill(userName); + await this.page.locator(this.passwordField).fill(password); + await this.page.locator(this.loginButton).click(); } -} \ No newline at end of file + + public async validateLoginErrorMessage(expectedMessage: string) { + await expect(this.page.locator(this.loginErrorMessage)).toHaveText(expectedMessage); + } + + public async validateProductsPage() { + await expect(this.page).toHaveURL(/\/inventory\.html$/); + await expect(this.page.locator(this.productsTitle)).toHaveText('Products'); + } +} diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1..5e41c93 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -1,14 +1,35 @@ -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 page: Page; + private readonly addToCart = '[data-test="add-to-cart-sauce-labs-backpack"]'; + private readonly sortDropdown = '[data-test="product-sort-container"]'; + private readonly inventoryPrices = '[data-test="inventory-item-price"]'; + private readonly cartBadge = '[data-test="shopping-cart-badge"]'; constructor(page: Page) { this.page = page; } public async addBackPackToCart() { - await this.page.locator(this.addToCart).click() + await this.page.locator(this.addToCart).click(); } -} \ No newline at end of file + + public async sortProductsBy(sortOption: string) { + await this.page.locator(this.sortDropdown).selectOption({ label: sortOption }); + } + + public async validateProductsSortedByPrice(direction: string) { + const priceTexts = await this.page.locator(this.inventoryPrices).allTextContents(); + const prices = priceTexts.map((price) => Number(price.replace('$', '').trim())); + const expectedPrices = [...prices].sort((first, second) => + direction === 'ascending' ? first - second : second - first + ); + + expect(prices).toEqual(expectedPrices); + } + + public async validateCartBadgeCount(expectedCount: string) { + await expect(this.page.locator(this.cartBadge)).toHaveText(expectedCount); + } +} diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 0000000..31e523b --- /dev/null +++ b/pages/purchase.page.ts @@ -0,0 +1,43 @@ +import { expect, Page } from '@playwright/test'; + +export class Purchase { + private readonly page: Page; + private readonly cartLink = '[data-test="shopping-cart-link"]'; + private readonly checkoutButton = '[data-test="checkout"]'; + private readonly firstNameField = '[data-test="firstName"]'; + private readonly lastNameField = '[data-test="lastName"]'; + private readonly postalCodeField = '[data-test="postalCode"]'; + private readonly continueButton = '[data-test="continue"]'; + private readonly finishButton = '[data-test="finish"]'; + private readonly confirmationHeader = '[data-test="complete-header"]'; + + constructor(page: Page) { + this.page = page; + } + + public async openCart() { + await this.page.locator(this.cartLink).click(); + } + + public async startCheckout() { + await this.page.locator(this.checkoutButton).click(); + } + + public async fillCheckoutInformation(firstName: string, lastName: string, zipCode: string) { + await this.page.locator(this.firstNameField).fill(firstName); + await this.page.locator(this.lastNameField).fill(lastName); + await this.page.locator(this.postalCodeField).fill(zipCode); + } + + public async continueCheckout() { + await this.page.locator(this.continueButton).click(); + } + + public async finishCheckout() { + await this.page.locator(this.finishButton).click(); + } + + public async validatePurchaseConfirmationText(expectedText: string) { + await expect(this.page.locator(this.confirmationHeader)).toHaveText(expectedText); + } +} diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d8..ff8fdfb 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -8,4 +8,12 @@ Then('I should see the title {string}', async (expectedTitle) => { Then('I will login as {string}', async (userName) => { await new Login(getPage()).loginAsUser(userName); -}); \ No newline at end of file +}); + +Then('I should see the login error message {string}', async (expectedMessage) => { + await new Login(getPage()).validateLoginErrorMessage(expectedMessage); +}); + +Then('I should be on the products page', async () => { + await new Login(getPage()).validateProductsPage(); +}); diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb9..8dcce10 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -4,4 +4,16 @@ 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 sort the products by {string}', async (sortOption) => { + await new Product(getPage()).sortProductsBy(sortOption); +}); + +Then('I should see all products sorted by price in {string} order', async (direction) => { + await new Product(getPage()).validateProductsSortedByPrice(direction); +}); + +Then('I should see the cart badge count {string}', async (expectedCount) => { + await new Product(getPage()).validateCartBadgeCount(expectedCount); +}); diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 0000000..9f280a7 --- /dev/null +++ b/steps/purchase.steps.ts @@ -0,0 +1,30 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Purchase } from '../pages/purchase.page'; + +Then('I open the cart', async () => { + await new Purchase(getPage()).openCart(); +}); + +Then('I start the checkout', async () => { + await new Purchase(getPage()).startCheckout(); +}); + +Then( + 'I fill the checkout form with first name {string}, last name {string}, and zip code {string}', + async (firstName, lastName, zipCode) => { + await new Purchase(getPage()).fillCheckoutInformation(firstName, lastName, zipCode); + } +); + +Then('I continue the checkout', async () => { + await new Purchase(getPage()).continueCheckout(); +}); + +Then('I finish the checkout', async () => { + await new Purchase(getPage()).finishCheckout(); +}); + +Then('I should see the purchase confirmation text {string}', async (expectedText) => { + await new Purchase(getPage()).validatePurchaseConfirmationText(expectedText); +});