diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..79b2f365 Binary files /dev/null and b/.DS_Store differ diff --git a/features/login.feature b/features/login.feature index fb9f1fa5..86a86f00 100644 --- a/features/login.feature +++ b/features/login.feature @@ -4,9 +4,21 @@ 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" + # TODO: Fix this failing scenario- done below + 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 will login as "locked_out_user" + # TODO: Add a step to validate the error message received- done below + Then I should see the error message "Epic sadface: Sorry, this user has been locked out." + + # TODO: Extending the testing coverage- done below + Scenario: Validate login with invalid credentials + Then I will login with username "invalid" and password "invalid" + Then I should see the error message "Epic sadface: Username and password do not match any user in this service" + + # TODO: Extending the testing coverage- done below + Scenario: Validate user logout + Then I will login as "standard_user" + Then I will logout + Then I should see the login page \ No newline at end of file diff --git a/features/product.feature b/features/product.feature index 8a7ceab9..16c390e9 100644 --- a/features/product.feature +++ b/features/product.feature @@ -4,10 +4,15 @@ Feature: Product Feature 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 - Examples: - # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + + Scenario Outline: Validate product sort by price + Then I will login as "standard_user" + # TODO: Sort the items by - done below + Then I will sort products by "" + # TODO: Validate all 6 items are sorted correctly by price- done below + Then I should see all items sorted by "" + # TODO: extend the datatable to paramterize this test- done below + Examples: + | sort | order | + | Price (low to high) | asc | + | Price (high to low) | desc | \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..a9647cc2 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -3,12 +3,20 @@ 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 + # TODO: Select the cart (top-right)- done below + Then I will open the cart + # TODO: Extending the testing coverage- done below + Then I should see the product "Sauce Labs Backpack" in the cart + # TODO: Select Checkout- done below + Then I will proceed to checkout + # TODO: Fill in the First Name, Last Name, and Zip/Postal Code- done below + Then I will fill in checkout information "Nishi" "Mewada" "28262" + # TODO: Select Continue- done below + Then I will continue to overview + # TODO: Select Finish- done below + Then I will finish the purchase + # TODO: Validate the text "Thank you for your order!"- done below + Then I should see the purchase confirmation text "Thank you for your order!" diff --git a/pages/cart.page.ts b/pages/cart.page.ts new file mode 100644 index 00000000..685c9000 --- /dev/null +++ b/pages/cart.page.ts @@ -0,0 +1,24 @@ +import { expect, Page } from "@playwright/test" + +export class Cart { + private readonly page: Page + private readonly cartIcon: string = '.shopping_cart_link'; + private readonly checkoutButton: string = '#checkout'; + private readonly cartItems: string = '.cart_item'; + + constructor(page: Page) { + this.page = page; + } + + public async openCart() { + await this.page.locator(this.cartIcon).click(); + } + + public async validateProductInCart(expectedProduct: string) { + await expect(this.page.locator(this.cartItems)).toContainText(expectedProduct); + } + + public async proceedToCheckout() { + await this.page.locator(this.checkoutButton).click(); + } +} \ No newline at end of file diff --git a/pages/checkout.page.ts b/pages/checkout.page.ts new file mode 100644 index 00000000..abfeb5c5 --- /dev/null +++ b/pages/checkout.page.ts @@ -0,0 +1,36 @@ +import { Page } from "@playwright/test" + +export class Checkout { + private readonly page: Page + private readonly firstNameField: string = '#first-name'; + private readonly lastNameField: string = '#last-name'; + private readonly zipField: string = '#postal-code'; + private readonly continueButton: string = '#continue'; + private readonly finishButton: string = '#finish'; + private readonly confirmationText: string = '.complete-header'; + + constructor(page: Page) { + this.page = page; + } + + public async fillInformation(firstName: string, lastName: string, zip: string) { + await this.page.locator(this.firstNameField).fill(firstName); + await this.page.locator(this.lastNameField).fill(lastName); + await this.page.locator(this.zipField).fill(zip); + } + + public async continueToOverview() { + await this.page.locator(this.continueButton).click(); + } + + public async finishPurchase() { + await this.page.locator(this.finishButton).click(); + } + + public async validateConfirmationText(expectedText: string) { + const actualText = await this.page.locator(this.confirmationText).textContent(); + if (actualText?.trim() !== expectedText) { + throw new Error(`Expected confirmation text "${expectedText}" but found "${actualText}"`); + } + } +} \ No newline at end of file diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614b..4c794ca9 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -6,6 +6,9 @@ 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 errorMessage: string = '[data-test="error"]' + private readonly menuButton: string = '#react-burger-menu-btn' + private readonly logoutLink: string = '#logout_sidebar_link' constructor(page: Page) { this.page = page; @@ -23,4 +26,17 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } -} \ No newline at end of file + + public async loginWithCredentials(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(); + } + + public async validateErrorMessage(expectedMessage: string) { + const actualMessage = (await this.page.locator(this.errorMessage).textContent())?.trim(); + if (actualMessage !== expectedMessage) { + throw new Error(`Expected error message to be ${expectedMessage} but found ${actualMessage}`); + } + } +} diff --git a/pages/menu.page.ts b/pages/menu.page.ts new file mode 100644 index 00000000..4ef6cd6d --- /dev/null +++ b/pages/menu.page.ts @@ -0,0 +1,26 @@ +import { Page } from "@playwright/test" + +export class Menu { + private readonly page: Page + + private readonly menuButton: string = '#react-burger-menu-btn' + private readonly logoutLink: string = '#logout_sidebar_link' + private readonly loginButton: string = '#login-button' + + constructor(page: Page) { + this.page = page; + } + + public async logout() { + await this.page.locator(this.menuButton).click(); + await this.page.locator(this.logoutLink).click(); + } + + public async validateLoginPage() { + const isLoginButtonVisible = await this.page.locator(this.loginButton).isVisible(); + + if (!isLoginButtonVisible) { + throw new Error('Login page is not displayed after logout'); + } + } +} \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..4ced56bd 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -1,9 +1,13 @@ -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 addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + private readonly sortDropdown: string = 'select[data-test="product-sort-container"]' + private readonly itemPrices: string = '.inventory_item_price' + constructor(page: Page) { this.page = page; } @@ -11,4 +15,30 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async sortByPrice(sortOption: string) { + const valueMap: Record = { + 'Price (low to high)': 'lohi', + 'Price (high to low)': 'hilo', + }; + const optionValue = valueMap[sortOption]; + if (!optionValue) { + throw new Error(`Unsupported sort option: ${sortOption}`); + } + await this.page.locator(this.sortDropdown).selectOption(optionValue); + } + + public async validateSortedByPrice(order: 'asc' | 'desc') { + const priceTexts = await this.page + .locator(this.itemPrices) + .allTextContents(); + const prices = priceTexts.map((price) => + parseFloat(price.replace('$', '')) + ); + expect(prices).toHaveLength(6) + const expected = [...prices].sort((a, b) => + order === 'asc' ? a - b : b - a + ); + expect(prices).toEqual(expected); + } } \ No newline at end of file diff --git a/playwrightUtilities.ts b/playwrightUtilities.ts index 93244a29..8a50b042 100644 --- a/playwrightUtilities.ts +++ b/playwrightUtilities.ts @@ -6,7 +6,7 @@ const DEFAULT_TIMEOUT = 30000; export const initializeBrowser = async () => { if (!browser) { - browser = await chromium.launch({ headless: false }); + browser = await chromium.launch({ headless: false, channel:"chrome" }); } }; @@ -17,6 +17,7 @@ export const initializePage = async () => { } }; + export const getPage = (): Page => { if (!page) { throw new Error('Page has not been initialized. Please call initializePage first.'); diff --git a/steps/checkout.steps.ts b/steps/checkout.steps.ts new file mode 100644 index 00000000..d5654286 --- /dev/null +++ b/steps/checkout.steps.ts @@ -0,0 +1,32 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Cart } from '../pages/cart.page'; +import { Checkout } from '../pages/checkout.page'; + +Then('I will open the cart', async () => { + await new Cart(getPage()).openCart(); +}); + +Then('I should see the product {string} in the cart', async (productName) => { + await new Cart(getPage()).validateProductInCart(productName); +}); + +Then('I will proceed to checkout', async () => { + await new Cart(getPage()).proceedToCheckout(); +}); + +Then('I will fill in checkout information {string} {string} {string}', async (firstName, lastName, zip) => { + await new Checkout(getPage()).fillInformation(firstName, lastName, zip); +}); + +Then('I will continue to overview', async () => { + await new Checkout(getPage()).continueToOverview(); +}); + +Then('I will finish the purchase', async () => { + await new Checkout(getPage()).finishPurchase(); +}); + +Then('I should see the purchase confirmation text {string}', async (expectedText) => { + await new Checkout(getPage()).validateConfirmationText(expectedText); +}); \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..4aacccf4 100644 --- a/steps/login.steps.ts +++ b/steps/login.steps.ts @@ -1,6 +1,7 @@ import { Then } from '@cucumber/cucumber'; import { getPage } from '../playwrightUtilities'; import { Login } from '../pages/login.page'; +import { Menu } from '../pages/menu.page'; Then('I should see the title {string}', async (expectedTitle) => { await new Login(getPage()).validateTitle(expectedTitle); @@ -8,4 +9,20 @@ 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 will login with username {string} and password {string}', async (userName, password) => { + await new Login(getPage()).loginWithCredentials(userName, password); +}); + +Then('I should see the error message {string}', async (expectedMessage) => { + await new Login(getPage()).validateErrorMessage(expectedMessage); +}); + +Then('I will logout', async () => { + await new Menu(getPage()).logout(); +}); + +Then('I should see the login page', async () => { + await new Menu(getPage()).validateLoginPage(); }); \ No newline at end of file diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb98..a2ba7492 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 will sort products by {string}', async (sortOption: string) => { + await new Product(getPage()).sortByPrice(sortOption); +}); + +Then('I should see all items sorted by {string}', async (order) => { + await new Product(getPage()).validateSortedByPrice(order as 'asc' | 'desc'); +});