From 9c86bb829cebcf56826c1d32fb127e21b7c2d04b Mon Sep 17 00:00:00 2001 From: Mouni-QA-Automation Date: Fri, 20 Mar 2026 21:59:11 -0400 Subject: [PATCH] completed Playwright Cucucmber Assessment with Extra coverage --- features/login.feature | 10 ++++-- features/product.feature | 6 +++- features/purchase.feature | 14 +++++++- package-lock.json | 28 +++++++++++++++- package.json | 3 ++ pages/login.page.ts | 16 ++++++++- pages/product.page.ts | 68 ++++++++++++++++++++++++++++++++++++++- playwrightUtilities.ts | 18 ++++++++++- steps/login.steps.ts | 8 +++++ steps/product.steps.ts | 13 +++++++- steps/purchase.steps.ts | 36 +++++++++++++++++++++ 11 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 steps/purchase.steps.ts diff --git a/features/login.feature b/features/login.feature index fb9f1fa..6e939c8 100644 --- a/features/login.feature +++ b/features/login.feature @@ -5,8 +5,14 @@ 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' - # 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 error message "Epic sadface: Sorry, this user has been locked out." + + #Extra coverage + Scenario: Validate successful login + Then I will login as 'standard_user' + Then I should be navigated to the products page \ No newline at end of file diff --git a/features/product.feature b/features/product.feature index 8a7ceab..417026a 100644 --- a/features/product.feature +++ b/features/product.feature @@ -7,7 +7,11 @@ Feature: Product Feature Scenario Outline: Validate product sort by price Then I will login as 'standard_user' # TODO: Sort the items by + Then I sort the items by "" # TODO: Validate all 6 items are sorted correctly by price + Then I should see all products sorted by price "" Examples: # TODO: extend the datatable to paramterize this test - | sort | \ No newline at end of file + | sort | + |hilo| + |lohi| \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 2863478..fe3b847 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -7,8 +7,20 @@ 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 select cart on the top-right # TODO: Select Checkout + Then I will select Checkout # TODO: Fill in the First Name, Last Name, and Zip/Postal Code + Then I will Fill in the First Name, Last Name and Zip/Postal Code # TODO: Select Continue + Then select continue # TODO: Select Finish - # TODO: Validate the text 'Thank you for your order!' \ No newline at end of file + Then select finish + # TODO: Validate the text 'Thank you for your order!' + Then validate the text 'Thank you for your order' + + #Extra coverage + Scenario: Validate cart badge count 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 as "1" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b90d7c6..3443885 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,12 @@ { - "name": "Playwright-Project", + "name": "Playwright-Cucumber-Exercise", "lockfileVersion": 3, "requires": true, "packages": { "": { + "dependencies": { + "@faker-js/faker": "^10.3.0" + }, "devDependencies": { "@cucumber/cucumber": "^10.0.1", "@cucumber/pretty-formatter": "^1.0.0", @@ -189,6 +192,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-10.0.1.tgz", "integrity": "sha512-g7W7SQnNMSNnMRQVGubjefCxdgNFyq4P3qxT2Ve7Xhh8ZLoNkoRDcWsyfKQVWnxNfgW3aGJmxbucWRoTi+ZUqg==", "dev": true, + "peer": true, "dependencies": { "@cucumber/ci-environment": "9.2.0", "@cucumber/cucumber-expressions": "16.1.2", @@ -253,6 +257,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-26.2.0.tgz", "integrity": "sha512-iRSiK8YAIHAmLrn/mUfpAx7OXZ7LyNlh1zT89RoziSVCbqSVDxJS6ckEzW8loxs+EEXl0dKPQOXiDmbHV+C/fA==", "dev": true, + "peer": true, "dependencies": { "@cucumber/messages": ">=19.1.4 <=22" } @@ -350,6 +355,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, + "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -359,6 +365,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-22.0.0.tgz", "integrity": "sha512-EuaUtYte9ilkxcKmfqGF9pJsHRUU0jwie5ukuZ/1NPTuHS1LxHPsGEODK17RPRbZHOFhqybNzG2rHAwThxEymg==", "dev": true, + "peer": true, "dependencies": { "@types/uuid": "9.0.1", "class-transformer": "0.5.1", @@ -388,6 +395,22 @@ "integrity": "sha512-N43uWud8ZXuVjza423T9ZCIJsaZhFekmakt7S9bvogTxqdVGbRobjR663s0+uW0Rz9e+Pa8I6jUuWtoBLQD2Mw==", "dev": true }, + "node_modules/@faker-js/faker": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.3.0.tgz", + "integrity": "sha512-It0Sne6P3szg7JIi6CgKbvTZoMjxBZhcv91ZrqrNuaZQfB5WoqYYbzCUOq89YR+VY8juY9M1vDWmDDa2TzfXCw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0", + "npm": ">=10" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -549,6 +572,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz", "integrity": "sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==", "dev": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -974,6 +998,7 @@ "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-21.0.1.tgz", "integrity": "sha512-pGR7iURM4SF9Qp1IIpNiVQ77J9kfxMkPOEbyy+zRmGABnWWCsqMpJdfHeh9Mb3VskemVw85++e15JT0PYdcR3g==", "dev": true, + "peer": true, "dependencies": { "@types/uuid": "8.3.4", "class-transformer": "0.5.1", @@ -2473,6 +2498,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index ed38f6f..f8ef2c0 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,8 @@ "cucumber-html-reporter": "^7.1.1", "ts-node": "^10.9.1", "typescript": "^5.3.2" + }, + "dependencies": { + "@faker-js/faker": "^10.3.0" } } diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614..93a1cca 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -1,4 +1,4 @@ -import { Page } from "@playwright/test" +import { Page,expect } 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 errorMessage: string = 'h3[data-test="error"]' constructor(page: Page) { this.page = page; @@ -23,4 +24,17 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } + + public async validateErrorMessage(expectedErrorMsg:string) { + this.page.pause(); + const errorMessage = this.page.locator(this.errorMessage); + await expect(errorMessage).toBeVisible(); + await expect(errorMessage).toContainText(expectedErrorMsg); + } + + public async validateSuccessfulLogin(){ + await this.page.waitForURL('**/inventory.html'); + } + + } \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1..95f46eb 100644 --- a/pages/product.page.ts +++ b/pages/product.page.ts @@ -1,8 +1,22 @@ -import { Page } from "@playwright/test" +import { Page, expect } from "@playwright/test" +import { getRandomFirstName, getRandomLastName, getRandomZipCode } from "../playwrightUtilities" export class Product { private readonly page: Page private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]' + private readonly sortDropdown: string = '[data-test="product-sort-container"]' + private readonly itemPrices: string = '[data-test="inventory-item-price"]' + private readonly cart: string = 'a[data-test="shopping-cart-link"]' + private readonly checkout: string = 'button[data-test="checkout"]' + private readonly firstName: string = 'input[data-test="firstName"]' + private readonly lastName: string = 'input[data-test="lastName"]' + private readonly zipCode: string = 'input[data-test="postalCode"]' + private readonly continue: string = 'input[data-test="continue"]' + private readonly finish: string = 'button[data-test="finish"]' + private readonly thankyouMessage: string = 'h2[data-test="complete-header"]' + private readonly cartBadge: string = '[data-test="shopping-cart-badge"]' + + constructor(page: Page) { this.page = page; @@ -11,4 +25,56 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + + public async selectCart() { + await this.page.locator(this.cart).click(); + } + + public async selectCheckout() { + await this.page.locator(this.checkout).click(); + } + + public async fillInTheDetails() { + await this.page.waitForSelector(this.firstName); + await this.page.locator(this.firstName).fill(getRandomFirstName()); + await this.page.locator(this.lastName).fill(getRandomLastName()); + await this.page.locator(this.zipCode).fill(getRandomZipCode()); + + } + public async sortItemsByPrice(sort: string) { + await this.page.locator(this.sortDropdown).selectOption(sort); + } + public async selectContinue() { + await this.page.locator(this.continue).click(); + + } + public async selectFinish() { + await this.page.locator(this.finish).click(); + + } + public async validateFinalMessage(ThankyouMsg: string) { + const finalMsg = await this.page.locator(this.thankyouMessage); + await expect(finalMsg).toBeVisible(); + await expect(finalMsg).toContainText(ThankyouMsg); + + } + public async validateProductsAreSortedByPrice(sort: string) { + const priceTexts = await this.page.locator(this.itemPrices).allTextContents(); + const actualPrices = priceTexts.map(price => Number(price.replace('$', '').trim())); + await expect(actualPrices.length).toBe(6); + const expectedPrices = [...actualPrices].sort((a, b) => + sort === 'hilo' ? b - a : a - b + ); + + await expect(actualPrices).toEqual(expectedPrices); + + } + + public async validateCartBadgeCount(expectedCount: string) { + const cartBadge = this.page.locator(this.cartBadge); + await expect(cartBadge).toBeVisible(); + await expect(cartBadge).toHaveText(expectedCount); + } + + } \ No newline at end of file diff --git a/playwrightUtilities.ts b/playwrightUtilities.ts index 93244a2..0a4d514 100644 --- a/playwrightUtilities.ts +++ b/playwrightUtilities.ts @@ -1,4 +1,5 @@ import { Browser, chromium, Page } from 'playwright'; +import { faker } from '@faker-js/faker'; let browser: Browser | null = null; let page: Page | null = null; @@ -30,4 +31,19 @@ export const closeBrowser = async () => { browser = null; page = null; } -}; \ No newline at end of file +}; + +export const getRandomFirstName = ():string =>{ + return faker.person.firstName(); +} + +export const getRandomLastName = ():string =>{ + return faker.person.lastName(); +} + +export const getRandomZipCode = ():string =>{ + return faker.location.zipCode("#####"); +} + + + diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d8..f789e1f 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 error message {string}', async (ErrorMessage) => { + await new Login(getPage()).validateErrorMessage(ErrorMessage); +}); + +Then('I should be navigated to the products page', async () => { + await new Login(getPage()).validateSuccessfulLogin(); }); \ No newline at end of file diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb9..c42ea3d 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -4,4 +4,15 @@ 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 items by {string}', async (sort)=> { + await new Product(getPage()).sortItemsByPrice(sort); +}); + + Then('I should see all products sorted by price {string}', async (sort) => { + await new Product(getPage()).validateProductsAreSortedByPrice(sort); +}); + + + diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 0000000..023aae2 --- /dev/null +++ b/steps/purchase.steps.ts @@ -0,0 +1,36 @@ +import { Then } from '@cucumber/cucumber'; +import { getPage } from '../playwrightUtilities'; +import { Product } from '../pages/product.page'; + + +Then('I will select cart on the top-right', async () => { + await new Product(getPage()).selectCart(); +}); + +Then('I will select Checkout', async () => { + await new Product(getPage()).selectCheckout(); +}); + + +Then('I will Fill in the First Name, Last Name and Zip\\/Postal Code', async () => { + await new Product(getPage()).fillInTheDetails(); +}); + +Then('select continue', async () => { + await new Product(getPage()).selectContinue(); +}); + + +Then('select finish', async () => { + await new Product(getPage()).selectFinish(); +}); + + +Then('validate the text {string}', async (expectedMsg) => { + await new Product(getPage()).validateFinalMessage(expectedMsg); +}); + +Then('I should see the cart badge count as {string}', async (count)=> { + await new Product(getPage()).validateCartBadgeCount(count); + +});