Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,16 @@ node >= v18.5.x

npm >= v7


## Setup

// Install Visual Studio Code (or any editor)

https://code.visualstudio.com/download


// Install Node.js

https://nodejs.org/en/download


```bash
git clone https://github.com/automationExamples/Playwright-Cucumber-Exercise.git
npm install
Expand All @@ -31,28 +28,31 @@ Cucumber v1.7.0

Cucumber (Gherkin) Support enhanced for Behat


## Instructions

To run the test

```bash
npm run test
```

After running, to generate the cucumber report (cucumber_report.html)

```bash
npm run report
```

It is not expected that you complete every task, however, please give your best effort
It is not expected that you complete every task, however, please give your best effort

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

### 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
5 changes: 5 additions & 0 deletions data/userDetails.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"firstName": "Dmitry",
"lastName": "Ivanov",
"ZIP": "28277"
}
8 changes: 6 additions & 2 deletions features/login.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ 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
Then I should see the error message "Epic sadface: Sorry, this user has been locked out."

Scenario: Validate login with invalid credentials
Then I will login as 'invalid_user'
Then I should see the error message "Epic sadface: Username and password do not match any user in this service"
25 changes: 17 additions & 8 deletions features/product.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ 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 <sort>
Then I will login as 'standard_user'
# TODO: Sort the items by <sort>
# TODO: Validate all 6 items are sorted correctly by price
Examples:
# TODO: extend the datatable to paramterize this test
| sort |
Scenario Outline: Validate product sort by price <sort>
Then I will login as 'standard_user'
Then I sort items by "<sort>"
Then items should be sorted by price "<order>"
Examples:
| sort | order |
| Price (low to high) | asc |
| Price (high to low) | desc |

Scenario Outline: Validate product sort by name <sort>
Then I will login as 'standard_user'
Then I sort items by "<sort>"
Then items should be sorted by name "<order>"
Examples:
| sort | order |
| Name (A to Z) | asc |
| Name (Z to A) | desc |
16 changes: 8 additions & 8 deletions features/purchase.feature
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ Feature: Purchase Feature
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!'
Then I will login as 'standard_user'
Then I will add the backpack to the cart
Then Select the cart (top-right)
Then Click on Checkout button
Then I fill out checkout form
Then I click on continue
Then I click on Finish button
Then I have to validate success message
36 changes: 20 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"devDependencies": {
"@cucumber/cucumber": "^10.0.1",
"@cucumber/pretty-formatter": "^1.0.0",
"@playwright/test": "^1.40.1",
"@playwright/test": "^1.58.2",
"@types/node": "^20.10.3",
"cucumber-html-reporter": "^7.1.1",
"ts-node": "^10.9.1",
Expand Down
39 changes: 39 additions & 0 deletions pages/category.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Page } from "@playwright/test"

export class Category {
private readonly page: Page
private readonly sortDropdown: string = '[data-test="product-sort-container"]'
private readonly itemPrices: string = '[data-test="inventory-item-price"]'

constructor(page: Page) {
this.page = page;
}

public async sortBy(option: string) {
await this.page.locator(this.sortDropdown).selectOption({ label: option });
}

public async validatePriceSort(order: string) {
const priceTexts = await this.page.locator(this.itemPrices).allTextContents();
const prices = priceTexts.map(p => parseFloat(p.replace('$', '')));

const sorted = order === 'asc'
? [...prices].sort((a, b) => a - b)
: [...prices].sort((a, b) => b - a);

if (JSON.stringify(prices) !== JSON.stringify(sorted)) {
throw new Error(`Expected prices [${sorted}] but found [${prices}]`);
}
}

public async validateNameSort(order: string) {
const names = await this.page.locator('[data-test="inventory-item-name"]').allTextContents();
const sorted = order === 'asc'
? [...names].sort()
: [...names].sort().reverse();

if (JSON.stringify(names) !== JSON.stringify(sorted)) {
throw new Error(`Expected names [${sorted}] but found [${names}]`);
}
}
}
52 changes: 52 additions & 0 deletions pages/checkout.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Page } from "@playwright/test"
import userData from '../data/userDetails.json'

export class Checkout {
private readonly page: Page
private readonly headerCartIcon: string = '[data-test="shopping-cart-link"]'
private readonly checkoutButton: string = '[data-test="checkout"]'
private readonly firstName: string = '[data-test="firstName"]'
private readonly lastName: string = '[data-test="lastName"]'
private readonly zip: string = '[data-test="postalCode"]'
private readonly checkoutContinue: string = '[data-test="continue"]'
private readonly finishButton: string = '[data-test="finish"]'
private readonly orderSuccessMessage: string = '[data-test="complete-header"]'


constructor(page: Page) {
this.page = page;
}

public async goToCart() {
await this.page.locator(this.headerCartIcon).click()
}

public async goToCheckout() {
await this.page.locator(this.checkoutButton).click()
}

public async fillOutCheckoutForm() {
await this.page.locator(this.firstName).fill(userData.firstName)
await this.page.locator(this.lastName).fill(userData.lastName)
await this.page.locator(this.zip).fill(userData.ZIP)
}

public async checkoutClickContinue() {
await this.page.locator(this.checkoutContinue).click()
}

public async checkoutClickFinish() {
await this.page.locator(this.finishButton).click()
}

public async orderSuccess() {
await this.page.locator(this.orderSuccessMessage)
const expectedMessage = 'Thank you for your order!';
const actualMessage = await this.page.locator(this.orderSuccessMessage).textContent()
if (actualMessage?.trim() !== expectedMessage) {
throw new Error(`Expected "${expectedMessage}" but found "${actualMessage}"`)
}
}


}
14 changes: 13 additions & 1 deletion pages/login.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ 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"]'

constructor(page: Page) {
this.page = page;
}

public async validateTitle(expectedTitle: string) {
const pageTitle = await this.page.title();
if (pageTitle !== expectedTitle) {
/**
* // This change is not necessary,
* but, if register isn't important it's better to check use toLowerCase() to avoid possible issues
*/
if (pageTitle.toLowerCase() !== expectedTitle.toLowerCase()) {
throw new Error(`Expected title to be ${expectedTitle} but found ${pageTitle}`);
}
}
Expand All @@ -23,4 +28,11 @@ export class Login {
await this.page.locator(this.passwordField).fill(this.password)
await this.page.locator(this.loginButton).click()
}

public async validateErrorMessage(expectedMessage: string) {
const actualMessage = await this.page.locator(this.errorMessage).textContent();
if (actualMessage !== expectedMessage) {
throw new Error(`Expected error "${expectedMessage}" but found "${actualMessage}"`);
}
}
}
15 changes: 15 additions & 0 deletions steps/category.steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Then } from '@cucumber/cucumber';
import { getPage } from '../playwrightUtilities';
import { Category } from '../pages/category.page';

Then('I sort items by {string}', async (sort: string) => {
await new Category(getPage()).sortBy(sort);
});

Then('items should be sorted by price {string}', async (order: string) => {
await new Category(getPage()).validatePriceSort(order);
});

Then('items should be sorted by name {string}', async (order: string) => {
await new Category(getPage()).validateNameSort(order);
});
Loading
Loading