Skip to content

Commit

Permalink
E2E tests for shopping cart (#193)
Browse files Browse the repository at this point in the history
* fix conflicts

* fix conflicts

* fix conflicts

* fix conflicts

* refactor test specs

* revert changes in .vscode/settings.json

---------

Co-authored-by: PollySt <[email protected]>
  • Loading branch information
PollySt and PollySt authored Jan 26, 2024
1 parent 3e9f907 commit e47de82
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 55 deletions.
8 changes: 6 additions & 2 deletions .env.local-example
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ SHOPIFY_MULTIPASS_SECRET=''
# Playwright URL for local runs
PLAYWRIGHT_TEST_BASE_URL='http://127.0.0.1:3000'
# Set a product name to use in your own tests
PLAYWRIGHT_PRODUCT_NAME='Striped Skirt and Top'
PLAYWRIGHT_COLLECTION_NAME='women'
PLAYWRIGHT_PRODUCT_NAME='Mesh Gym Shorts'
PLAYWRIGHT_PRODUCT_NAME_OUTOFSTOCK='An Exceptional Tee for Men'
PLAYWRIGHT_PRODUCT_SIZE_OUTOFSTOCK='XS'
PLAYWRIGHT_PRODUCT_COLOR_OUTOFSTOCK='Gray'
PLAYWRIGHT_COLLECTION_NAME='men'

3 changes: 3 additions & 0 deletions e2e/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export const PRODUCT_NAME = process.env.PLAYWRIGHT_PRODUCT_NAME;
export const PRODUCT_OUTOFSTOCK = process.env.PLAYWRIGHT_PRODUCT_NAME_OUTOFSTOCK;
export const PRODUCT_COLOR_OUTOFSTOCK = process.env.PLAYWRIGHT_PRODUCT_COLOR_OUTOFSTOCK;
export const PRODUCT_SIZE_OUTOFSTOCK = process.env.PLAYWRIGHT_PRODUCT_SIZE_OUTOFSTOCK;
export const HOMEPAGE_ENDPOINT = '/';
export const COLLECTION_NAME = process.env.PLAYWRIGHT_COLLECTION_NAME;
export const COLLECTIONS_ENDPOINT = '/collections/';
3 changes: 3 additions & 0 deletions e2e/page-objects/product-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ export class ProductPage {

addToCartBtn = () => this.page.getByRole('button', { name: 'Add to cart', exact: true });
productPrice = () => this.page.getByTestId('product-price');
colorPicker = (color: string) => this.page.getByRole('radio', { name: color });
sizePickerDisabled = () => this.page.getByTestId('size-picker-disabled');
sizePickerEnabled = () => this.page.getByTestId('size-picker-enabled');
}
5 changes: 5 additions & 0 deletions e2e/page-objects/shopping-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ export class ShoppingCart {
cartTotalPrice = () => this.page.locator('p:has-text("Subtotal") + p');
checkoutBtn = () => this.page.getByRole('button', { name: 'Checkout', exact: true });
cartItemsCount = () => this.page.getByTestId('cart-items-count');
cartIcon = () => this.page.getByTestId('cart-icon');
cartDialog = () => this.page.getByTestId('cart-dialog');
addItemBtn = () => this.page.getByTestId('plus-icon');
reduceItemBtn = () => this.page.getByTestId('minus-icon');
removeCartItemBtn = () => this.page.getByRole('button', { name: 'Remove', exact: true });
}
49 changes: 0 additions & 49 deletions e2e/tests/add-to-cart.spec.ts

This file was deleted.

132 changes: 132 additions & 0 deletions e2e/tests/shopping-cart.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { test } from '../fixtures';
import {
COLLECTION_NAME,
COLLECTIONS_ENDPOINT,
PRODUCT_COLOR_OUTOFSTOCK,
PRODUCT_NAME,
PRODUCT_OUTOFSTOCK,
PRODUCT_SIZE_OUTOFSTOCK
} from '../constants';
import { expect } from 'playwright/test';

test.describe('Shopping cart', () => {
test.beforeEach('Navigate to the Collections page', async ({ page }) => {
if (!COLLECTION_NAME) {
test.skip(!COLLECTION_NAME, 'PLAYWRIGHT_COLLECTION_NAME was not defined');
return;
}
await page.goto(COLLECTIONS_ENDPOINT + COLLECTION_NAME);
});

test('User is able to add product to cart', async ({ page, collectionsPage, shoppingCart, productPage }) => {
if (!PRODUCT_NAME) {
test.skip(!PRODUCT_NAME, 'PLAYWRIGHT_PRODUCT_NAME was not defined');
return;
}

await collectionsPage.getProductByName(PRODUCT_NAME).click();
await expect(page.getByLabel('Breadcrumb')).toContainText(PRODUCT_NAME);

const productPrice = await productPage.productPrice().innerText();

await productPage.addToCartBtn().click();
await expect(page.getByText('Shopping cart')).toBeVisible();
await expect(shoppingCart.shoppingCartItems()).toHaveCount(1);
await expect(shoppingCart.shoppingCartItems()).toContainText(PRODUCT_NAME);
await expect(shoppingCart.cartTotalPrice()).toContainText(productPrice);
});

test('Checkout phase', async ({ page, shoppingCart, collectionsPage, productPage }) => {
if (!PRODUCT_NAME) {
test.skip(!PRODUCT_NAME, 'PLAYWRIGHT_PRODUCT_NAME was not defined');
return;
}

await collectionsPage.getProductByName(PRODUCT_NAME).click();
await productPage.addToCartBtn().click();
await expect(page.getByText('Shopping cart')).toBeVisible();
await expect(shoppingCart.shoppingCartItems()).toContainText(PRODUCT_NAME);

await page.goto('/?shopify_checkout_action=success');

await page.waitForURL('/');
await page.getByText('Successfully checked out').waitFor();
await expect(shoppingCart.cartItemsCount()).toContainText('0');
});

test('Verify clicking on the cart icon opens shopping cart', async ({ shoppingCart }) => {
await shoppingCart.cartIcon().click();
await shoppingCart.cartDialog().waitFor();
await expect(shoppingCart.cartDialog()).toContainText('Your cart is empty');
});

test('Adjust the number of items', async ({ shoppingCart, page, collectionsPage, productPage }) => {
if (!PRODUCT_NAME) {
test.skip(!PRODUCT_NAME, 'PLAYWRIGHT_PRODUCT_NAME was not defined');
return;
}

await collectionsPage.getProductByName(PRODUCT_NAME).click();
await page.getByLabel('Breadcrumb').waitFor();

const productPrice = await productPage.productPrice().innerText();
const convertedPrice = parseFloat(productPrice.replace('$', ''));

await productPage.addToCartBtn().click();
await shoppingCart.cartDialog().waitFor();
await expect(shoppingCart.shoppingCartItems()).toHaveCount(1);
await expect(shoppingCart.cartTotalPrice()).toContainText(productPrice);

await shoppingCart.addItemBtn().click();
await shoppingCart.addItemBtn().click();
await expect(shoppingCart.cartTotalPrice()).toContainText(String(convertedPrice * 3));

await shoppingCart.reduceItemBtn().click();
await expect(shoppingCart.cartTotalPrice()).toContainText(String(convertedPrice * 2));
});

test('Remove items from the cart', async ({ shoppingCart, page, collectionsPage, productPage }) => {
if (!PRODUCT_NAME) {
test.skip(!PRODUCT_NAME, 'PLAYWRIGHT_PRODUCT_NAME was not defined');
return;
}

await collectionsPage.getProductByName(PRODUCT_NAME).click();
await page.getByLabel('Breadcrumb').waitFor();

const productPrice = await productPage.productPrice().innerText();

await productPage.addToCartBtn().click();
await shoppingCart.cartDialog().waitFor();
await expect(shoppingCart.shoppingCartItems()).toHaveCount(1);
await expect(shoppingCart.cartTotalPrice()).toContainText(productPrice);

await shoppingCart.removeCartItemBtn().click();
await expect(shoppingCart.cartDialog()).toContainText('Your cart is empty');
await expect(shoppingCart.shoppingCartItems()).toHaveCount(0);
});

test('Verify out of stock products cannot be added to cart', async ({ page, collectionsPage, productPage }) => {
if (!PRODUCT_OUTOFSTOCK) {
test.skip(!PRODUCT_OUTOFSTOCK, 'PLAYWRIGHT_PRODUCT_NAME_OUTOFSTOCK was not defined');
return;
}
if (!PRODUCT_COLOR_OUTOFSTOCK) {
test.skip(!PRODUCT_COLOR_OUTOFSTOCK, 'PLAYWRIGHT_PRODUCT_COLOR_OUTOFSTOCK was not defined');
return;
}
if (!PRODUCT_SIZE_OUTOFSTOCK) {
test.skip(!PRODUCT_SIZE_OUTOFSTOCK, 'PLAYWRIGHT_PRODUCT_SIZE_OUTOFSTOCK was not defined');
return;
}

await collectionsPage.getProductByName(PRODUCT_OUTOFSTOCK).click();
await page.getByLabel('Breadcrumb').waitFor();

await productPage.colorPicker(PRODUCT_COLOR_OUTOFSTOCK).click();
await productPage.sizePickerDisabled().getByText(PRODUCT_SIZE_OUTOFSTOCK, { exact: true }).click();

await expect(page.getByText('Out of stock')).toBeVisible();
await expect(productPage.addToCartBtn()).toBeDisabled();
});
});
1 change: 1 addition & 0 deletions src/components/Product/ProductSizeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const ProductSizeSelect = ({
buttonStyles
)
}
data-testid={isSoftDisabled ? 'size-picker-disabled' : 'size-picker-enabled'}
>
{({ active, checked }) => (
<>
Expand Down
2 changes: 1 addition & 1 deletion src/features/Cart/Cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const Cart = () => {
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen max-w-md relative">
<Dialog.Panel className="pointer-events-auto w-screen max-w-md relative" data-testid="cart-dialog">
{isCartCheckingOut && (
<div className="z-20 bg-gray-500 bg-opacity-75 absolute h-full w-full">
<PageLoader colorClass="text-background" />
Expand Down
8 changes: 5 additions & 3 deletions src/features/Cart/components/CartItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,19 @@ export const CartItem = ({ atom, onRemove }: CartItemProps) => {
className="text-body-400 hover:text-body-500 disabled:text-body-300"
>
<span className="sr-only">One Less</span>
<MinusCircleIcon className="w-5 h-5" aria-hidden="true" />
<MinusCircleIcon className="w-5 h-5" aria-hidden="true" data-testid="minus-icon" />
</button>

<div className="flex justify-center items-center w-1 text-body-500">{quantity}</div>
<div className="flex justify-center items-center w-1 text-body-500" data-testid="cart-items-count">
{quantity}
</div>

<a
onClick={() => setItem({ ...item, quantity: item.quantity + 1 })}
className="text-body-400 hover:text-body-500"
>
<span className="sr-only">One More</span>
<PlusCircleIcon className="w-5 h-5" aria-hidden="true" />
<PlusCircleIcon className="w-5 h-5" aria-hidden="true" data-testid="plus-icon" />
</a>
</div>
</div>
Expand Down

1 comment on commit e47de82

@vercel
Copy link

@vercel vercel bot commented on e47de82 Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.