From 89d59360078b3417823bb6d40f344084f9aed96a Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 3 Jun 2026 15:04:23 -0400 Subject: [PATCH 1/6] Fix Page Title Test Scenario --- 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 280786242f9d96e708197882aa5f9ba2e3dd8488 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 3 Jun 2026 15:33:50 -0400 Subject: [PATCH 2/6] Added Error Message Validation For Locked Out User --- features/login.feature | 3 ++- pages/login.page.ts | 9 +++++++++ steps/login.steps.ts | 6 +++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/features/login.feature b/features/login.feature index 17cddd39..0b177bba 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 + # TODO: Add a step to validate the error message received + 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/pages/login.page.ts b/pages/login.page.ts index 5a01614b..c5329802 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -23,4 +23,13 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } + + private readonly errorMessage: string = '[data-test="error"]'; + + public async validateErrorMessage(expectedError: string) { + const actualError = await this.page.locator(this.errorMessage).textContent(); + if (actualError !== expectedError) { + throw new Error(`Expected error to be ${expectedError} but found ${actualError}`); + } + } } \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..5a6509a3 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); -}); \ No newline at end of file +}); + +Then('I should see the error message {string}', async (expectedError) => { + await new Login(getPage()).validateErrorMessage(expectedError); +}); From cf89f4f8f8019e9220eea215b116695960bf0140 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 3 Jun 2026 16:01:11 -0400 Subject: [PATCH 3/6] Added Parameterized product price sort test --- features/product.feature | 10 +++++++--- pages/product.page.ts | 17 +++++++++++++++++ steps/product.steps.ts | 10 +++++++++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/features/product.feature b/features/product.feature index 8a7ceab9..ca96bb3e 100644 --- a/features/product.feature +++ b/features/product.feature @@ -6,8 +6,12 @@ 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 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 + # TODO: Sort the items by + Then I sort the products by "" + # TODO: Validate all 6 items are sorted correctly by price + Then the products should be 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/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..9f84a77d 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 sortContainer: string = '[data-test="product-sort-container"]'; + private readonly itemPrice: string = '[data-test="inventory-item-price"]'; constructor(page: Page) { this.page = page; @@ -11,4 +13,19 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async sortBy(sortOption: string) { + await this.page.locator(this.sortContainer).selectOption({ label: sortOption }); + } + + + public async validatePriceSort(order: string) { + const priceTexts = await this.page.locator(this.itemPrice).allTextContents(); + const prices = priceTexts.map(t => parseFloat(t.replace('$', ''))); + const expected = [...prices].sort((a, b) => order === 'asc' ? a - b : b - a); + if (JSON.stringify(prices) !== JSON.stringify(expected)) { + throw new Error(`Prices not sorted ${order}: got ${prices}, expected ${expected}`); + } + } + } \ No newline at end of file diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb98..21b909f9 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 sort the products by {string}', async (sortOption) => { + await new Product(getPage()).sortBy(sortOption); +}); + +Then('the products should be sorted by price {string}', async (order) => { + await new Product(getPage()).validatePriceSort(order); +}); From 69b65b7a6a175fa467b7eb60916b862b6aa29df2 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 3 Jun 2026 16:31:05 -0400 Subject: [PATCH 4/6] Completed The Purchase Flow Test --- features/purchase.feature | 18 ++++++++++----- pages/purchase.page.ts | 46 +++++++++++++++++++++++++++++++++++++++ steps/purchase.steps.ts | 27 +++++++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 pages/purchase.page.ts create mode 100644 steps/purchase.steps.ts diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..7979f7cc 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -6,9 +6,15 @@ 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 + # TODO: Select the cart (top-right) + Then I go to the cart + # TODO: Select Checkout + Then I checkout + # TODO: Fill in the First Name, Last Name, and Zip/Postal Code + Then I fill in the checkout information with first name "Brian", last name "Johnson", and postal code "12345" + # TODO: Select Continue + Then I continue to the overview + # TODO: Select Finish + Then I finish the checkout + # TODO: Validate the text 'Thank you for your order!' + Then I should see the text "Thank you for your order!" \ No newline at end of file diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 00000000..30fd10eb --- /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 cartLink: 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 postalCodeField: string = '[data-test="postalCode"]' + private readonly continueButton: string = '[data-test="continue"]' + private readonly finishButton: string = '[data-test="finish"]' + private readonly completeHeader: string = '[data-test="complete-header"]' + + constructor(page: Page) { + this.page = page; + } + + public async goToCart() { + await this.page.locator(this.cartLink).click() + } + + public async checkout() { + await this.page.locator(this.checkoutButton).click() + } + + public async fillCheckoutInfo(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.postalCodeField).fill(postalCode) + } + + public async continueCheckout() { + await this.page.locator(this.continueButton).click() + } + + public async finish() { + await this.page.locator(this.finishButton).click() + } + + public async validateCompleteHeader(expectedText: string) { + const actual = await this.page.locator(this.completeHeader).textContent() + if (actual !== expectedText) { + throw new Error(`Expected text to be ${expectedText} but found ${actual}`) + } + } +} \ No newline at end of file diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 00000000..ed2b7da2 --- /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 go to the cart', async () => { + await new Purchase(getPage()).goToCart(); +}); + +Then('I checkout', async () => { + await new Purchase(getPage()).checkout(); +}); + +Then('I fill in the checkout information with first name {string}, last name {string}, and postal code {string}', async (firstName, lastName, postalCode) => { + await new Purchase(getPage()).fillCheckoutInfo(firstName, lastName, postalCode); +}); + +Then('I continue to the overview', async () => { + await new Purchase(getPage()).continueCheckout(); +}); + +Then('I finish the checkout', async () => { + await new Purchase(getPage()).finish(); +}); + +Then('I should see the text {string}', async (expectedText) => { + await new Purchase(getPage()).validateCompleteHeader(expectedText); +}); From 39507c69b242575ae13a0542978cb36388123ff5 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 3 Jun 2026 16:54:20 -0400 Subject: [PATCH 5/6] Added Bonus Tests for Login Edge Cases and Checkout Validation --- features/login.feature | 14 +++++++++++++- features/purchase.feature | 10 +++++++++- pages/login.page.ts | 6 ++++++ pages/product.page.ts | 10 ++++++++++ pages/purchase.page.ts | 8 ++++++++ steps/login.steps.ts | 6 +++++- steps/product.steps.ts | 4 ++++ steps/purchase.steps.ts | 4 ++++ 8 files changed, 59 insertions(+), 3 deletions(-) diff --git a/features/login.feature b/features/login.feature index 0b177bba..774745d7 100644 --- a/features/login.feature +++ b/features/login.feature @@ -10,4 +10,16 @@ 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 - Then I should see the error message "Epic sadface: Sorry, this user has been locked out." \ No newline at end of file + Then I should see the error message "Epic sadface: Sorry, this user has been locked out." + + Scenario: Validate successful login displays the inventory page + Then I will login as 'standard_user' + Then I should see 6 products on the inventory page + + Scenario Outline: Validate login error for + When I attempt to login with username "" and password "" + Then I should see the error message "" + Examples: + | description | username | password | error | + | empty username | | | Epic sadface: Username is required | + | empty password | standard_user | | Epic sadface: Password is required | \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 7979f7cc..4fc17710 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -17,4 +17,12 @@ Feature: Purchase Feature # TODO: Select Finish Then I finish the checkout # TODO: Validate the text 'Thank you for your order!' - Then I should see the text "Thank you for your order!" \ No newline at end of file + Then I should see the text "Thank you for your order!" + + Scenario: Validate checkout requires customer information + Then I will login as 'standard_user' + Then I will add the backpack to the cart + Then I go to the cart + Then I checkout + Then I continue to the overview + Then I should see the checkout error "Error: First Name is required" \ No newline at end of file diff --git a/pages/login.page.ts b/pages/login.page.ts index c5329802..79262c19 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -24,6 +24,12 @@ export class Login { await this.page.locator(this.loginButton).click() } + public async attemptLogin(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() + } + private readonly errorMessage: string = '[data-test="error"]'; public async validateErrorMessage(expectedError: string) { diff --git a/pages/product.page.ts b/pages/product.page.ts index 9f84a77d..0c7f1ce4 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -5,6 +5,7 @@ export class Product { private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' private readonly sortContainer: string = '[data-test="product-sort-container"]'; private readonly itemPrice: string = '[data-test="inventory-item-price"]'; + private readonly inventoryItem: string = '[data-test="inventory-item"]'; constructor(page: Page) { this.page = page; @@ -28,4 +29,13 @@ export class Product { } } + public async validateInventoryPage(expectedCount: number) { + await this.page.waitForURL('**/inventory.html'); + await this.page.locator(this.inventoryItem).first().waitFor(); + const count = await this.page.locator(this.inventoryItem).count(); + if (count !== expectedCount) { + throw new Error(`Expected ${expectedCount} products on the inventory page but found ${count}`); + } + } + } \ No newline at end of file diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts index 30fd10eb..70cc0b8a 100644 --- a/pages/purchase.page.ts +++ b/pages/purchase.page.ts @@ -10,6 +10,7 @@ export class Purchase { private readonly continueButton: string = '[data-test="continue"]' private readonly finishButton: string = '[data-test="finish"]' private readonly completeHeader: string = '[data-test="complete-header"]' + private readonly errorMessage: string = '[data-test="error"]' constructor(page: Page) { this.page = page; @@ -43,4 +44,11 @@ export class Purchase { throw new Error(`Expected text to be ${expectedText} but found ${actual}`) } } + + public async validateCheckoutError(expectedError: string) { + const actual = await this.page.locator(this.errorMessage).textContent() + if (actual !== expectedError) { + throw new Error(`Expected checkout error to be ${expectedError} but found ${actual}`) + } + } } \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index 5a6509a3..12b20e42 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -1,4 +1,4 @@ -import { Then } from '@cucumber/cucumber'; +import { Then, When } from '@cucumber/cucumber'; import { getPage } from '../playwrightUtilities'; import { Login } from '../pages/login.page'; @@ -13,3 +13,7 @@ Then('I will login as {string}', async (userName) => { Then('I should see the error message {string}', async (expectedError) => { await new Login(getPage()).validateErrorMessage(expectedError); }); + +When('I attempt to login with username {string} and password {string}', async (userName, password) => { + await new Login(getPage()).attemptLogin(userName, password); +}); diff --git a/steps/product.steps.ts b/steps/product.steps.ts index 21b909f9..317950a3 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -13,3 +13,7 @@ Then('I sort the products by {string}', async (sortOption) => { Then('the products should be sorted by price {string}', async (order) => { await new Product(getPage()).validatePriceSort(order); }); + +Then('I should see {int} products on the inventory page', async (count) => { + await new Product(getPage()).validateInventoryPage(count); +}); diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts index ed2b7da2..11d24dca 100644 --- a/steps/purchase.steps.ts +++ b/steps/purchase.steps.ts @@ -25,3 +25,7 @@ Then('I finish the checkout', async () => { Then('I should see the text {string}', async (expectedText) => { await new Purchase(getPage()).validateCompleteHeader(expectedText); }); + +Then('I should see the checkout error {string}', async (expectedError) => { + await new Purchase(getPage()).validateCheckoutError(expectedError); +}); From 877cfd4a56012aa5e38855e4f580c5d697ca43f0 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Wed, 3 Jun 2026 17:01:22 -0400 Subject: [PATCH 6/6] Removed TODO and Added Context to README --- README.md | 31 ++++++++++++++++++++++++------- features/login.feature | 4 +--- features/product.feature | 6 +----- features/purchase.feature | 8 +------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index b9111057..f7d348ec 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,29 @@ It is not expected that you complete every task, however, please give your best You will be scored based on your ability to complete the following tasks: -- [ ] Install and setup this repository on your personal computer -- [ ] Complete the automation tasks listed below +- [x] Install and setup this repository on your personal computer +- [x] Complete the automation tasks listed below ### Tasks -- [ ] Modify the scenario 'Validate the login page title' from [login.feature](features/login.feature#8) which runs but fails. Determine the cause of the failure and update the scenario to pass in the test -- [ ] Extend the scenario 'Validate login error message' from [login.feature](features/login.feature#10) which runs and passes but is missing a step. Extend the scenario to validate the error message received. -- [ ] Modify and extend the 'Validate successful purchase text' from [purchase.feature](features/purchase.feature#6) with steps for each comment listed. Consider writing a new steps.ts file along with an appropriate page.ts -- [ ] Modify and extend the 'Validate product sort by price sort' from [product.feature](features/product.feature#6) with steps for each comment listed. Utilize the Scenario Outline and Examples table to parameterize the test -- [ ] Extend the testing coverage with anything you believe would be beneficial +- [x] Modify the scenario 'Validate the login page title' from [login.feature](features/login.feature#8) which runs but fails. Determine the cause of the failure and update the scenario to pass in the test +- [x] Extend the scenario 'Validate login error message' from [login.feature](features/login.feature#10) which runs and passes but is missing a step. Extend the scenario to validate the error message received. +- [x] Modify and extend the 'Validate successful purchase text' from [purchase.feature](features/purchase.feature#6) with steps for each comment listed. Consider writing a new steps.ts file along with an appropriate page.ts +- [x] Modify and extend the 'Validate product sort by price sort' from [product.feature](features/product.feature#6) with steps for each comment listed. Utilize the Scenario Outline and Examples table to parameterize the test +- [x] Extend the testing coverage with anything you believe would be beneficial + +## Implemented Tests + +All four required tasks are complete, plus additional bonus coverage. The full suite runs **9 scenarios (38 steps)**, all passing. + +### Required tasks +- **Login page title** ([login.feature](features/login.feature)) — corrected the expected title to "Swag Labs" so the failing scenario passes. +- **Login error message** ([login.feature](features/login.feature)) — added a step asserting the locked-out user's error banner. +- **Successful purchase flow** ([purchase.feature](features/purchase.feature)) — implemented the full checkout (cart → checkout → customer info → continue → finish → order confirmation) via a new [purchase.steps.ts](steps/purchase.steps.ts) and [purchase.page.ts](pages/purchase.page.ts). +- **Product price sort** ([product.feature](features/product.feature)) — parameterized the price (low→high / high→low) sort using a Scenario Outline and Examples table. + +### Bonus coverage +Added to cover important paths the original suite missed: + +- **Parameterized login errors** ([login.feature](features/login.feature)) — a Scenario Outline validating the "Username is required" and "Password is required" errors for empty credentials. Reuses the existing error-message validation to demonstrate data-driven testing. +- **Successful login happy-path** ([login.feature](features/login.feature)) — confirms a valid login lands on the inventory page and renders all 6 products (the suite previously had no positive login assertion). +- **Checkout required-field validation** ([purchase.feature](features/purchase.feature)) — confirms the checkout form blocks continuing without customer info, asserting the "First Name is required" error. diff --git a/features/login.feature b/features/login.feature index 774745d7..79ae5f86 100644 --- a/features/login.feature +++ b/features/login.feature @@ -4,12 +4,10 @@ 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 "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 Then I should see the error message "Epic sadface: Sorry, this user has been locked out." Scenario: Validate successful login displays the inventory page @@ -22,4 +20,4 @@ Feature: Login Feature Examples: | description | username | password | error | | empty username | | | Epic sadface: Username is required | - | empty password | standard_user | | Epic sadface: Password is required | \ No newline at end of file + | empty password | standard_user | | Epic sadface: Password is required | diff --git a/features/product.feature b/features/product.feature index ca96bb3e..9288118f 100644 --- a/features/product.feature +++ b/features/product.feature @@ -3,15 +3,11 @@ 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 Then I sort the products by "" - # TODO: Validate all 6 items are sorted correctly by price Then the products should be sorted by price "" Examples: - # TODO: extend the datatable to paramterize this test | sort | order | | Price (low to high) | asc | - | Price (high to low) | desc | \ No newline at end of file + | Price (high to low) | desc | diff --git a/features/purchase.feature b/features/purchase.feature index 4fc17710..f8a41948 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -6,17 +6,11 @@ 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) Then I go to the cart - # TODO: Select Checkout Then I checkout - # TODO: Fill in the First Name, Last Name, and Zip/Postal Code Then I fill in the checkout information with first name "Brian", last name "Johnson", and postal code "12345" - # TODO: Select Continue Then I continue to the overview - # TODO: Select Finish Then I finish the checkout - # TODO: Validate the text 'Thank you for your order!' Then I should see the text "Thank you for your order!" Scenario: Validate checkout requires customer information @@ -25,4 +19,4 @@ Feature: Purchase Feature Then I go to the cart Then I checkout Then I continue to the overview - Then I should see the checkout error "Error: First Name is required" \ No newline at end of file + Then I should see the checkout error "Error: First Name is required"