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
4 changes: 2 additions & 2 deletions features/login.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ 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."
21 changes: 14 additions & 7 deletions features/product.feature
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ 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
Scenario Outline: Validate product sort by price <sort>
Then I will login as 'standard_user'
Then I will sort products by <sort>
Then I should validate all products are sorted by <sort>

Examples:
# TODO: extend the datatable to paramterize this test
| sort |
| sort |
| Price (low to high) |
| Price (high to low) |
| Name (A to Z) |
| Name (Z to A) |

Scenario: Validate add backpack to cart
Then I will login as 'standard_user'
Then I will add the backpack to the cart
12 changes: 6 additions & 6 deletions features/purchase.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Feature: Purchase Feature
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 open the cart
Then I will checkout
Then I will fill checkout details with "Sufia" "Tester" "12345"
Then I will continue checkout
Then I will finish checkout
Then I should see the purchase confirmation "Thank you for your order!"
2 changes: 1 addition & 1 deletion package-lock.json

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

12 changes: 10 additions & 2 deletions pages/login.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ export class Login {
private readonly loginButton: string = 'input[id="login-button"]'

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

public async validateTitle(expectedTitle: string) {
const pageTitle = await this.page.title();
if (pageTitle !== expectedTitle) {
Expand All @@ -23,4 +23,12 @@ 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 errorElement = await this.page.locator('[data-test="error"]');
const actualMessage = await errorElement.textContent();
if (!actualMessage?.includes(expectedMessage)) {
throw new Error(`Expected error message "${expectedMessage}" but found "${actualMessage}"`);
}
}
}
55 changes: 55 additions & 0 deletions pages/product.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ 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 = '[data-testid="product-sort-container"]'
private readonly productPrices: string = '[data-testid="inventory-item-price"]'
private readonly productNames: string = '[data-testid="inventory-item-name"]'

constructor(page: Page) {
this.page = page;
Expand All @@ -11,4 +14,56 @@ export class Product {
public async addBackPackToCart() {
await this.page.locator(this.addToCart).click()
}

public async sortProductsBy(sortOption: string) {
await this.page.locator(this.sortDropdown).selectOption(sortOption.toLowerCase().replace(/\s+/g, '_'))
// Wait for products to be resorted
await this.page.waitForTimeout(500)
}

public async getProductPrices(): Promise<number[]> {
const priceElements = await this.page.locator(this.productPrices).allTextContents()
return priceElements.map(price => parseFloat(price.replace('$', '')))
}

public async getProductNames(): Promise<string[]> {
return await this.page.locator(this.productNames).allTextContents()
}

public async validateProductsSortedByPrice(sortOrder: 'asc' | 'desc') {
const prices = await this.getProductPrices()
const sortedPrices = [...prices].sort((a, b) => sortOrder === 'asc' ? a - b : b - a)

for (let i = 0; i < prices.length; i++) {
if (prices[i] !== sortedPrices[i]) {
throw new Error(
`Products not sorted correctly by price (${sortOrder}). ` +
`Expected: [${sortedPrices}] but got: [${prices}]`
)
}
}
}

public async validateProductsSortedByName(sortOrder: 'asc' | 'desc') {
const names = await this.getProductNames()
const sortedNames = [...names].sort((a, b) => sortOrder === 'asc' ? a.localeCompare(b) : b.localeCompare(a))

for (let i = 0; i < names.length; i++) {
if (names[i] !== sortedNames[i]) {
throw new Error(
`Products not sorted correctly by name (${sortOrder}). ` +
`Expected: [${sortedNames}] but got: [${names}]`
)
}
}
}

public async validateProductCountIs(expectedCount: number) {
const actualCount = await this.page.locator(this.productNames).count()
if (actualCount !== expectedCount) {
throw new Error(
`Expected ${expectedCount} products but found ${actualCount}`
)
}
}
}
56 changes: 56 additions & 0 deletions pages/purchase.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Page } from "@playwright/test"

export class Purchase {
private readonly page: Page
private readonly addToCart: string = 'button[id="add-to-cart-sauce-labs-backpack"]'
private readonly cartLink: string = 'a.shopping_cart_link'
private readonly checkoutButton: string = 'button[id="checkout"]'
private readonly firstNameField: string = 'input[id="first-name"]'
private readonly lastNameField: string = 'input[id="last-name"]'
private readonly postalField: string = 'input[id="postal-code"]'
private readonly continueButton: string = 'input[id="continue"]'
private readonly finishButton: string = 'button[id="finish"]'
private readonly completeHeader: string = '.complete-header'

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

public async addBackpackToCart() {
await this.page.locator(this.addToCart).click()
}

public async openCart() {
await this.page.locator(this.cartLink).click()
}

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

public async fillCheckoutDetails(firstName: string, lastName: string, postalCode: string) {
await this.page.locator(this.firstNameField).fill(firstName)
await this.page.locator(this.lastNameField).fill(lastName)
await this.page.locator(this.postalField).fill(postalCode)
}

public async continueCheckout() {
await this.page.locator(this.continueButton).click()
}

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

public async getConfirmationHeader(): Promise<string> {
const t = await this.page.locator(this.completeHeader).textContent()
return t ? t.trim() : ''
}

public async validateConfirmation(expected: string) {
const actual = await this.getConfirmationHeader()
if (!actual.toLowerCase().includes(expected.toLowerCase())) {
throw new Error(`Expected confirmation to include "${expected}" but found "${actual}"`)
}
}
}
4 changes: 4 additions & 0 deletions steps/login.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ 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 the error message {string}', async (expectedMessage) => {
await new Login(getPage()).validateErrorMessage(expectedMessage);
});
22 changes: 22 additions & 0 deletions steps/product.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,26 @@ import { Product } from '../pages/product.page';

Then('I will add the backpack to the cart', async () => {
await new Product(getPage()).addBackPackToCart();
});

Then('I will sort products by {string}', async (sortOption: string) => {
await new Product(getPage()).sortProductsBy(sortOption);
});

Then('I should validate all products are sorted by {string}', async (sortOption: string) => {
const product = new Product(getPage());

// Validate 6 products are present
await product.validateProductCountIs(6);

// Validate sorting based on the option
if (sortOption.toLowerCase().includes('price') && sortOption.toLowerCase().includes('low')) {
await product.validateProductsSortedByPrice('asc');
} else if (sortOption.toLowerCase().includes('price') && sortOption.toLowerCase().includes('high')) {
await product.validateProductsSortedByPrice('desc');
} else if (sortOption.toLowerCase().includes('name') && sortOption.toLowerCase().includes('z to a')) {
await product.validateProductsSortedByName('desc');
} else if (sortOption.toLowerCase().includes('name') && sortOption.toLowerCase().includes('a to z')) {
await product.validateProductsSortedByName('asc');
}
});
27 changes: 27 additions & 0 deletions steps/purchase.steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Then } from '@cucumber/cucumber';
import { getPage } from '../playwrightUtilities';
import { Purchase } from '../pages/purchase.page';

Then('I will open the cart', async () => {
await new Purchase(getPage()).openCart();
});

Then('I will checkout', async () => {
await new Purchase(getPage()).checkout();
});

Then('I will fill checkout details with {string} {string} {string}', async (firstName: string, lastName: string, postalCode: string) => {
await new Purchase(getPage()).fillCheckoutDetails(firstName, lastName, postalCode);
});

Then('I will continue checkout', async () => {
await new Purchase(getPage()).continueCheckout();
});

Then('I will finish checkout', async () => {
await new Purchase(getPage()).finishCheckout();
});

Then('I should see the purchase confirmation {string}', async (expectedText: string) => {
await new Purchase(getPage()).validateConfirmation(expectedText);
});
Loading