
In eCommerce software development, it’s important to make sure that the entire system works correctly from start to finish. End-to-end (E2E) testing helps check if key user flows function as expected across the whole application.
This article explores the E2E testing process used in the our B2C Starter - a B2C eCommerce storefront built on top of Medusa.
We'll break down how test automation is set up to verify the entire user journey, how automated testing improves the development process, and what testing methods are used to ensure the platform behaves correctly from the user's perspective.
What is being tested?

The tested system is our Medusa B2C Starter, a storefront that demonstrates what is possible when building on top of modern eCommerce technologies. It includes features like:
-
Full product management system on Medusa 2.0
-
Shopping cart and checkout flows
-
Customer account management
-
Order processing and tracking
-
Search and filtering capabilities
-
Responsive mobile-first design
Each of the Starter's areas are covered by specific test cases - from verifying the visibility and behavior of the search bar, to testing checkout steps. These tests simulate real user actions across the entire storefront to ensure all core features function correctly in different scenarios.
What value does E2E testing bring?
End-to-end testing on the frontend of the B2C platform brings several key benefits:
Verifying real user flows
The tests simulate user actions such as logging in, placing orders, and navigating the site. This ensures that core processes work as expected in the tested software.
Early regression detection
E2E tests help catch unexpected bugs caused by changes in the code before deployment.
Integration testing
The platform includes business logic and external integrations (e.g. with Medusa backend, payment systems). End-to-end tests check that frontend-to-backend communication works properly.
Improved user experience
Tests can also check if UI elements like buttons or forms work correctly, supporting a smooth user interface experience.
Reduced production issues
Detecting bugs early in the test environment helps avoid costly fixes after deployment.
Automated testing of business-critical paths
The test suite includes automation for key flows like checkout or account access.
Safer releases
With stable test results, it’s easier to release updates without fear of breaking core features.
E2E Test Structure in Solace
The E2E test suite in Solace is organized into three main folders:
1. tests - Test cases

This folder contains the actual test cases executed against the platform. The files are grouped into subfolders for better organization:
-
1-homepage – tests related to the homepage (e.g. searchbar),
-
2-account – tests for account-related actions,
-
3-checkout – tests covering the purchase flow.
2. fixtures - Test data and page objects

Fixtures are a great way to store test data, mock APIs, and improve test readability. They are especially useful in larger projects, particularly in end-to-end testing for B2B systems where large volumes of data are involved. One common example used in the context of E2E tests and often stored in fixtures is the Page Object Model (POM).
POM (Page Object Model) is a design pattern used in E2E testing to help organize and maintain automated tests. In short, it separates the test logic from the interaction layer with the user interface.
In Solace, POMs are divided and named according to each test — meaning every test file has its own POM file, where interactions with the user interface are organized. This helps keep the structure clean and maintainable.
Example POM usage in our B2C Starter:
Test for the search bar (searchbar.spec.ts).
Final version of the test file (searchbar.spec.ts) that is executed:
import { test } from '@playwright/test';
import Searchbar from '../../fixtures/page-objects/1-homepage/searchbar';
test.describe('Searchbar Tests', () => {
let searchbar: Searchbar
test.beforeEach(async ({ page }) => {
searchbar = new Searchbar(page)
await page.goto('https://solace-medusa-starter.vercel.app/de/about-us')
await page.waitForLoadState('load');
await page.waitForLoadState('domcontentloaded');
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check searchbar visibility', async ({ page }) => {
await searchbar.checkSearchbarVisibility()
});
test('Checking the search for a real product', async ({ page }) => {
await searchbar.checkRealProductSearching()
});
test('Checking the search for a unexisting product', async ({ page }) => {
await searchbar.checkNonExistingProductSearching()
});
})
POM for the test searchbar.spec.ts:
export{}
import { Page, expect } from '@playwright/test'
import helpers from '../../../utils/tests-helpers'
class Searchbar {
page: Page
constructor(page: Page) {
this.page = page;
}
async checkSearchbarVisibility() {
await helpers.waitForPageLoad(this.page)
await this.page.getByTestId('search-button').click();
await this.page.waitForLoadState('load');
const recommendedSearchResults = await this.page.locator('div').filter({ hasText: /^Recommended$/ })
expect(recommendedSearchResults).toBeTruthy()
const lastSearchResults = await this.page.locator('div').filter({ hasText: /^Search results$/ })
expect(lastSearchResults).toBeTruthy()
}
async checkRealProductSearching() {
await helpers.waitForPageLoad(this.page)
await this.page.getByTestId('search-button').click();
await this.page.waitForLoadState('load');
await this.page.getByTestId('search-input').click();
await this.page.getByTestId('search-input').fill('Ashton')
await this.page.getByTestId('search-input').press('Enter')
await this.page.waitForLoadState('domcontentloaded');
await this.page.waitForURL(/\/Ashton/)
await expect(this.page).toHaveURL(/\/Ashton/)
const realProductHeading = await this.page.getByRole('heading', { name: '"Ashton"' })
expect (realProductHeading).toBeTruthy()
}
async checkNonExistingProductSearching() {
await helpers.waitForPageLoad(this.page)
await this.page.getByTestId('search-button').click();
await this.page.waitForLoadState('load');
await this.page.getByTestId('search-input').click();
await this.page.getByTestId('search-input').fill('non existing item')
await this.page.getByTestId('search-input').press('Enter')
await this.page.waitForLoadState('domcontentloaded');
const noResultPlaceholder = await this.page.getByRole('heading', { name: 'No results for "non existing item"' })
expect(noResultPlaceholder).toBeTruthy()
}
}
export default Searchbar
The code defines a Searchbar class that handles search functionality testing on the website using Playwright. The class includes three test methods that cover different test scenarios related to product search.
-
Constructor
The constructor receives a Page object and assigns it to this.page, allowing the same page instance to be reused across different tests. -
checkSearchbarVisibility() – Checking search bar visibility
This method waits for the page to fully load, clicks the search button, and waits again. It then checks whether the "Recommended" and "Search results" sections are visible, confirming that the search bar displays suggestions and previous user searches. -
checkRealProductSearching() – Searching for an existing product
This method opens the search bar, types the product name "Ashton", and presses Enter. It waits for the new page to load, checks that the URL includes /Ashton, and verifies that the product name appears as a heading on the page. The goal is to test whether the system correctly handles searches for real products and redirects the user to the correct page. -
checkNonExistingProductSearching() – Searching for a non-existing product
This method performs similar actions but enters a non-existing product name. After pressing Enter, it checks if a message is displayed indicating no search results were found. The purpose is to verify that the system handles empty search results correctly. -
helpers.waitForPageLoad(this.page)
Each test method calls this helper function before performing actions. It ensures the page is fully loaded to avoid issues caused by asynchronous loading of elements.
To sum up, the tests are split into three scenarios that verify different aspects of the search bar - visibility, searching for an existing product, and handling of no results. Using helpers.waitForPageLoad() improves test stability by minimizing errors caused by delays in loading the page.
3. utils - Shared helpers

Utils is a folder or module that contains helper functions that can be used across different tests. It stores code that isn’t specific to a single page (as in POM), but is reusable in multiple test cases.
Inside the utils folder, there's an important file: test-helpers.ts.
Helpers (called test-helpers in our B2C Starter) are a collection of functions that simplify writing tests, such as:
-
generating test data (e.g. random emails, phone numbers),
-
data conversion and formatting (e.g. formatting dates or currency),
-
reusable actions in tests (e.g. user login, database reset),
-
mocking APIs or setting up local data.
Examples of helper usage in our B2C Starter:
The code defines several helper functions used in Playwright tests to simplify login and navigation steps in the application.
-
waitForPageLoad(page) – Waiting for full page load
This function waits until the HTML content is fully loaded and the page has finished rendering. It's useful to avoid issues caused by asynchronous loading of elements. -
goToSignInPage(page) – Navigate to the sign-in page
This function navigates to the homepage, opens the user profile dropdown, and clicks the link to the login page.

- fillSignInInputs(page) – Fill in login inputs
This function automatically fills the email and password fields with example test data.

- login(page) – Complete login process
This function performs the full login flow by first calling goToSignInPage(), then fillSignInInputs(), and finally clicking the login button.

Test file configuration in B2C Starter - Example: adding-to-cart
Below is an example of how a test file is structured in our B2C Starter. The example focuses on testing the functionality of adding a product to the cart.
test.describe('Add product to cart Tests', () => {
let addToCart: AddToCart;
test.beforeEach(async ({ page }) => {
addToCart = new AddToCart(page);
await helpers.waitForPageLoad(page);
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
});
Let’s break down each part of the configuration:
test.describe(`Add product to cart Tests`, () => { ... })
This defines a test suite named "Add product to cart Tests". All test cases inside this block are related to adding products to the cart.
let addToCart: AddToCart
This line declares a variable that will hold an instance of the AddToCart class - a Page Object that contains logic for interacting with the cart UI. The class encapsulates all steps required to simulate adding products to the cart.
test.beforeEach(...)
This is a Playwright hook that runs before each test case in the suite.
-
It creates a new AddToCart object, passing in the page (browser tab) instance.
-
It then calls helpers.waitForPageLoad(page), which waits until the entire page is fully loaded before starting the test.
test.afterEach(...)
This hook runs after each test case.
-
It checks whether the test was not interrupted or timed out.
-
If the test completed (successfully or with a failure), it closes the page using await page.close() to free up system resources and ensure test isolation.
This structure ensures that each test starts with a clean, loaded page and that browser sessions are properly cleaned up afterward. It’s a core part of writing reliable and efficient tests in E2E setups.
Medusa B2C Starter – Step-by-step test scenarios
This section describes specific test cases implemented in the Medusa B2C Starter. Each case focuses on a user-visible part of the platform and simulates real interactions to verify expected behavior.
1) Homepage: "Shop" Tab
This test focuses on verifying the first tab on the homepage. The following steps are covered:
-
verifying navigation to the page and checking if it fully loads,
-
testing filtering by collection, type, material, and price,
-
verifying the correctness of filtered results and the visibility of active filter tabs,
-
checking the "Recommended products" section,
-
testing pagination.
import { test } from '@playwright/test';
import ShopPage from '../../fixtures/page-objects/1-homepage/shop-page';
test.describe('Shop page Tests', () => {
let shoppage: ShopPage;
test.beforeEach(async ({ page }) => {
shoppage = new ShopPage(page);
await page.waitForLoadState('load');
await page.waitForLoadState('domcontentloaded');
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check shop page loading and it`s title and header', async ({ page }) => {
await shoppage.checkTitleAndHeading()
});
test('Check filtering by collections', async({page}) => {
await shoppage.checkFilteringByCollections()
})
test('Check filtering by product type', async({page}) => {
await shoppage.checkFilteringByProductType()
})
test('Check filtering by material', async({page}) => {
await shoppage.checkFilteringByMaterial()
})
test('Check filtering by price', async({page}) => {
await shoppage.checkFilteringByPrice()
})
test('Check filter tabs and result of filtering', async({page}) => {
await shoppage.checkFilterResults()
})
test('Check `Recommended products` section', async({page}) => {
await shoppage.checkRecommendedProducts()
})
test('Check pagination', async({page}) => {
await shoppage.checkPagination()
})
})
2) Homepage: about-us Tab
This test verifies all components on the About Us page:
-
checks redirect to the page and the header,
-
checks the "Our Story" section – texts and images,
-
checks the "Why Us?" section – texts, images, and component grid,
-
checks the "Our Craftsmanship" section – texts and images,
-
checks the "Get Inspired" section – texts and images.
import { test } from '@playwright/test';
import AboutUsPage from '../../fixtures/page-objects/1-homepage/about-us'
test.describe('Shop page Tests', () => {
let aboutuspage: AboutUsPage
test.beforeEach(async ({ page }) => {
aboutuspage = new AboutUsPage(page);
await page.goto('https://solace-medusa-starter.vercel.app/de/about-us')
await page.waitForLoadState('load');
await page.waitForLoadState('domcontentloaded');
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check about us page loading and it`s header', async ({ page }) => {
await aboutuspage.checkLoadingAndHeader()
});
test('Check header and logo', async ({ page }) => {
await aboutuspage.checkPageHeaderAndLogo()
});
test('Check `Our story` section`', async ({ page }) => {
await aboutuspage.checkOurStorySection()
});
test('Check `Why us?` section`', async ({ page }) => {
await aboutuspage.checkWhyUsSection()
});
test('Check `Our craftsmanship` section`', async ({ page }) => {
await aboutuspage.checkOurCraftsmanshipSection()
});
test('Check `Get inspired` section`', async ({ page }) => {
await aboutuspage.checkGetInspiredSection()
});
})
3) Homepage: Search Bar
This test verifies the search bar on the homepage:
-
checks if the search bar is visible after page load,
-
checks the happy path for searching an existing product,
-
checks if searching for a non-existing product shows the correct placeholder message.
import { test } from '@playwright/test';
import Searchbar from '../../fixtures/page-objects/1-homepage/searchbar';
test.describe('Searchbar Tests', () => {
let searchbar: Searchbar
test.beforeEach(async ({ page }) => {
searchbar = new Searchbar(page)
await page.goto('https://solace-medusa-starter.vercel.app/de/about-us')
await page.waitForLoadState('load');
await page.waitForLoadState('domcontentloaded');
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check searchbar visibility', async ({ page }) => {
await searchbar.checkSearchbarVisibility()
});
test('Checking the search for a real product', async ({ page }) => {
await searchbar.checkRealProductSearching()
});
test('Checking the search for a unexisting product', async ({ page }) => {
await searchbar.checkNonExistingProductSearching()
});
})
4) Homepage: Main Page
This test verifies all components and navigation on the homepage:
-
checks the page header and title,
-
checks navigation from the header to all tabs,
-
checks if images are displayed correctly,
-
checks navigation from sections like All Products, Get Inspired, Our Bestsellers.
import { test } from '@playwright/test';
import Homepage from '../../fixtures/page-objects/1-homepage/homepage';
test.describe('Homepage Tests', () => {
let homepage: Homepage;
test.beforeEach(async ({ page }) => {
homepage = new Homepage(page);
});
test.beforeEach(async ({ page }) => {
await page.goto(homepage.homepageUrl);
});
test('Check page loading and it`s title', async ({ page }) => {
homepage.checkPageTitle()
});
test('Check header redirections', async ({ page }) => {
homepage.checkShopTab()
homepage.checkCollectionsTab()
homepage.checkAboutUsTab()
});
test('Check img existing', async ({ page }) => {
homepage.checkImgExisting()
});
test('Check redirection to `All products` by `Explore now` button', async ({ page }) => {
homepage.checkRedirectionByExploreNowBtn()
});
test('Check redirections from `Our bestsellers` section', async ({ page }) => {
homepage.checkRedirectionToSingleProduct()
homepage.checkRedirectionByViewAllBtn()
});
test('Check redirections to blog from `Get inspired` section', async ({ page }) => {
homepage.checkRedirectionToSingleBlogPost()
homepage.checkRedirectionToAllBlogPosts()
});
})
5) Account: adding-to-cart
This test verifies the process of adding a product to the cart.
-
navigates to the product page and checks full load,
-
Case 1: adds a product and uses the Bag icon button to go to /cart,
-
Case 2: adds a product and uses the "Go to cart" button to go to /cart,
-
checks if the items are added correctly,
-
checks if products can be removed from the cart.
import { test } from '@playwright/test';
import AddToCart from '../../fixtures/page-objects/2-account/adding-to-cart';
import helpers from '../../utils/tests-helpers';
test.describe('Add product to cart Tests', () => {
let addToCart: AddToCart
test.beforeEach(async ({ page }) => {
addToCart = new AddToCart(page)
await helpers.waitForPageLoad(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check single product', async ({ page }) => {
await addToCart.checkSingleProduct()
})
test('Add product, go to cart via `Bag icon` and go back', async ({ page }) => {
await addToCart.addProductToCart()
await addToCart.goToCartViaBagIcon()
})
test('Add product and go to cart via `Go to cart` button', async ({ page }) => {
await addToCart.addProductToCart()
await addToCart.goToCartViaButton()
})
test('Check cart items', async ({ page }) => {
await addToCart.addProductToCart()
await addToCart.checkCartItems()
})
test('Check removing product from cart', async ({ page }) => {
await addToCart.addProductToCart()
await addToCart.checkRemovingItemFromCart()
})
})
6) Account: check-account-settings
This test verifies editing account data. To access this section, you need to be logged in - otherwise, the tab won’t be visible. The test includes the following steps:
-
logs in using a helper,
-
navigates to the account section,
-
opens the "Account Settings" tab,
-
edits the account data,
-
checks if the new data is saved,
-
restores default data.
export{}
import { test } from '@playwright/test';
import AccountSettings from '../../fixtures/page-objects/2-account/check-account-settings';
import helpers from '../../utils/tests-helpers';
test.describe('Account settings page', () => {
let accountSettings: AccountSettings
test.beforeEach(async ({ page, browser }) => {
accountSettings = new AccountSettings(page)
await helpers.waitForPageLoad(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check account settings page', async ({ page }) => {
// login
await helpers.login(page)
// go to account
await helpers.goToAccount(page)
// go to account settings
await accountSettings.goToAccountSettings()
// edit acconut details
await accountSettings.editAccountDetails()
// check new account details
await accountSettings.checkNewAccountDetails()
// reverse default account details
await helpers.fillProfileDetails(page)
})
})
7) Account: check-order-history
This test verifies the order history section of the account. To access this section, you need to be logged in - otherwise, the tab won’t be visible. The test includes the following steps:
-
logs in using the login helper,
-
performs an order flow using the orderFlow helper,
-
checks the order history components,
-
checks the order details page.
export{}
import { test } from '@playwright/test';
import helpers from '../../utils/tests-helpers';
import OrderHistory from '../../fixtures/page-objects/2-account/check-order-history'
test.describe('Order history page', () => {
let orderHistory: OrderHistory
test.beforeEach(async ({ page, browser }) => {
orderHistory = new OrderHistory(page)
await helpers.waitForPageLoad(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check order history page and single order page', async ({ page }) => {
// login
await helpers.login(page)
// order product
await helpers.orderProductFlow(page)
// go to account
await helpers.goToAccount(page)
// check order history page
await orderHistory.checkOrderHistoryPage()
// check single order page
await orderHistory.checkSingleOrderPage()
})
})
8) Account: check-shipping-details
This test verifies the shipping address management section. To access this section, you need to be logged in - otherwise, the tab won’t be visible. The test includes the following steps:
-
logs into the account,
-
navigates to the account page and then to the shipping details section,
-
checks the shipping details page,
-
adds a new address,
-
edits the address,
-
checks if the edited address is saved,
-
deletes the address.
export{}
import { test } from '@playwright/test';
import helpers from '../../utils/tests-helpers';
import ShippingDetails from '../../fixtures/page-objects/2-account/check-shipping-details'
test.describe('Shipping details in account settings page', () => {
let shippingDetails: ShippingDetails
test.beforeEach(async ({ page, browser }) => {
shippingDetails = new ShippingDetails(page)
await helpers.waitForPageLoad(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Add and edit shipping details in account settings', async ({ page }) => {
// login
await helpers.login(page)
// go to account
await helpers.goToAccount(page)
// go to shipping details page
await shippingDetails.checkShippingDetailsPage()
// add new address
await shippingDetails.addNewAddress()
// check new address
await shippingDetails.checkNewAddress()
// edit address
await shippingDetails.editNewAddress()
// check edited address
await shippingDetails.checkEditedAddress()
//delete address
await shippingDetails.deleteAddress()
})
})
9) Account: Login Test
This test verifies the login flow.
-
navigates to the login page and checks the URL and title,
-
checks the page header and form component,
-
tests input validation for empty and invalid data,
-
tests a successful login path.
export {}
import { test, expect } from '@playwright/test';
import Signin from '../../fixtures/page-objects/2-account/signin';
import helpers from '../../utils/tests-helpers';
test.describe('Signin Tests', () => {
let signin: Signin
test.beforeEach(async ({ page }) => {
signin = new Signin(page)
await helpers.waitForPageLoad(page)
await helpers.goToSignInPage(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Go to `/account` page', async ({ page }) => {
await page.waitForURL(signin.accountPageUrl)
await expect(page).toHaveURL(/\/account/)
})
test('Check `Log in` page items and inputs validation', async ({ page }) => {
await signin.checkHeaderAndFormComponent()
await signin.checkEmptyInputsValidation()
await signin.checkIncorrectDataValidation()
})
test('Check correctly sign in', async ({ page }) => {
await signin.checkCorrectlySignIn()
})
})
10) Account: Registration Test
This test verifies the user registration process. Since the login and registration pages use the same URL, you’ll first need to click the "Create account" button to switch to the registration view.
It walks through the following steps:
-
navigates to the registration page and checks the URL,
-
checks the form component and header,
-
tests input validation for empty fields, incorrect password format, and missing required password rules (uppercase, special characters, etc.),
-
checks navigation between signin/signup pages,
-
tests a successful registration flow.
export{}
import { test, expect } from '@playwright/test';
import helpers from '../../utils/tests-helpers';
import Signup from '../../fixtures/page-objects/2-account/signup';
test.describe('Signup Tests', () => {
let signup: Signup
test.beforeEach(async ({ page }) => {
signup = new Signup(page)
helpers.waitForPageLoad(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Go to `/account` page', async ({ page }) => {
await helpers.goToSignUpPage(page)
await page.waitForURL(signup.accountPageUrl)
await expect(page).toHaveURL(/\/account/)
})
test('Check `Create account` page items and inputs validation', async ({ page }) => {
await helpers.goToSignUpPage(page)
await signup.checkHeaderAndFormComponent()
await signup.checkEmptyInputsValidation()
await signup.checkInvalidPasswordCharacters()
await signup.checkPasswordLackOfUpperCase()
await signup.checkPasswordLackOfNumberOrSymbol()
})
test('Check transition between `signup` and `signin` pages', async ({ page }) => {
await helpers.goToSignUpPage(page)
await signup.checkTransitionBetweenLogInAndSignUp()
})
test('Fill inputs and correctly signup', async ({ page }) => {
await signup.checkCorrectlySignUp()
})
});
11) Checkout: checkout-flow Test
This test verifies the full checkout process.
-
adds a product to the cart,
-
fills out input fields,
-
proceeds to the delivery step,
-
fills out and edits shipping details,
-
verifies edited data, saves, and continues to delivery method,
-
selects delivery method,
-
edits and confirms delivery method change,
-
selects payment method,
-
completes the process and checks the confirmation screen.
export{}
import { test } from '@playwright/test';
import Checkout from '../../fixtures/page-objects/3-checkout/checkout-flow'
import helpers from '../../utils/tests-helpers';
test.describe('Checkout flow', () => {
let checkout: Checkout
test.beforeEach(async ({ page, browser }) => {
checkout = new Checkout(page)
await helpers.waitForPageLoad(page)
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'timedOut' && testInfo.status !== 'interrupted') {
await page.close();
}
});
test('Check flow of whole purchasing process', async ({ page }) => {
// add product to cart
await checkout.addProductToCart()
// fill shipping address
await helpers.fillShippingAddressInputs(page)
// save and proceed to delivery step
await checkout.saveAndProceedToDeliveryStep()
// edit shipping details
await checkout.editShippingDetails()
// check if shipping details was edited correctly
await checkout.checkEditedShippingDetails()
// choose delivery method
await checkout.chooseDeliveryMethod()
// edit delivery method
await checkout.editDeliveryMethod()
// check if delivery method was edited correctly
await checkout.checkEditedDeliveryMethod()
// choose payment method
await checkout.choosePaymentMethod()
// check order confirmation items
await checkout.checkOrderConfirmationPage()
})
})
Wrapping Up
The end-to-end testing setup in the B2C Starter covers a wide range of user interactions - from basic navigation and product search to account management and the full checkout flow. Tests are structured into clear modules, use the Page Object Model for maintainability, and rely on reusable helpers to reduce repetition.
This approach improves overall test coverage and helps teams validate not just isolated features but the behavior of the entire system. It allows developers to identify test scenarios early, combine automated testing with optional manual testing where needed, and ensure consistent software testing practices across the project. With this structure in place, it's easier to run tests regularly, detect regressions before deployment, and maintain confidence in the stability of the platform as it evolves.
For any eCommerce project having a reliable E2E testing process is essential for delivering consistent software quality across the entire user journey.