Skip to content

Commit bc82ccd

Browse files
committed
added new pages - modfied logic and added cart functionality test
1 parent 1ee11d0 commit bc82ccd

14 files changed

Lines changed: 241 additions & 43 deletions

File tree

common/navigationEnums/menuBarCategories/MenuBarCategories.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
export enum MenuBarCategories {
22
//================================================================
3-
//-----------------------------WOMEN------------------------------
4-
WOMEN_TOPS = 'Tops',
5-
WOMEN_BOTTOMS = 'Bottomd',
6-
//================================================================
7-
//-----------------------------MEN--------------------------------
8-
MEN_TOPS = 'Tops',
9-
MEN_BOTTOMS = 'Bottoms',
3+
//-----------------------------MEN&WOMEN--------------------------
4+
TOPS = 'Tops',
5+
BOTTOMS = 'Bottoms',
106
//================================================================
117
//-----------------------------GEAR-------------------------------
128
BAGS = 'Bags',

helpers/fixtures/customFixtures/CustomFixtures.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CheckoutShippingPage } from '../../../pages/checkoutPage/CheckOutShippi
77
import { CheckoutReviewAndPaymentPage } from '../../../pages/checkoutPage/CheckoutReviewAndPaymentPage';
88
import { ShoppingCartPage } from '../../../pages/cartPage/ShoppingCartPage';
99
import { WomenCategoryPage } from '../../../pages/women/WomenCategoryPage';
10+
import { ProductPage } from '../../../pages/productPage/ProductPage';
1011

1112
type MyFixtures = {
1213
loginPage: LoginPage;
@@ -19,6 +20,7 @@ type MyFixtures = {
1920
checkoutPaymentPage: CheckoutReviewAndPaymentPage;
2021
shoppingCartPage: ShoppingCartPage;
2122
womenCategoryPage: WomenCategoryPage;
23+
productPage: ProductPage;
2224
}
2325

2426
/**
@@ -30,44 +32,41 @@ export const test = base.extend<MyFixtures>({
3032
await loginPage.loadApp();
3133
await loginPage.login();
3234
await use(loginPage);
33-
await context.clearCookies()
3435
},
3536
loadApplication: async ({ page, context }, use) => {
3637
let lumaMainPage = new LumaMainPage(page);
3738
await lumaMainPage.loadApp();
3839
await use(lumaMainPage);
39-
await context.clearCookies();
4040
},
41-
loginPage: async ({ page, context }, use) => {
41+
loginPage: async ({ page }, use) => {
4242
await use(new LoginPage(page));
43-
await context.clearCookies();
4443
},
45-
lumaMainPage: async ({ page, context }, use) => {
44+
lumaMainPage: async ({ page }, use) => {
4645
await use(new LumaMainPage(page));
47-
await context.clearCookies();
4846
},
49-
createAnAccountPage: async ({ page, context }, use) => {
47+
createAnAccountPage: async ({ page }, use) => {
5048
await use(new CreateAnAccountPage(page));
51-
await context.clearCookies();
5249
},
53-
menCategoryPage: async ({ page, context }, use) => {
50+
menCategoryPage: async ({ page }, use) => {
5451
await use(new MenCategoryPage(page));
55-
await context.clearCookies();
5652
},
57-
checkoutShippingPage: async ({ page, context }, use) => {
53+
checkoutShippingPage: async ({ page }, use) => {
5854
await use(new CheckoutShippingPage(page))
59-
await context.clearCookies();
6055
},
61-
checkoutPaymentPage: async ({ page, context }, use) => {
56+
checkoutPaymentPage: async ({ page }, use) => {
6257
await use(new CheckoutReviewAndPaymentPage(page))
63-
await context.clearCookies();
6458
},
65-
shoppingCartPage: async ({ page, context }, use) => {
59+
shoppingCartPage: async ({ page }, use) => {
6660
await use(new ShoppingCartPage(page));
67-
await context.clearCookies();
6861
},
69-
womenCategoryPage: async ({ page, context }, use) => {
62+
womenCategoryPage: async ({ page }, use) => {
7063
await use(new WomenCategoryPage(page));
71-
await context.clearCookies();
72-
}
73-
})
64+
},
65+
productPage: async ({ page }, use) => {
66+
await use(new ProductPage(page));
67+
},
68+
})
69+
70+
test.afterEach(async ({ context }) => {
71+
await context.clearCookies();
72+
});

helpers/optionalParamsInterfaces/OptionalParams.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export interface ProductItemOptionalParamsInterface {
2222
validatePrice?: boolean,
2323
price?: string,
2424
addItemToCart?: boolean,
25+
modifyQuantity?: boolean,
26+
quantity?: string,
2527
}
2628

2729
export interface ClientSideValiationErrorOptionalParamsInterface {

pages/BasePage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class BasePage {
6363
await locatorElement.clear();
6464
}
6565

66-
public async countElement(locator: (string | Locator)) {
66+
public async countElements(locator: (string | Locator)) {
6767
const locatorElement = await this.getTypeOfLocator(locator);
6868
const locatorCount = await locatorElement.count();
6969
return locatorCount;
@@ -86,7 +86,7 @@ export class BasePage {
8686
}
8787

8888
public async getPageUrl() {
89-
const pageUrl = this.page.url;
89+
const pageUrl = this.page.url();
9090
return pageUrl;
9191
}
9292

pages/LumaMainPage.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class LumaMainPage extends BasePage {
3131

3232
public async chooseMenuBarOption(menuBarItem: MenuBar) {
3333
let menuBarValue = this.page.locator(this.navigationMenuBar, { hasText: new RegExp(`^\\${menuBarItem.valueOf()}\\b$`, 'i') });
34-
if (menuBarItem === 'Women' || menuBarItem === 'Men' || menuBarItem === 'Training') {
34+
if (menuBarItem === 'Women' || menuBarItem === 'Men' || menuBarItem === 'Training' || menuBarItem === 'Gear') {
3535
await this.hover(menuBarValue);
3636
} else {
3737
await this.clickElement(menuBarValue);
@@ -169,7 +169,7 @@ export class LumaMainPage extends BasePage {
169169
*/
170170
public async countShoppingCartItems(expectedCount: number) {
171171
const cartItems = this.page.locator(this.cartItemListLocator);
172-
const itemCount = await this.countElement(cartItems);
172+
const itemCount = await this.countElements(cartItems);
173173
expect(itemCount).toBe(expectedCount);
174174
}
175175

@@ -211,7 +211,7 @@ export class LumaMainPage extends BasePage {
211211
*/
212212
public async handleClientSideValidationErrors(expectedCount: number, inputFieldsLocator: Locator[], options?: ClientSideValiationErrorOptionalParamsInterface) {
213213
const cliendSideValidationError = this.page.locator(this.clientSideValidationErrorLocator);
214-
const validationErrorsCount = await this.countElement(cliendSideValidationError);
214+
const validationErrorsCount = await this.countElements(cliendSideValidationError);
215215
expect(validationErrorsCount).toBe(expectedCount);
216216
const inputFields = await this.getInputFieldsValues(inputFieldsLocator);
217217
const emptyFieldIndexes: number[] = [];
@@ -314,8 +314,32 @@ export class LumaMainPage extends BasePage {
314314
expect(cellInnerText).toBe(expectedCellValue);
315315
}
316316

317+
/**
318+
* @description generic function to proceed to checkout - every page has this option
319+
*/
317320
public async proceedToCheckout() {
318321
const proceedToCheckoutButton = this.page.getByRole('button', { name: 'Proceed to Checkout' });
319322
await this.clickElement(proceedToCheckoutButton);
320323
}
321-
}
324+
325+
/**
326+
* @description clicks on the desired button from a specific row that contains a specific text on a table
327+
*/
328+
public async clickOnTargetButtonFromSpecificTableRow(tableLocator: string, rowText: string, buttonLocator: (string | Locator)) {
329+
const tableRow = this.page.locator(`${tableLocator} tbody tr`, { hasText: rowText });
330+
const tableRowButton = tableRow.locator(buttonLocator);
331+
await this.clickElement(tableRowButton);
332+
}
333+
334+
public async validateAllTableCellValues(tableLocator: string, rowText: string, expectedCellValues: string[]) {
335+
const tableRow = this.page.locator(`${tableLocator} tbody tr`, { hasText: rowText });
336+
const tableRowInnerText = await this.getInnerText(tableRow);
337+
const tableRowCellValues = tableRowInnerText.split('\n');
338+
for (let item of expectedCellValues) {
339+
if (!tableRowCellValues.includes(item)) {
340+
throw new Error(`one of the expected cell values: ${expectedCellValues} do not exist on table row`)
341+
}
342+
}
343+
expect(tableRowCellValues).toEqual(expectedCellValues);
344+
}
345+
}

pages/cartPage/ShoppingCartPage.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@ import { Page, expect } from "@playwright/test";
22
import { LumaMainPage } from "../LumaMainPage";
33
import { ItemShoppingComponentPage } from "../pageComponents/productShoppingComponent/ItemShoppingPage";
44

5+
export enum CartActionsEnum {
6+
EDIT = 'Edit',
7+
REMOVE = 'Remove'
8+
}
9+
510
export class ShoppingCartPage extends LumaMainPage {
611
private cartItemLocator = '[class="cart item"]';
712
private moveToWishListLinkName = 'Move to Wishlist'
813
private updateShoppingCartButtonName = 'Update Shopping Cart'
9-
private shoppingCartTable = '#shopping-cart-table ';
14+
private shoppingCartTable = '#shopping-cart-table';
1015
private subTotalTableColumn = 'Subtotal';
16+
private editCartItemTitle = 'Edit item parameters';
17+
private removeItemTitle = 'Remove item';
18+
private orderTotalLocator = '.grand.totals .amount';
1119
itemShoppingPage: ItemShoppingComponentPage;
1220

1321
constructor(page: Page) {
@@ -35,4 +43,40 @@ export class ShoppingCartPage extends LumaMainPage {
3543
public async validateItemPrice(itemText: string, expectedTotalPrice: string) {
3644
await this.validateTableCellValue(this.shoppingCartTable, itemText, this.subTotalTableColumn, expectedTotalPrice);
3745
}
46+
47+
/**
48+
* @description helper function for modify cart item function
49+
* @param itemText
50+
* @param buttonTitle
51+
*/
52+
private async clickOnCartTargetButton(itemText: string, buttonTitle: string) {
53+
const targetButton = this.page.getByTitle(buttonTitle);
54+
await this.clickOnTargetButtonFromSpecificTableRow(this.shoppingCartTable, itemText, targetButton);
55+
}
56+
/**
57+
* @description this function clicks on either edit item button or delete an item from cart based on the enum value you choose
58+
*/
59+
public async modifyCartItem(cartAction: CartActionsEnum, itemName: string) {
60+
try {
61+
switch (cartAction) {
62+
case CartActionsEnum.EDIT:
63+
await this.clickOnCartTargetButton(itemName, this.editCartItemTitle);
64+
break;
65+
case CartActionsEnum.REMOVE:
66+
await this.clickOnCartTargetButton(itemName, this.removeItemTitle);
67+
}
68+
} catch (error) {
69+
throw new Error(`an error occured on function "modifyCartItem": ${error} `)
70+
}
71+
}
72+
73+
public async validateCartItemsCount(expectedItemCount: number) {
74+
const itemCount = await this.countElements(this.cartItemLocator);
75+
expect(itemCount).toBe(expectedItemCount);
76+
}
77+
78+
public async validateCartTotal(expectedTotal: string) {
79+
const cartTotal = await this.getInnerText(this.orderTotalLocator);
80+
expect(cartTotal).toBe(expectedTotal);
81+
}
3882
}

pages/men/MenCategoryPage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { ItemShoppingComponentPage } from "../pageComponents/productShoppingComp
44
import { SideBarShoppingComponentPage } from "../pageComponents/sidebarShoppingComponent/SideBarShoppingComponentPage";
55

66
export class MenCategoryPage extends LumaMainPage {
7-
itemShoppingPage: ItemShoppingComponentPage;
7+
itemShoppingComponent: ItemShoppingComponentPage;
88
sideBarShoppingComponent: SideBarShoppingComponentPage;
99

1010
constructor(page: Page) {
1111
super(page)
12-
this.itemShoppingPage = new ItemShoppingComponentPage(page);
12+
this.itemShoppingComponent = new ItemShoppingComponentPage(page);
1313
this.sideBarShoppingComponent = new SideBarShoppingComponentPage(page);
1414
}
1515
}

pages/pageComponents/productShoppingComponent/ItemShoppingPage.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class ItemShoppingComponentPage extends BasePage {
1313
private productItemSize = '[class="swatch-attribute size"]';
1414
private productColorLocator = '[class="swatch-attribute color"]';
1515
private productPriceLocator = '.price';
16+
private productItemInCartLocator = '[class="product-info-main"]';
17+
private productQtyLocator = '#qty';
1618

1719
/**
1820
* @description function to choose a product item with flexible options to choose from
@@ -21,7 +23,7 @@ export class ItemShoppingComponentPage extends BasePage {
2123
* in the test
2224
*/
2325
public async chooseProductItem(productName: string, options?: ProductItemOptionalParamsInterface) {
24-
const productItem = this.page.locator(this.productItemLocator, { hasText: productName });
26+
const productItem = await this.deterimeItemScope(productName);
2527
await this.hover(productItem);
2628
try {
2729
if (options?.chooseSize && options.size !== undefined) {
@@ -33,6 +35,9 @@ export class ItemShoppingComponentPage extends BasePage {
3335
if (options?.validatePrice && options.price !== undefined) {
3436
await this.validateItemPrice(productItem, options.price);
3537
}
38+
if (options?.modifyQuantity && options.quantity !== undefined) {
39+
await this.modifyQuantity(productItem, options.quantity)
40+
}
3641
if (options?.addItemToCart) {
3742
await this.addProductItemToCart(productItem);
3843
await this.page.waitForTimeout(2500)
@@ -53,6 +58,11 @@ export class ItemShoppingComponentPage extends BasePage {
5358
await this.clickElement(chosenColor);
5459
}
5560

61+
private async modifyQuantity(productItemLocator: Locator, quantity: string) {
62+
const itemQuantity = productItemLocator.locator(this.productQtyLocator);
63+
await this.fillText(itemQuantity, quantity);
64+
}
65+
5666
private async validateItemPrice(productItemLocator: Locator, price: string) {
5767
const productItemPrice = productItemLocator.locator(this.productPriceLocator);
5868
expect(productItemPrice).toBe(price);
@@ -63,4 +73,21 @@ export class ItemShoppingComponentPage extends BasePage {
6373
const addProductToCart = productItemLocator.locator(addToCartButton);
6474
await this.clickElement(addProductToCart);
6575
}
76+
77+
/**
78+
* @description returns the scope of the item if it is an item in a random page or is it inside a cart to get the correct locator
79+
* and choose product with the correct options and reduce code duplication
80+
* @param itemText
81+
* @returns
82+
*/
83+
private async deterimeItemScope(itemText: string) {
84+
let itemLocator: Locator | undefined;
85+
let currentPageUrl = await this.getPageUrl();
86+
if (currentPageUrl.includes('cart')) {
87+
itemLocator = this.page.locator(this.productItemInCartLocator, { hasText: itemText });
88+
} else {
89+
itemLocator = this.page.locator(this.productItemLocator, { hasText: itemText });
90+
}
91+
return itemLocator;
92+
}
6693
}

pages/productPage/ProductPage.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Page, expect } from "@playwright/test";
2+
import { LumaMainPage } from "../LumaMainPage";
3+
import { ItemShoppingComponentPage } from "../pageComponents/productShoppingComponent/ItemShoppingPage";
4+
5+
export enum ProductStockAvailability {
6+
IN_STOCK = 'In Stock',
7+
OUT_OF_STOCK = 'Out Of Stock',
8+
}
9+
10+
export class ProductPage extends LumaMainPage {
11+
private productInStockLocator = '[class="product-info-stock-sku"]'
12+
private addToWishListLink = 'Add to Wish List';
13+
private addToCompareLink = 'Add to Compare';
14+
private updateCartLocator = '#product-updatecart-button';
15+
itemShoppingPage: ItemShoppingComponentPage;
16+
17+
constructor(page: Page) {
18+
super(page);
19+
this.itemShoppingPage = new ItemShoppingComponentPage(page);
20+
}
21+
22+
public async validateProductStockAvailability(expectedStockStatus: string) {
23+
let producyAvailibility: ProductStockAvailability | undefined;
24+
const productStock = this.page.locator(this.productInStockLocator);
25+
const stockStatus = await productStock.locator('div').getAttribute('class');
26+
if (stockStatus === 'stock available') {
27+
producyAvailibility = ProductStockAvailability.IN_STOCK;
28+
} else {
29+
this.productInStockLocator = ProductStockAvailability.OUT_OF_STOCK;
30+
}
31+
expect(producyAvailibility).toBe(expectedStockStatus);
32+
}
33+
34+
public async addProductToWishList() {
35+
await this.clickOnLink(this.addToWishListLink);
36+
}
37+
38+
public async addProductToCompare() {
39+
await this.clickOnLink(this.addToCompareLink);
40+
}
41+
42+
public async updateCart() {
43+
await this.clickElement(this.updateCartLocator);
44+
}
45+
}

pages/women/WomenCategoryPage.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { Page } from "@playwright/test";
22
import { LumaMainPage } from "../LumaMainPage";
33
import { ItemShoppingComponentPage } from "../pageComponents/productShoppingComponent/ItemShoppingPage";
4+
import { SideBarShoppingComponentPage } from "../pageComponents/sidebarShoppingComponent/SideBarShoppingComponentPage";
45

56
export class WomenCategoryPage extends LumaMainPage {
6-
itemShoppingPage: ItemShoppingComponentPage;
7+
itemShoppingComponent: ItemShoppingComponentPage;
8+
sideBarShoppingComponent: SideBarShoppingComponentPage;
9+
710
constructor(page: Page) {
811
super(page)
9-
this.itemShoppingPage = new ItemShoppingComponentPage(page);
12+
this.itemShoppingComponent = new ItemShoppingComponentPage(page);
13+
this.sideBarShoppingComponent = new SideBarShoppingComponentPage(page);
1014
}
1115
}

0 commit comments

Comments
 (0)