diff --git a/common.steps.ts b/common.steps.ts new file mode 100644 index 0000000..459b742 --- /dev/null +++ b/common.steps.ts @@ -0,0 +1,6 @@ +import { Given } 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 diff --git a/login.feature b/login.feature new file mode 100644 index 0000000..88ae473 --- /dev/null +++ b/login.feature @@ -0,0 +1,13 @@ +Feature: Login Feature + + Background: + Given I open the "https://www.saucedemo.com/" page + + Scenario: Validate the login page title + Then I will login as 'standard_user' + Then I should see the title "Labs Swag" + + Scenario: Validate login error message + And I login as 'locked_out_user' + Then I should see Login error message + \ No newline at end of file diff --git a/login.page.ts b/login.page.ts new file mode 100644 index 0000000..7efeff4 --- /dev/null +++ b/login.page.ts @@ -0,0 +1,26 @@ +import { 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"]' + + 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}`); + } + } + + 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() + } +} \ No newline at end of file diff --git a/login.steps.ts b/login.steps.ts new file mode 100644 index 0000000..c3f4a6f --- /dev/null +++ b/login.steps.ts @@ -0,0 +1,15 @@ +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 will login as {string}', async (userName) => { + await new Login(getPage()).loginAsUser(userName); +}); + +Then('Validate Login error message', async () => { + await expect(errorMessage).toHaveText('Invalid username or password'); +}); \ No newline at end of file diff --git a/product.feature b/product.feature new file mode 100644 index 0000000..d8aa8a6 --- /dev/null +++ b/product.feature @@ -0,0 +1,14 @@ +Feature: Product Feature + + Background: + Given I open the "https://www.saucedemo.com/" page + + Scenario Outline: Validate product sort by price + Then I will login as 'standard_user' + Then I sort items by "" + Then I validate all 6 items are sorted correctly by "" + +Examples: + | sort | + | Price (low to high) | + | Price (high to low) | \ No newline at end of file diff --git a/product.page.ts b/product.page.ts new file mode 100644 index 0000000..33fbd8c --- /dev/null +++ b/product.page.ts @@ -0,0 +1,21 @@ +import { Page } from "@playwright/test" + +export class Product { + private readonly page: Page + private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + + constructor(page: Page) { + this.page = page; + } + + public async addBackPackToCart() { + await this.page.locator(this.addToCart).click() + } + +public async sortItems(sortType: string) { + await this.page.selectOption(".product_sort_container", sortType); +} + public async waitForSelector(selector: string) { + await this.page.waitForSelector(selector); + +} \ No newline at end of file diff --git a/product.steps.ts b/product.steps.ts new file mode 100644 index 0000000..24a3fec --- /dev/null +++ b/product.steps.ts @@ -0,0 +1,7 @@ +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(); +}); \ No newline at end of file diff --git a/purchase.feature b/purchase.feature new file mode 100644 index 0000000..60bbe32 --- /dev/null +++ b/purchase.feature @@ -0,0 +1,14 @@ +Feature: Purchase Feature + + Background: + Given I open the "https://www.saucedemo.com/" page + + ll add the backpack to the cart + Then Select the cart (top-right) + Then Select CheScenario: Validate successful purchase text + Then I will login as 'standard_user' + Then I wickout + Then Fill in the First Name, Last Name, and Zip/Postal Code + Then Select Continue + Then Select Finish + Then Validate the text 'Thank you for your order!' \ No newline at end of file diff --git a/purchase.page.ts b/purchase.page.ts new file mode 100644 index 0000000..ba79705 --- /dev/null +++ b/purchase.page.ts @@ -0,0 +1,72 @@ +import { Page } from "@playwright/test" + +export class Purchase { + private readonly page: Page + private readonly clickShoppingcartlink: string = '.shopping_cart_link' + + constructor(page: Page) { + this.page = page; + } + + public async clickShoppingCart() { + await this.page.locator(this.clickShoppingcartlink).click() + } + + public async clickCheckout() { + await this.page.click("#checkout"); + } + public async fillCheckoutInformation() { + await this.page.fill("#first-name", "John"); + await this.page.fill("#last-name", "Doe"); + await this.page.fill("#postal-code", "560001"); + } + public async clickContinue() { + await this.page.click("#continue"); + } + public async clickFinish() { + await this.page.click("#finish"); + } + public async validateTitle(expectedTitle: 'Thank you for your order!') {const pageTitle = await this.page.title(); + if (pageTitle == expectedTitle) { + throw new Error(`Expected title to be ${expectedTitle} but found ${pageTitle}`); + } + } + +public async sortItems(sortType: string) { + await this.page.selectOption(".product_sort_container", sortType); + + const priceElements = await this.page.locator(".inventory_item_price").allTextContents(); + const priceTexts = priceElements.map((text: string) => text.trim()); + + interface PriceValidation { + expectedCount: number; + actualCount: number; + } + + const priceValidation: PriceValidation = { + expectedCount: 6, + actualCount: priceTexts.length + }; + + if (priceValidation.actualCount !== priceValidation.expectedCount) { + throw new Error(`❌ Expected ${priceValidation.expectedCount} items but found: ${priceValidation.actualCount}`); + } + + const actualPrices = priceTexts.map((p: string) => parseFloat(p.replace("$", ""))); + + // Create expected sorted list + const expectedPrices = [...actualPrices]; + + if (sortType === "Price (low to high)") { + expectedPrices.sort((a, b) => a - b); + } else if (sortType === "Price (high to low)") { + expectedPrices.sort((a, b) => b - a); + } + + // Compare actual vs expected + if (JSON.stringify(actualPrices) !== JSON.stringify(expectedPrices)) { + throw new Error(`❌ Sorting failed for: ${sortType}\nActual: ${actualPrices}\nExpected: ${expectedPrices}`); + } + +} +} diff --git a/purchase.steps.ts b/purchase.steps.ts new file mode 100644 index 0000000..6c84cf0 --- /dev/null +++ b/purchase.steps.ts @@ -0,0 +1,64 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Purchase } from '../pages/purchase.page'; + +Then('I will click on the shopping cart', async () => { + await new Purchase(getPage()).clickShoppingCart(); +}); + +Then('I will click on checkout', async () => { + await new Purchase(getPage()).clickCheckout(); +}); + +Then('I will fill the checkout information', async () => { + await new Purchase(getPage()).fillCheckoutInformation(); +}); + +Then('I will click on continue', async () => { + await new Purchase(getPage()).clickContinue(); +}); + +Then('I will click on finish', async () => { + await new Purchase(getPage()).clickFinish(); +}); + +Then('I should see the order confirmation message', async () => { + await new Purchase(getPage()).validateTitle('Thank you for your order!'); +}); + +Then('I sort the items by {string}', async (sortType) => { + const page = getPage(); + const priceTexts = await page.locator(".inventory_item_price").allTextContents(); + + if (priceTexts.length !== 6) { + throw new Error(`❌ Expected 6 items but found: ${priceTexts.length}`); + } + + // Convert to number array + const actualPrices = priceTexts.map(p => parseFloat(p.replace("$", ""))); + + // Create expected sorted list + const expectedPrices = [...actualPrices]; + + if (sortType === "Price (low to high)") { + expectedPrices.sort((a, b) => a - b); + } else if (sortType === "Price (high to low)") { + expectedPrices.sort((a, b) => b - a); + } + + // Compare actual vs expected + if (JSON.stringify(actualPrices) !== JSON.stringify(expectedPrices)) { + throw new Error(`❌ Sorting failed for: ${sortType}\nActual: ${actualPrices}\nExpected: ${expectedPrices}`); + } + + console.log(`✅ Sorting validated successfully for: ${sortType}`); +}); + +Then ('I validate the sorting of items', async () => { + const priceTexts = await this.page.locator(".inventory_item_price").allTextContents(); + const actualPrices = priceTexts.map((p: string) => parseFloat(p.replace("$", ""))); + const expectedPrices = [...actualPrices]; +}); + + +