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
9 changes: 7 additions & 2 deletions features/login.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ 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
# 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 successful login
Then I will login as 'standard_user'
Then I should be redirected to the products page
7 changes: 6 additions & 1 deletion features/product.feature
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ Feature: Product Feature
Scenario Outline: Validate product sort by price <sort>
Then I will login as 'standard_user'
# TODO: Sort the items by <sort>

Then I will sort the items by "<sort>"
# TODO: Validate all 6 items are sorted correctly by price
Then I will validate all 6 items are sorted correctly by price "<sort>"
Examples:
# TODO: extend the datatable to paramterize this test
| sort |
| sort |
| Price (low to high) |
| Price (high to low) |
9 changes: 8 additions & 1 deletion features/purchase.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ 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 will validate Cart Badge shows 1 item
# TODO: Select the cart (top-right)
Then I will click on the cart icon
# TODO: Select Checkout
Then I will click on the checkout button
# TODO: Fill in the First Name, Last Name, and Zip/Postal Code
Then I will Fill in the First Name, Last Name, and Zip Code
# TODO: Select Continue
Then I will click on the continue button
# TODO: Select Finish
# TODO: Validate the text 'Thank you for your order!'
Then I will click on the finish button
# TODO: Validate the text 'Thank you for your order!'
Then I should see the text 'Thank you for your order!'
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.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
"devDependencies": {
"@cucumber/cucumber": "^10.0.1",
"@cucumber/pretty-formatter": "^1.0.0",
"@playwright/test": "^1.40.1",
"@playwright/test": "^1.62.1",
"@types/node": "^20.10.3",
"cucumber-html-reporter": "^7.1.1",
"playwright": "^1.62.1",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
}
Expand Down
58 changes: 40 additions & 18 deletions pages/login.page.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import { Page } from "@playwright/test"
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 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 = 'h3[data-test="error"]';

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

public async validateTitle(expectedTitle: string) {
const pageTitle = await this.page.title();
if (pageTitle !== expectedTitle) {
throw new Error(
`Expected title to be ${expectedTitle} but found ${pageTitle}`,
);
}
}

public async loginAsUser(userName: string) {
await this.page.locator(this.userNameField).fill(userName);
await this.page.locator(this.passwordField).fill(this.password);
await this.page.locator(this.loginButton).click();
}

public async validateTitle(expectedTitle: string) {
const pageTitle = await this.page.title();
if (pageTitle !== expectedTitle) {
throw new Error(`Expected title to be ${expectedTitle} but found ${pageTitle}`);
}
public async validateErrorMessage(expectedErrorMessage: string) {
const errorMessageLocator = this.page.locator(this.errorMessage);
const actualErrorMessage = await errorMessageLocator.textContent();
if (actualErrorMessage?.trim() !== expectedErrorMessage) {
throw new Error(
`Expected error message to be "${expectedErrorMessage}" but found "${actualErrorMessage?.trim()}"`,
);
}
}

public async loginAsUser(userName: string) {
await this.page.locator(this.userNameField).fill(userName)
await this.page.locator(this.passwordField).fill(this.password)
await this.page.locator(this.loginButton).click()
public async validateSuccessfulLogin() {
const url = this.page.url();
if (!url.includes("/inventory.html")) {
throw new Error(
`Expected to be redirected to the products page, but current URL is ${url}`,
);
}
}
}
}
56 changes: 48 additions & 8 deletions pages/product.page.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
import { Page } from "@playwright/test"
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 page: Page;
private readonly addToCart: string =
'button[id="add-to-cart-sauce-labs-backpack"]';
private readonly sortBy: string =
'select[data-test="product-sort-container"]';

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

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

public async sortItemsBy(sort: string) {
let sortValue: string;
if (sort === "Price (low to high)") {
sortValue = "lohi";
} else if (sort === "Price (high to low)") {
sortValue = "hilo";
} else {
throw new Error(`Invalid sort option: ${sort}`);
}
await this.page.selectOption(this.sortBy, sortValue);
}

public async validateItemsAreSortedByPrice(sort: string) {
const prices = await this.page.$$eval(
'div[class="inventory_item_price"]',
(elements) =>
elements.map((el) =>
parseFloat(el.textContent?.replace("$", "") || "0"),
),
);

let sortedPrices;
if (sort === "Price (low to high)") {
sortedPrices = [...prices].sort((a, b) => a - b);
} else if (sort === "Price (high to low)") {
sortedPrices = [...prices].sort((a, b) => b - a);
} else {
throw new Error(`Invalid sort option: ${sort}`);
}

public async addBackPackToCart() {
await this.page.locator(this.addToCart).click()
if (JSON.stringify(prices) !== JSON.stringify(sortedPrices)) {
throw new Error(
`Expected items to be sorted by price, but got ${JSON.stringify(prices)}`,
);
}
}
}
}
64 changes: 64 additions & 0 deletions pages/purchase.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Page } from "@playwright/test";

export class Purchase {
private readonly page: Page;
private readonly cartIcon: string = 'a[class="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 postalCodeField: string = 'input[id="postal-code"]';
private readonly continueButton: string = 'input[id="continue"]';
private readonly finishButton: string = 'button[id="finish"]';
private readonly thankYouMessage: string = 'h2[class="complete-header"]';
private readonly cartBadge: string = 'span[class="shopping_cart_badge"]';

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

public async validateCartBadgeShowsItems(expectedItems: number) {
const cartBadge = await this.page.locator(this.cartBadge).textContent();
if (cartBadge !== expectedItems.toString()) {
throw new Error(
`Expected cart badge to show "${expectedItems}" items, but got "${cartBadge}"`,
);
}
}

public async clickOnCartIcon() {
await this.page.locator(this.cartIcon).click();
}

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

public async fillInCheckoutInformation(
firstName: string,
lastName: string,
postalCode: string,
) {
await this.page.fill(this.firstNameField, firstName);
await this.page.fill(this.lastNameField, lastName);
await this.page.fill(this.postalCodeField, postalCode);
}

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

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

public async validateThankYouMessage() {
const thankYouMessage = await this.page
.locator(this.thankYouMessage)
.textContent();
if (thankYouMessage !== "Thank you for your order!") {
throw new Error(
`Expected thank you message to be "Thank you for your order!", but got "${thankYouMessage}"`,
);
}
}
}
23 changes: 17 additions & 6 deletions steps/login.steps.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
import { Then } from '@cucumber/cucumber';
import { getPage } from '../playwrightUtilities';
import { Login } from '../pages/login.page';
import { Then } from "@cucumber/cucumber";
import { getPage } from "../playwrightUtilities";
import { Login } from "../pages/login.page";

Then('I should see the title {string}', async (expectedTitle) => {
Then("I should see the title {string}", async (expectedTitle) => {
await new Login(getPage()).validateTitle(expectedTitle);
});

Then('I will login as {string}', async (userName) => {
Then("I will login as {string}", async (userName) => {
await new Login(getPage()).loginAsUser(userName);
});
});

Then(
"I should see the error message {string}",
async (expectedErrorMessage) => {
await new Login(getPage()).validateErrorMessage(expectedErrorMessage);
},
);

Then("I should be redirected to the products page", async () => {
await new Login(getPage()).validateSuccessfulLogin();
});
Loading
Loading