From 67d637ea4d388f999b10e6dfa84147348d785cbe Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 14:10:29 -0400 Subject: [PATCH 01/10] NPM init --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": { From 5f0d374bfd51c3740248a87db206e09bbae716b9 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 14:10:49 -0400 Subject: [PATCH 02/10] Task 1 - Updated expected title --- features/login.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/login.feature b/features/login.feature index fb9f1fa5..17cddd39 100644 --- a/features/login.feature +++ b/features/login.feature @@ -5,7 +5,7 @@ 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' From 0168181cee070adc21b3bd3e1213cd3d50565d49 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 14:18:51 -0400 Subject: [PATCH 03/10] Task 2 - Validated error message is visible on login. --- features/login.feature | 3 ++- pages/login.page.ts | 11 ++++++++++- steps/login.steps.ts | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/features/login.feature b/features/login.feature index 17cddd39..4edbc0a2 100644 --- a/features/login.feature +++ b/features/login.feature @@ -9,4 +9,5 @@ Feature: Login Feature 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 container + Then The error message container should contain the text "Epic sadface: Sorry, this user has been locked out." \ No newline at end of file diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614b..5209ef2e 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -1,4 +1,4 @@ -import { Page } from "@playwright/test" +import { expect, Page } from "@playwright/test" export class Login { private readonly page: Page @@ -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 errorContainer: string = 'div.error-message-container.error' constructor(page: Page) { this.page = page; @@ -23,4 +24,12 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } + + public async validateErrorExists() { + await expect(this.page.locator(this.errorContainer)).toBeVisible(); + } + + public async validateErrorMessage(expectedMessage: string) { + await expect(this.page.locator(this.errorContainer)).toContainText(expectedMessage); + } } \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..baf5347a 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); +}); + +Then('I should see the error message container', async () => { + await new Login(getPage()).validateErrorExists(); +}); + +Then('The error message container should contain the text {string}', async (expectedMessage) => { + await new Login(getPage()).validateErrorMessage(expectedMessage); }); \ No newline at end of file From 6be7be0467b5963a560c48fc11247ee2ef9d092f Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 17:00:13 -0400 Subject: [PATCH 04/10] Task 3 - Validate successful purchase text --- features/purchase.feature | 10 ++++- pages/product.page.ts | 79 ++++++++++++++++++++++++++++++++++----- steps/product.steps.ts | 45 +++++++++++++++++++--- 3 files changed, 118 insertions(+), 16 deletions(-) diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..6ec6943a 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -7,8 +7,16 @@ 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 click the cart button # TODO: Select Checkout + Then I will click the checkout button # TODO: Fill in the First Name, Last Name, and Zip/Postal Code + Then I will fill the checkout form as "Test" "User", "12345" # TODO: Select Continue + Then I will click the continue button # TODO: Select Finish - # TODO: Validate the text 'Thank you for your order!' \ No newline at end of file + Then I will click the finish button + # TODO: Validate the text 'Thank you for your order!' + Then The checkout success container should be visible + And The checkout success header should read 'Thank you for your order!' + And The checkout success text should read 'Your order has been dispatched, and will arrive just as fast as the pony can get there!' \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..a75e334d 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -1,14 +1,73 @@ -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: string = + 'button[id="add-to-cart-sauce-labs-backpack"]'; + private readonly cartButton: string = '[data-test="shopping-cart-link"]'; + private readonly checkoutButton: string = '[data-test="checkout"]'; + private readonly checkoutForm = { + firstNameInput: '[data-test="firstName"]', + lastNameInput: '[data-test="lastName"]', + zipCodeInput: '[data-test="postalCode"]', + }; + private readonly continueButton: string = '[data-test="continue"]'; + private readonly finishButton: string = '[data-test="finish"]'; + private readonly checkoutCompleteContainer: string = + '[data-test="checkout-complete-container"]'; + private readonly checkoutCompleteHeader: string = + '[data-test="complete-header"]'; + private readonly checkoutCompleteText: string = '[data-test="complete-text"]'; - constructor(page: Page) { - this.page = page; - } + constructor(page: Page) { + this.page = page; + } - public async addBackPackToCart() { - await this.page.locator(this.addToCart).click() - } -} \ No newline at end of file + public async addBackPackToCart() { + await this.page.locator(this.addToCart).click(); + } + + public async clickCartButton() { + await this.page.locator(this.cartButton).click(); + } + + public async clickCheckoutButton() { + await this.page.locator(this.checkoutButton).click(); + } + + public async fillCheckoutForm( + firstName: string, + lastName: string, + zipCode: string, + ) { + await this.page.locator(this.checkoutForm.firstNameInput).fill(firstName); + await this.page.locator(this.checkoutForm.lastNameInput).fill(lastName); + await this.page.locator(this.checkoutForm.zipCodeInput).fill(zipCode); + } + + public async clickContinueButton() { + await this.page.locator(this.continueButton).click(); + } + + public async clickFinishButton() { + await this.page.locator(this.finishButton).click(); + } + + public async assertSuccessContainerVisible() { + await expect( + this.page.locator(this.checkoutCompleteContainer), + ).toBeVisible(); + } + + public async assertSuccessHeaderCorrect(expectedText: string) { + await expect( + this.page.locator(this.checkoutCompleteHeader), + ).toHaveText(expectedText); + } + + public async assertSuccessTextCorrect(expectedText: string) { + await expect( + this.page.locator(this.checkoutCompleteText), + ).toHaveText(expectedText); + } +} diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb98..55f20244 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -1,7 +1,42 @@ -import { Then } from '@cucumber/cucumber'; -import { getPage } from '../playwrightUtilities'; -import { Product } from '../pages/product.page'; +import { Then } from "@cucumber/cucumber"; +import { getPage } from "../playwrightUtilities"; +import { Product } from "../pages/product.page"; -Then('I will add the backpack to the cart', async () => { +Then("I will add the backpack to the cart", async () => { await new Product(getPage()).addBackPackToCart(); -}); \ No newline at end of file +}); + +Then("I will click the cart button", async () => { + await new Product(getPage()).clickCartButton(); +}); + +Then("I will click the checkout button", async () => { + await new Product(getPage()).clickCheckoutButton(); +}); + +Then( + "I will fill the checkout form as {string} {string}, {string}", + async (firstName, lastName, zipCode) => { + await new Product(getPage()).fillCheckoutForm(firstName, lastName, `${zipCode}`); + }, +); + +Then("I will click the continue button", async () => { + await new Product(getPage()).clickContinueButton(); +}); + +Then("I will click the finish button", async () => { + await new Product(getPage()).clickFinishButton(); +}); + +Then("The checkout success container should be visible", async () => { + await new Product(getPage()).assertSuccessContainerVisible(); +}); + +Then("The checkout success header should read {string}", async (expectedText) => { + await new Product(getPage()).assertSuccessHeaderCorrect(expectedText); +}); + +Then("The checkout success text should read {string}", async (expectedText) => { + await new Product(getPage()).assertSuccessTextCorrect(expectedText); +}); From 4fd8fb245a5b2d2ebeed87dc03ad0c56c309a880 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 17:08:04 -0400 Subject: [PATCH 05/10] Task 3A - Refactored steps/page to its own purchase page/steps classes --- pages/product.page.ts | 7 ++++ pages/purchase.page.ts | 74 +++++++++++++++++++++++++++++++++++++++++ steps/purchase.steps.ts | 42 +++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 pages/purchase.page.ts create mode 100644 steps/purchase.steps.ts diff --git a/pages/product.page.ts b/pages/product.page.ts index a75e334d..5bcb541b 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -2,6 +2,7 @@ import { expect, Page } from "@playwright/test"; export class Product { private readonly page: Page; + // ================= CHECKOUT LOCATORS ================= private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]'; private readonly cartButton: string = '[data-test="shopping-cart-link"]'; @@ -19,10 +20,13 @@ export class Product { '[data-test="complete-header"]'; private readonly checkoutCompleteText: string = '[data-test="complete-text"]'; + // ================= PRODUCT LOCATORS ================= + constructor(page: Page) { this.page = page; } + // ================= CHECKOUT FUNCTIONS ================= public async addBackPackToCart() { await this.page.locator(this.addToCart).click(); } @@ -70,4 +74,7 @@ export class Product { this.page.locator(this.checkoutCompleteText), ).toHaveText(expectedText); } + + // ================= PRODUCT FUNCTIONS ================= + } diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 00000000..e87efbca --- /dev/null +++ b/pages/purchase.page.ts @@ -0,0 +1,74 @@ +import { expect, 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 cartButton: string = '[data-test="shopping-cart-link"]'; + private readonly checkoutButton: string = '[data-test="checkout"]'; + private readonly checkoutForm = { + firstNameInput: '[data-test="firstName"]', + lastNameInput: '[data-test="lastName"]', + zipCodeInput: '[data-test="postalCode"]', + }; + private readonly continueButton: string = '[data-test="continue"]'; + private readonly finishButton: string = '[data-test="finish"]'; + private readonly checkoutCompleteContainer: string = + '[data-test="checkout-complete-container"]'; + private readonly checkoutCompleteHeader: string = + '[data-test="complete-header"]'; + private readonly checkoutCompleteText: string = '[data-test="complete-text"]'; + + constructor(page: Page) { + this.page = page; + } + + public async addBackPackToCart() { + await this.page.locator(this.addToCart).click(); + } + + public async clickCartButton() { + await this.page.locator(this.cartButton).click(); + } + + public async clickCheckoutButton() { + await this.page.locator(this.checkoutButton).click(); + } + + public async fillCheckoutForm( + firstName: string, + lastName: string, + zipCode: string, + ) { + await this.page.locator(this.checkoutForm.firstNameInput).fill(firstName); + await this.page.locator(this.checkoutForm.lastNameInput).fill(lastName); + await this.page.locator(this.checkoutForm.zipCodeInput).fill(zipCode); + } + + public async clickContinueButton() { + await this.page.locator(this.continueButton).click(); + } + + public async clickFinishButton() { + await this.page.locator(this.finishButton).click(); + } + + public async assertSuccessContainerVisible() { + await expect( + this.page.locator(this.checkoutCompleteContainer), + ).toBeVisible(); + } + + public async assertSuccessHeaderCorrect(expectedText: string) { + await expect( + this.page.locator(this.checkoutCompleteHeader), + ).toHaveText(expectedText); + } + + public async assertSuccessTextCorrect(expectedText: string) { + await expect( + this.page.locator(this.checkoutCompleteText), + ).toHaveText(expectedText); + } + +} \ No newline at end of file diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 00000000..f15018ef --- /dev/null +++ b/steps/purchase.steps.ts @@ -0,0 +1,42 @@ +import { Then } from "@cucumber/cucumber"; +import { getPage } from "../playwrightUtilities"; +import { Purchase } from "../pages/purchase.page"; + +Then("I will add the backpack to the cart", async () => { + await new Purchase(getPage()).addBackPackToCart(); +}); + +Then("I will click the cart button", async () => { + await new Purchase(getPage()).clickCartButton(); +}); + +Then("I will click the checkout button", async () => { + await new Purchase(getPage()).clickCheckoutButton(); +}); + +Then( + "I will fill the checkout form as {string} {string}, {string}", + async (firstName, lastName, zipCode) => { + await new Purchase(getPage()).fillCheckoutForm(firstName, lastName, `${zipCode}`); + }, +); + +Then("I will click the continue button", async () => { + await new Purchase(getPage()).clickContinueButton(); +}); + +Then("I will click the finish button", async () => { + await new Purchase(getPage()).clickFinishButton(); +}); + +Then("The checkout success container should be visible", async () => { + await new Purchase(getPage()).assertSuccessContainerVisible(); +}); + +Then("The checkout success header should read {string}", async (expectedText) => { + await new Purchase(getPage()).assertSuccessHeaderCorrect(expectedText); +}); + +Then("The checkout success text should read {string}", async (expectedText) => { + await new Purchase(getPage()).assertSuccessTextCorrect(expectedText); +}); From 4ab4819d6eae8501861349ecb54f28acd801fb70 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 17:11:44 -0400 Subject: [PATCH 06/10] Task3B - Fixed refactor --- steps/product.steps.ts | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/steps/product.steps.ts b/steps/product.steps.ts index 55f20244..e69de29b 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -1,42 +0,0 @@ -import { Then } from "@cucumber/cucumber"; -import { getPage } from "../playwrightUtilities"; -import { Product } from "../pages/product.page"; - -Then("I will add the backpack to the cart", async () => { - await new Product(getPage()).addBackPackToCart(); -}); - -Then("I will click the cart button", async () => { - await new Product(getPage()).clickCartButton(); -}); - -Then("I will click the checkout button", async () => { - await new Product(getPage()).clickCheckoutButton(); -}); - -Then( - "I will fill the checkout form as {string} {string}, {string}", - async (firstName, lastName, zipCode) => { - await new Product(getPage()).fillCheckoutForm(firstName, lastName, `${zipCode}`); - }, -); - -Then("I will click the continue button", async () => { - await new Product(getPage()).clickContinueButton(); -}); - -Then("I will click the finish button", async () => { - await new Product(getPage()).clickFinishButton(); -}); - -Then("The checkout success container should be visible", async () => { - await new Product(getPage()).assertSuccessContainerVisible(); -}); - -Then("The checkout success header should read {string}", async (expectedText) => { - await new Product(getPage()).assertSuccessHeaderCorrect(expectedText); -}); - -Then("The checkout success text should read {string}", async (expectedText) => { - await new Product(getPage()).assertSuccessTextCorrect(expectedText); -}); From 76d33eece7dc8fbdaafbeb67907d495b1d1a6238 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 17:33:55 -0400 Subject: [PATCH 07/10] Task 4 - Added price sort --- features/product.feature | 11 ++--- pages/product.page.ts | 97 ++++++++++++---------------------------- steps/product.steps.ts | 12 +++++ 3 files changed, 46 insertions(+), 74 deletions(-) diff --git a/features/product.feature b/features/product.feature index 8a7ceab9..5b4529b5 100644 --- a/features/product.feature +++ b/features/product.feature @@ -2,12 +2,13 @@ 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 + And I will sort items by '' + Then The items should be sorted by price '' + 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) | diff --git a/pages/product.page.ts b/pages/product.page.ts index 5bcb541b..eca1d7f0 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -2,79 +2,38 @@ import { expect, Page } from "@playwright/test"; export class Product { private readonly page: Page; - // ================= CHECKOUT LOCATORS ================= - private readonly addToCart: string = - 'button[id="add-to-cart-sauce-labs-backpack"]'; - private readonly cartButton: string = '[data-test="shopping-cart-link"]'; - private readonly checkoutButton: string = '[data-test="checkout"]'; - private readonly checkoutForm = { - firstNameInput: '[data-test="firstName"]', - lastNameInput: '[data-test="lastName"]', - zipCodeInput: '[data-test="postalCode"]', - }; - private readonly continueButton: string = '[data-test="continue"]'; - private readonly finishButton: string = '[data-test="finish"]'; - private readonly checkoutCompleteContainer: string = - '[data-test="checkout-complete-container"]'; - private readonly checkoutCompleteHeader: string = - '[data-test="complete-header"]'; - private readonly checkoutCompleteText: string = '[data-test="complete-text"]'; - - // ================= PRODUCT LOCATORS ================= + private readonly sortDropdown: string = + '[data-test="product-sort-container"]'; + private readonly itemContainer: string = '[data-test="inventory-list"] > div'; + private readonly itemPrice: string = `[data-test="inventory-item-price"]`; constructor(page: Page) { this.page = page; } - // ================= CHECKOUT FUNCTIONS ================= - public async addBackPackToCart() { - await this.page.locator(this.addToCart).click(); - } - - public async clickCartButton() { - await this.page.locator(this.cartButton).click(); - } - - public async clickCheckoutButton() { - await this.page.locator(this.checkoutButton).click(); + public async selectSortOption(sort: string) { + await this.page.locator(this.sortDropdown).selectOption({ label: sort }); + } + + public async assertPricesSorted(sort: string) { + // Get all prices + const prices: number[] = []; + for ( + let i = 0; + i < + (await this.page + .locator(`${this.itemContainer} ${this.itemPrice}`) + .count()); + i++ + ) { + const priceFormatted = parseFloat((await this.page + .locator(`${this.itemContainer} ${this.itemPrice}`).nth(i).innerText()).replace("$", "")); + prices.push(priceFormatted); + } + console.log(`prices:`, prices); + const pricesSorted = [...prices].sort((a, b) => + sort === "Price (low to high)" ? a - b : b - a, + ); + expect(prices).toEqual(pricesSorted); } - - public async fillCheckoutForm( - firstName: string, - lastName: string, - zipCode: string, - ) { - await this.page.locator(this.checkoutForm.firstNameInput).fill(firstName); - await this.page.locator(this.checkoutForm.lastNameInput).fill(lastName); - await this.page.locator(this.checkoutForm.zipCodeInput).fill(zipCode); - } - - public async clickContinueButton() { - await this.page.locator(this.continueButton).click(); - } - - public async clickFinishButton() { - await this.page.locator(this.finishButton).click(); - } - - public async assertSuccessContainerVisible() { - await expect( - this.page.locator(this.checkoutCompleteContainer), - ).toBeVisible(); - } - - public async assertSuccessHeaderCorrect(expectedText: string) { - await expect( - this.page.locator(this.checkoutCompleteHeader), - ).toHaveText(expectedText); - } - - public async assertSuccessTextCorrect(expectedText: string) { - await expect( - this.page.locator(this.checkoutCompleteText), - ).toHaveText(expectedText); - } - - // ================= PRODUCT FUNCTIONS ================= - } diff --git a/steps/product.steps.ts b/steps/product.steps.ts index e69de29b..ea32b260 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -0,0 +1,12 @@ +import { Product } from './../pages/product.page'; +import { Then } from "@cucumber/cucumber"; +import { getPage } from "../playwrightUtilities"; + +Then("I will sort items by {string}", async (sortOption: string) => { + await new Product(getPage()).selectSortOption(sortOption); +}) + +Then("The items should be sorted by price {string}", async (sortOption: string) => { + await new Product(getPage()).assertPricesSorted(sortOption); +}) + From 95a7daba7f091f4436148684739adffa8cc0c1a5 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 18:23:54 -0400 Subject: [PATCH 08/10] Task 5A - Added sort by name --- features/product.feature | 29 +++++++++++++++++++---------- pages/product.page.ts | 35 +++++++++++++++++++++++++++++++++-- steps/product.steps.ts | 4 ++++ 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/features/product.feature b/features/product.feature index 5b4529b5..1028e69b 100644 --- a/features/product.feature +++ b/features/product.feature @@ -2,13 +2,22 @@ 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' - And I will sort items by '' - Then The items should be sorted by price '' - - Examples: - | sort | - | Price (low to high) | - | Price (high to low) | + Scenario Outline: Validate product sort by price + Then I will login as 'standard_user' + And I will sort items by '' + Then The items should be sorted by price '' + + Examples: + | price sort | + | Price (low to high) | + | Price (high to low) | + + Scenario Outline: Validate product sort by name + Then I will login as 'standard_user' + And I will sort items by '' + Then The items should be sorted by name '' + + Examples: + | name sort | + | Name (A to Z) | + | Name (Z to A) | diff --git a/pages/product.page.ts b/pages/product.page.ts index eca1d7f0..4543810a 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -6,6 +6,7 @@ export class Product { '[data-test="product-sort-container"]'; private readonly itemContainer: string = '[data-test="inventory-list"] > div'; private readonly itemPrice: string = `[data-test="inventory-item-price"]`; + private readonly itemName: string = `[data-test^="item-"][data-test$="-title-link"]`; constructor(page: Page) { this.page = page; @@ -26,8 +27,14 @@ export class Product { .count()); i++ ) { - const priceFormatted = parseFloat((await this.page - .locator(`${this.itemContainer} ${this.itemPrice}`).nth(i).innerText()).replace("$", "")); + const priceFormatted = parseFloat( + ( + await this.page + .locator(`${this.itemContainer} ${this.itemPrice}`) + .nth(i) + .innerText() + ).replace("$", ""), + ); prices.push(priceFormatted); } console.log(`prices:`, prices); @@ -36,4 +43,28 @@ export class Product { ); expect(prices).toEqual(pricesSorted); } + + public async assertNamesSorted(sort: string) { + // Get all prices + const names: string[] = []; + for ( + let i = 0; + i < + (await this.page + .locator(`${this.itemContainer} ${this.itemName}`) + .count()); + i++ + ) { + const name = await this.page + .locator(`${this.itemContainer} ${this.itemName}`) + .nth(i) + .innerText(); + names.push(name); + } + console.log(`names:`, names); + const namesSorted = [...names].sort((a, b) => + sort === "Name (A to Z)" ? a.localeCompare(b) : b.localeCompare(a), + ); + expect(names).toEqual(namesSorted); + } } diff --git a/steps/product.steps.ts b/steps/product.steps.ts index ea32b260..0b5c28b7 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -10,3 +10,7 @@ Then("The items should be sorted by price {string}", async (sortOption: string) await new Product(getPage()).assertPricesSorted(sortOption); }) +Then("The items should be sorted by name {string}", async (sortOption: string) => { + await new Product(getPage()).assertNamesSorted(sortOption); +}) + From e95eaa79caf5deaf63bb61c7f6026075de750241 Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 19:55:49 -0400 Subject: [PATCH 09/10] Task 5B - Add item to cart --- features/purchase.feature | 32 +++++++++++++++++++++----------- pages/purchase.page.ts | 37 +++++++++++++++++++++++++++++++------ steps/purchase.steps.ts | 12 ++++++++++++ 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/features/purchase.feature b/features/purchase.feature index 6ec6943a..389ee5e9 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -3,20 +3,30 @@ 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 + 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) - Then I will click the cart button + Then I will click the cart button # TODO: Select Checkout - Then I will click the checkout button + Then I will click the checkout button # TODO: Fill in the First Name, Last Name, and Zip/Postal Code - Then I will fill the checkout form as "Test" "User", "12345" + Then I will fill the checkout form as "Test" "User", "12345" # TODO: Select Continue - Then I will click the continue button + Then I will click the continue button # TODO: Select Finish - Then I will click the finish button + Then I will click the finish button # TODO: Validate the text 'Thank you for your order!' - Then The checkout success container should be visible - And The checkout success header should read 'Thank you for your order!' - And The checkout success text should read 'Your order has been dispatched, and will arrive just as fast as the pony can get there!' \ No newline at end of file + Then The checkout success container should be visible + And The checkout success header should read 'Thank you for your order!' + And The checkout success text should read 'Your order has been dispatched, and will arrive just as fast as the pony can get there!' + + Scenario: Add item to cart + Then I will login as 'standard_user' + Then I will add "Sauce Labs Backpack" to the cart + Then The shopping cart badge count should be 1 + And I will add "Sauce Labs Fleece Jacket" to the cart + Then The shopping cart badge count should be 2 + Then I will click the cart button + Then The cart should contain "Sauce Labs Backpack" + And The cart should contain "Sauce Labs Fleece Jacket" diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts index e87efbca..d47dc610 100644 --- a/pages/purchase.page.ts +++ b/pages/purchase.page.ts @@ -5,6 +5,8 @@ export class Purchase { private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]'; private readonly cartButton: string = '[data-test="shopping-cart-link"]'; + private readonly cartButtonBadge: string = + '[data-test="shopping-cart-badge"]'; private readonly checkoutButton: string = '[data-test="checkout"]'; private readonly checkoutForm = { firstNameInput: '[data-test="firstName"]', @@ -18,6 +20,10 @@ export class Purchase { private readonly checkoutCompleteHeader: string = '[data-test="complete-header"]'; private readonly checkoutCompleteText: string = '[data-test="complete-text"]'; + private readonly itemContainer: string = '[data-test="inventory-list"] > div'; + private readonly addToCartGeneric: string = 'button[id^="add-to-cart-"]'; + private readonly cartItem: string = + '[data-test="cart-list"] [data-test="inventory-item"]'; constructor(page: Page) { this.page = page; @@ -60,15 +66,34 @@ export class Purchase { } public async assertSuccessHeaderCorrect(expectedText: string) { - await expect( - this.page.locator(this.checkoutCompleteHeader), - ).toHaveText(expectedText); + await expect(this.page.locator(this.checkoutCompleteHeader)).toHaveText( + expectedText, + ); } public async assertSuccessTextCorrect(expectedText: string) { + await expect(this.page.locator(this.checkoutCompleteText)).toHaveText( + expectedText, + ); + } + + public async addItemToShoppingCart(itemName: string) { + await this.page + .locator(this.itemContainer) + .filter({ hasText: itemName }) + .locator(this.addToCartGeneric) + .click(); + } + + public async assertShoppingCartBadgeCount(expectedCount: number) { await expect( - this.page.locator(this.checkoutCompleteText), - ).toHaveText(expectedText); + this.page.locator(`${this.cartButton} ${this.cartButtonBadge}`), + ).toHaveText(`${expectedCount}`); } -} \ No newline at end of file + public async assertItemIsVisibleInCart(itemName: string) { + await expect( + this.page.locator(this.cartItem).filter({ hasText: itemName }), + ).toBeVisible(); + } +} diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts index f15018ef..33f57ae8 100644 --- a/steps/purchase.steps.ts +++ b/steps/purchase.steps.ts @@ -40,3 +40,15 @@ Then("The checkout success header should read {string}", async (expectedText) => Then("The checkout success text should read {string}", async (expectedText) => { await new Purchase(getPage()).assertSuccessTextCorrect(expectedText); }); + +Then("I will add {string} to the cart", async (itemName) => { + await new Purchase(getPage()).addItemToShoppingCart(itemName); +}); + +Then("The shopping cart badge count should be {int}", async (count) => { + await new Purchase(getPage()).assertShoppingCartBadgeCount(count); +}); + +Then("The cart should contain {string}", async (itemName) => { + await new Purchase(getPage()).assertItemIsVisibleInCart(itemName); +}); From a55517696aad30b6096739ea4307172649a7702b Mon Sep 17 00:00:00 2001 From: CJDAutorio Date: Mon, 1 Jun 2026 19:57:06 -0400 Subject: [PATCH 10/10] Task 5C - Remove item from cart --- features/purchase.feature | 11 +++++++++++ pages/purchase.page.ts | 15 +++++++++++++++ steps/purchase.steps.ts | 8 ++++++++ 3 files changed, 34 insertions(+) diff --git a/features/purchase.feature b/features/purchase.feature index 389ee5e9..fd54a527 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -30,3 +30,14 @@ Feature: Purchase Feature Then I will click the cart button Then The cart should contain "Sauce Labs Backpack" And The cart should contain "Sauce Labs Fleece Jacket" + + Scenario: Remove item from cart + Then I will login as 'standard_user' + Then I will add "Sauce Labs Backpack" to the cart + And I will add "Sauce Labs Fleece Jacket" to the cart + Then The shopping cart badge count should be 2 + Then I will remove "Sauce Labs Backpack" from the cart + Then The shopping cart badge count should be 1 + Then I will click the cart button + Then The cart should contain "Sauce Labs Fleece Jacket" + And The cart should not contain "Sauce Labs Backpack" diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts index d47dc610..24e94b12 100644 --- a/pages/purchase.page.ts +++ b/pages/purchase.page.ts @@ -22,6 +22,7 @@ export class Purchase { private readonly checkoutCompleteText: string = '[data-test="complete-text"]'; private readonly itemContainer: string = '[data-test="inventory-list"] > div'; private readonly addToCartGeneric: string = 'button[id^="add-to-cart-"]'; + private readonly removeFromCartGeneric: string = 'button[id^="remove-"]'; private readonly cartItem: string = '[data-test="cart-list"] [data-test="inventory-item"]'; @@ -96,4 +97,18 @@ export class Purchase { this.page.locator(this.cartItem).filter({ hasText: itemName }), ).toBeVisible(); } + + public async removeItemFromCart(itemName: string) { + await this.page + .locator(this.itemContainer) + .filter({ hasText: itemName }) + .locator(this.removeFromCartGeneric) + .click(); + } + + public async assertItemIsNotVisibleInCart(itemName: string) { + await expect( + this.page.locator(this.cartItem).filter({ hasText: itemName }), + ).toHaveCount(0); + } } diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts index 33f57ae8..e6834b06 100644 --- a/steps/purchase.steps.ts +++ b/steps/purchase.steps.ts @@ -52,3 +52,11 @@ Then("The shopping cart badge count should be {int}", async (count) => { Then("The cart should contain {string}", async (itemName) => { await new Purchase(getPage()).assertItemIsVisibleInCart(itemName); }); + +Then("I will remove {string} from the cart", async (itemName) => { + await new Purchase(getPage()).removeItemFromCart(itemName); +}); + +Then("The cart should not contain {string}", async (itemName) => { + await new Purchase(getPage()).assertItemIsNotVisibleInCart(itemName); +});