From 7f9d746bdb0654c51618a70273e72397d5c99067 Mon Sep 17 00:00:00 2001 From: B <2485336@cognizant.com> Date: Tue, 19 May 2026 15:39:44 +0530 Subject: [PATCH] Completed Playwright Cucumber assessment - PraveenKumarB --- features/login.feature | 5 +- features/product.feature | 6 +- features/purchase.feature | 9 + package-lock.json | 11 +- package.json | 2 +- pages/login.page.ts | 7 +- pages/product.page.ts | 13 + pages/purchase.page.ts | 38 +++ reports/cucumber-reportc.json | 456 ++++++++++++++++++++++++++++++++++ steps/login.steps.ts | 8 +- steps/product.steps.ts | 18 +- steps/purchase.steps.ts | 25 ++ 12 files changed, 586 insertions(+), 12 deletions(-) create mode 100644 pages/purchase.page.ts create mode 100644 reports/cucumber-reportc.json create mode 100644 steps/purchase.steps.ts diff --git a/features/login.feature b/features/login.feature index fb9f1fa5..d8d575a6 100644 --- a/features/login.feature +++ b/features/login.feature @@ -5,8 +5,9 @@ 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 the error message "Epic sadface: Sorry, this user has been locked out." diff --git a/features/product.feature b/features/product.feature index 8a7ceab9..f914a3b9 100644 --- a/features/product.feature +++ b/features/product.feature @@ -8,6 +8,10 @@ Feature: Product Feature Then I will login as 'standard_user' # TODO: Sort the items by # TODO: Validate all 6 items are sorted correctly by price + And I sort the products by "" + Then products should be sorted correctly by "" 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) | \ No newline at end of file diff --git a/features/purchase.feature b/features/purchase.feature index 28634789..0cb86285 100644 --- a/features/purchase.feature +++ b/features/purchase.feature @@ -6,6 +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 + Then I select the cart + Then I select the checkout + Then I fill the checkout details + Then I select continue + Then I select finish + Then I should see the text "Thank you for your order!" + + + # TODO: Select the cart (top-right) # TODO: Select Checkout # TODO: Fill in the First Name, Last Name, and Zip/Postal Code diff --git a/package-lock.json b/package-lock.json index b90d7c6c..7e5d5fce 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": { @@ -9,7 +9,7 @@ "@cucumber/pretty-formatter": "^1.0.0", "@playwright/test": "^1.40.1", "@types/node": "^20.10.3", - "cucumber-html-reporter": "^7.1.1", + "cucumber-html-reporter": "^7.2.0", "ts-node": "^10.9.1", "typescript": "^5.3.2" } @@ -810,10 +810,11 @@ } }, "node_modules/cucumber-html-reporter": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/cucumber-html-reporter/-/cucumber-html-reporter-7.1.1.tgz", - "integrity": "sha512-v1OUqM2aSoC27Dt/mUJfh0uxiyUWjfl/9wu7HqHpeT8ojZbTepPpmvYCmM84VGOU5LnyqGclab00mchetzqEEQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/cucumber-html-reporter/-/cucumber-html-reporter-7.2.0.tgz", + "integrity": "sha512-vThFFjzEUKeRiAZbWgfvjUjc3nV7kcDW1yU3bWCgHvcpc9dDIdZvaaOIqi4GxTCQzo4vgCK+8ZwbVT/4u35GeQ==", "dev": true, + "license": "MIT", "dependencies": { "@cucumber/cucumber": "9.1.2", "chalk": "^2.4.2", diff --git a/package.json b/package.json index ed38f6f5..957c2b8b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@cucumber/pretty-formatter": "^1.0.0", "@playwright/test": "^1.40.1", "@types/node": "^20.10.3", - "cucumber-html-reporter": "^7.1.1", + "cucumber-html-reporter": "^7.2.0", "ts-node": "^10.9.1", "typescript": "^5.3.2" } diff --git a/pages/login.page.ts b/pages/login.page.ts index 5a01614b..5bbd0e8d 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -1,11 +1,11 @@ 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"]' + private readonly errorMessage: string ='[data-test="error"]' constructor(page: Page) { this.page = page; @@ -23,4 +23,9 @@ export class Login { await this.page.locator(this.passwordField).fill(this.password) await this.page.locator(this.loginButton).click() } + public async getErrorMessage(){ + return await this.page + .locator(this.errorMessage) + .textContent(); + } } \ No newline at end of file diff --git a/pages/product.page.ts b/pages/product.page.ts index 14bedb1b..3e1519f9 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 sortDropdown : string ='.product_sort_container' + private readonly productPrices : string = '.inventory_item_price' constructor(page: Page) { this.page = page; @@ -11,4 +13,15 @@ export class Product { public async addBackPackToCart() { await this.page.locator(this.addToCart).click() } + public async sortProducts(sortOption: string){ + await this.page.selectOption(this.sortDropdown, + {label: sortOption}) + } + public async getAllProductPrices(){ + const prices = await this.page + .locator(this.productPrices) + .allTextContents(); + return prices.map(price => + parseFloat(price.replace('$','')) + )} } \ No newline at end of file diff --git a/pages/purchase.page.ts b/pages/purchase.page.ts new file mode 100644 index 00000000..3893bd4e --- /dev/null +++ b/pages/purchase.page.ts @@ -0,0 +1,38 @@ +import {Page} from "@playwright/test"; + +export class Purchase { + private readonly page: Page + private readonly cartButton: string ='.shopping_cart_link' + private readonly checkoutButton: string ='#checkout' + private readonly firstName: string ='#first-name' + private readonly lastName: string ='#last-name' + private readonly postalCode: string ='#postal-code' + private readonly continueButton: string ='#continue' + private readonly finishButton: string = '#finish' + private readonly successMessage: string = '.complete-header' + + constructor(page: Page){ + this.page = page; + } + public async openCart(){ + await this.page.locator(this.cartButton).click(); + } + public async clickCheckout(){ + await this.page.locator(this.checkoutButton).click(); + } + public async fillCheckoutDetails(){ + await this.page.locator(this.firstName).fill('Praveen'); + await this.page.locator(this.lastName).fill('Kumar'); + await this.page.locator(this.postalCode).fill('600001'); + } + public async clickContinue(){ + await this.page.locator(this.continueButton).click(); + } + public async clickFinish(){ + await this.page.locator(this.finishButton).click(); + } + public async getSuccessMessage(){ + return await this.page.locator(this.successMessage) + .textContent(); + } +} \ No newline at end of file diff --git a/reports/cucumber-reportc.json b/reports/cucumber-reportc.json new file mode 100644 index 00000000..88100d88 --- /dev/null +++ b/reports/cucumber-reportc.json @@ -0,0 +1,456 @@ +[ + { + "description": "", + "elements": [ + { + "description": "", + "id": "login-feature;validate-the-login-page-title", + "keyword": "Scenario", + "line": 6, + "name": "Validate the login page title", + "steps": [ + { + "keyword": "Before", + "hidden": true, + "result": { + "status": "passed", + "duration": 3932905699 + } + }, + { + "arguments": [], + "keyword": "Given ", + "line": 4, + "name": "I open the \"https://www.saucedemo.com/\" page", + "match": { + "location": "steps\\common.steps.ts:4" + }, + "result": { + "status": "passed", + "duration": 2185487900 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 8, + "name": "I should see the title \"Swag Labs\"", + "match": { + "location": "steps\\login.steps.ts:6" + }, + "result": { + "status": "passed", + "duration": 17002199 + } + }, + { + "keyword": "After", + "hidden": true, + "result": { + "status": "passed", + "duration": 651412099 + } + } + ], + "tags": [], + "type": "scenario" + }, + { + "description": "", + "id": "login-feature;validate-login-error-message", + "keyword": "Scenario", + "line": 10, + "name": "Validate login error message", + "steps": [ + { + "keyword": "Before", + "hidden": true, + "result": { + "status": "passed", + "duration": 3605312899 + } + }, + { + "arguments": [], + "keyword": "Given ", + "line": 4, + "name": "I open the \"https://www.saucedemo.com/\" page", + "match": { + "location": "steps\\common.steps.ts:4" + }, + "result": { + "status": "passed", + "duration": 602339299 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 11, + "name": "I will login as 'locked_out_user'", + "match": { + "location": "steps\\login.steps.ts:10" + }, + "result": { + "status": "passed", + "duration": 219704999 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 13, + "name": "I should see the error message \"Epic sadface: Sorry, this user has been locked out.\"", + "match": { + "location": "steps\\login.steps.ts:13" + }, + "result": { + "status": "passed", + "duration": 12356999 + } + }, + { + "keyword": "After", + "hidden": true, + "result": { + "status": "passed", + "duration": 614390400 + } + } + ], + "tags": [], + "type": "scenario" + } + ], + "id": "login-feature", + "line": 1, + "keyword": "Feature", + "name": "Login Feature", + "tags": [], + "uri": "features\\login.feature" + }, + { + "description": "", + "elements": [ + { + "description": "", + "id": "product-feature;validate-product-sort-by-price-price-(low-to-high)", + "keyword": "Scenario Outline", + "line": 16, + "name": "Validate product sort by price Price (low to high)", + "steps": [ + { + "keyword": "Before", + "hidden": true, + "result": { + "status": "passed", + "duration": 3364666300 + } + }, + { + "arguments": [], + "keyword": "Given ", + "line": 4, + "name": "I open the \"https://www.saucedemo.com/\" page", + "match": { + "location": "steps\\common.steps.ts:4" + }, + "result": { + "status": "passed", + "duration": 675715200 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 8, + "name": "I will login as 'standard_user'", + "match": { + "location": "steps\\login.steps.ts:10" + }, + "result": { + "status": "passed", + "duration": 213295500 + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 11, + "name": "I sort the products by \"Price (low to high)\"", + "match": { + "location": "steps\\product.steps.ts:11" + }, + "result": { + "status": "passed", + "duration": 65222799 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 12, + "name": "products should be sorted correctly by \"Price (low to high)\"", + "match": { + "location": "steps\\product.steps.ts:14" + }, + "result": { + "status": "passed", + "duration": 22951699 + } + }, + { + "keyword": "After", + "hidden": true, + "result": { + "status": "passed", + "duration": 649625699 + } + } + ], + "tags": [], + "type": "scenario" + }, + { + "description": "", + "id": "product-feature;validate-product-sort-by-price-price-(high-to-low)", + "keyword": "Scenario Outline", + "line": 17, + "name": "Validate product sort by price Price (high to low)", + "steps": [ + { + "keyword": "Before", + "hidden": true, + "result": { + "status": "passed", + "duration": 4011116900 + } + }, + { + "arguments": [], + "keyword": "Given ", + "line": 4, + "name": "I open the \"https://www.saucedemo.com/\" page", + "match": { + "location": "steps\\common.steps.ts:4" + }, + "result": { + "status": "passed", + "duration": 576825999 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 8, + "name": "I will login as 'standard_user'", + "match": { + "location": "steps\\login.steps.ts:10" + }, + "result": { + "status": "passed", + "duration": 193810299 + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 11, + "name": "I sort the products by \"Price (high to low)\"", + "match": { + "location": "steps\\product.steps.ts:11" + }, + "result": { + "status": "passed", + "duration": 69960600 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 12, + "name": "products should be sorted correctly by \"Price (high to low)\"", + "match": { + "location": "steps\\product.steps.ts:14" + }, + "result": { + "status": "passed", + "duration": 19078700 + } + }, + { + "keyword": "After", + "hidden": true, + "result": { + "status": "passed", + "duration": 704353999 + } + } + ], + "tags": [], + "type": "scenario" + } + ], + "id": "product-feature", + "line": 1, + "keyword": "Feature", + "name": "Product Feature", + "tags": [], + "uri": "features\\product.feature" + }, + { + "description": "", + "elements": [ + { + "description": "", + "id": "purchase-feature;validate-successful-purchase-text", + "keyword": "Scenario", + "line": 6, + "name": "Validate successful purchase text", + "steps": [ + { + "keyword": "Before", + "hidden": true, + "result": { + "status": "passed", + "duration": 3405523199 + } + }, + { + "arguments": [], + "keyword": "Given ", + "line": 4, + "name": "I open the \"https://www.saucedemo.com/\" page", + "match": { + "location": "steps\\common.steps.ts:4" + }, + "result": { + "status": "passed", + "duration": 533609099 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 7, + "name": "I will login as 'standard_user'", + "match": { + "location": "steps\\login.steps.ts:10" + }, + "result": { + "status": "passed", + "duration": 279131199 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 8, + "name": "I will add the backpack to the cart", + "match": { + "location": "steps\\product.steps.ts:8" + }, + "result": { + "status": "passed", + "duration": 66300699 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 9, + "name": "I select the cart", + "match": { + "location": "steps\\purchase.steps.ts:6" + }, + "result": { + "status": "passed", + "duration": 79124499 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 10, + "name": "I select the checkout", + "match": { + "location": "steps\\purchase.steps.ts:9" + }, + "result": { + "status": "passed", + "duration": 94198300 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 11, + "name": "I fill the checkout details", + "match": { + "location": "steps\\purchase.steps.ts:12" + }, + "result": { + "status": "passed", + "duration": 110164600 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 12, + "name": "I select continue", + "match": { + "location": "steps\\purchase.steps.ts:15" + }, + "result": { + "status": "passed", + "duration": 96919700 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 13, + "name": "I select finish", + "match": { + "location": "steps\\purchase.steps.ts:18" + }, + "result": { + "status": "passed", + "duration": 64195200 + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 14, + "name": "I should see the text \"Thank you for your order!\"", + "match": { + "location": "steps\\purchase.steps.ts:21" + }, + "result": { + "status": "passed", + "duration": 7076100 + } + }, + { + "keyword": "After", + "hidden": true, + "result": { + "status": "passed", + "duration": 623887399 + } + } + ], + "tags": [], + "type": "scenario" + } + ], + "id": "purchase-feature", + "line": 1, + "keyword": "Feature", + "name": "Purchase Feature", + "tags": [], + "uri": "features\\purchase.feature" + } +] \ No newline at end of file diff --git a/steps/login.steps.ts b/steps/login.steps.ts index c2aa0d80..54da2dfb 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 { expect } from '@playwright/test'; Then('I should see the title {string}', async (expectedTitle) => { await new Login(getPage()).validateTitle(expectedTitle); @@ -8,4 +9,9 @@ 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 (expectedMessage)=>{ + const actualMessage = await new Login(getPage()).getErrorMessage(); + expect(actualMessage).toContain(expectedMessage); +}); + diff --git a/steps/product.steps.ts b/steps/product.steps.ts index bb52fb98..7f3e9f26 100644 --- a/steps/product.steps.ts +++ b/steps/product.steps.ts @@ -1,7 +1,23 @@ -import { Then } from '@cucumber/cucumber'; +import { When,Then } from '@cucumber/cucumber'; import { getPage } from '../playwrightUtilities'; import { Product } from '../pages/product.page'; +import { expect } from '@playwright/test'; + + Then('I will add the backpack to the cart', async () => { await new Product(getPage()).addBackPackToCart(); +}); +When('I sort the products by {string}', async (sortOption) => { + await new Product(getPage()).sortProducts(sortOption); +}); +Then('products should be sorted correctly by {string}', async (sortOption)=>{ + const prices = await new Product(getPage()).getAllProductPrices(); + let expectedPrices = [...prices]; + if (sortOption === 'Price (low to high)'){ + expectedPrices.sort((a,b) => a-b); + }else{ + expectedPrices.sort((a,b)=> b-a); + } + expect(prices).toEqual(expectedPrices); }); \ No newline at end of file diff --git a/steps/purchase.steps.ts b/steps/purchase.steps.ts new file mode 100644 index 00000000..bc39bbc0 --- /dev/null +++ b/steps/purchase.steps.ts @@ -0,0 +1,25 @@ +import { Then } from "@cucumber/cucumber"; +import { expect } from "@playwright/test"; +import { getPage } from "../playwrightUtilities"; +import { Purchase } from "../pages/purchase.page"; + +Then('I select the cart', async () =>{ + await new Purchase(getPage()).openCart(); +}); +Then('I select the checkout', async () => { + await new Purchase(getPage()).clickCheckout(); +}); +Then('I fill the checkout details', async () => { + await new Purchase(getPage()).fillCheckoutDetails(); +}); +Then('I select continue', async () => { + await new Purchase(getPage()).clickContinue(); +}); +Then('I select finish', async () => { + await new Purchase(getPage()).clickFinish(); +}); +Then('I should see the text {string}', async(expectedMessage) => { + const actualMessage = await new Purchase(getPage()).getSuccessMessage(); + + expect(actualMessage?.trim()).toBe(expectedMessage); +}); \ No newline at end of file