Most checklists for an online shop list everything with equal weight. That is the wrong shape. A wrong product description costs you a little trust; a broken checkout costs you every order until someone notices. Baymard Institute's average of 50 cart abandonment studies puts the documented rate at 70.22%, so most shoppers who fill a cart already leave without paying. Your testing should make sure none of them leave because of a defect.
This checklist is ordered by what a defect costs, starting with the path that takes money. It is written for anyone who tests an online shop: QA engineers, developers and the product owners who sign off a release. As a software testing company that provides e-commerce QA testing services, we have kept it practical. Each area has the checks themselves, the defects they catch, and where it helps, a test we actually ran.
The worked examples use Sauce Labs' public practice store at saucedemo.com, which ships with test accounts that misbehave on purpose. Every result quoted below comes from a run on Playwright 1.64.1 on 26 October 2026.
How to use this checklist
Work through it in three passes rather than top to bottom.
- Before every release: the revenue path and the money maths. These are short, repeatable and almost all automatable.
- Before a major change or a sale event: add performance, security and the cross-browser checks.
- Each quarter, or when the design changes: accessibility, content and the full catalogue review.
Keep the order even when time is short. If you can only test one thing, test that a real customer can pay and that the order arrives in your system correctly. This list covers e-commerce testing as a whole; for a shorter set of habits, see our five best practices for testing an e-commerce website.
Which kind of testing finds which defect
Each area of the checklist relies on a different kind of testing. This table shows what each one catches on a shop, and when to run it.
Test data you need before you start
Most e-commerce test runs that stall do so for lack of data, not lack of time. Prepare these before the first pass, and keep them in every test environment.
- Accounts: a new customer, a returning customer with saved addresses and cards, a customer with an order history, and one account per staff role in the back office.
- Products: one of each kind you sell, plus the awkward ones: a product with variants, one on sale, one out of stock, one with no image, and one with a very long name.
- Prices and promotions: an active coupon, an expired coupon, a free-delivery threshold and a product priced so that tax rounding is visible.
- Addresses: one for every country or region you ship to, including formats with letters in the postcode and addresses with apartment numbers.
- Payments: the test cards for each outcome you support, and a sandbox account for every wallet or instalment provider.
- A reset: a way to restore stock and clear test orders, so the next run starts from the same state.
Name test data clearly so nobody mistakes it for a real order. Keep it out of production unless your payment provider and your finance team have agreed how it will be handled.
1. The revenue path: from search to confirmation
The revenue path is every step between a shopper arriving and an order existing in your back office. A defect anywhere on it stops sales, so it gets the deepest testing and the most automation.
Search and navigation
- Search returns relevant results for exact names, partial names and common misspellings.
- A search with no results shows a helpful page, not an empty grid or an error.
- Filters combine correctly: size and colour together return only items in both.
- Sorting by price, newest and popularity actually sorts. Check the order, not just that the page reloads.
- Category pages and pagination agree on the number of products.
- Out-of-stock products are either hidden or clearly marked, according to your rules.
Product pages
- Price, sale price and currency match the back office for every variant.
- Choosing a variant (size, colour) updates the price, stock message and image.
- Images load, match the product and have meaningful alternative text.
- Delivery estimates and return information match your current policy.
- The add-to-cart button is disabled or explained when a variant is not chosen.
Cart
- Items, quantities and prices are correct after adding, removing and changing quantity.
- The cart survives a page refresh, and logging in merges a guest cart sensibly.
- Quantity limits are enforced, including the stock you actually have.
- If a price or stock level changes after an item is added, the cart says so before payment.
- Coupons apply once, expire on time and cannot be stacked unless you allow it.
Checkout
- Guest checkout works, if you offer it, and does not force account creation.
- Required fields are validated with a clear message next to the field.
- Addresses in every country you ship to are accepted, including long names, apartment numbers and postcodes with letters.
- Pressing Back, refreshing or double-clicking Pay never creates two orders.
- A session that times out during checkout recovers without losing the cart.
The checks we ran
Here is a checkout test in Playwright, written against the practice store. It covers a positive path, two negative paths and four of the store's deliberately faulty accounts.
import { test, expect, Page } from '@playwright/test';
async function login(page: Page, user: string) {
await page.goto('/');
await page.getByPlaceholder('Username').fill(user);
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
}
async function addTwoItemsAndStartCheckout(page: Page) {
await page.locator('[data-test="add-to-cart-sauce-labs-backpack"]').click();
await page.locator('[data-test="add-to-cart-sauce-labs-bike-light"]').click();
await expect(page.locator('[data-test="shopping-cart-badge"]')).toHaveText('2');
await page.locator('[data-test="shopping-cart-link"]').click();
await page.getByRole('button', { name: 'Checkout' }).click();
}
const money = (s: string) => Number(s.replace(/[^0-9.]/g, ''));
test('positive: a standard user completes checkout and the totals add up', async ({ page }) => {
await login(page, 'standard_user');
await addTwoItemsAndStartCheckout(page);
await page.getByPlaceholder('First Name').fill('Asha');
await page.getByPlaceholder('Last Name').fill('Patel');
await page.getByPlaceholder('Zip/Postal Code').fill('380015');
await page.getByRole('button', { name: 'Continue' }).click();
const items = money(await page.locator('[data-test="subtotal-label"]').innerText());
const tax = money(await page.locator('[data-test="tax-label"]').innerText());
const total = money(await page.locator('[data-test="total-label"]').innerText());
console.log(`item total ${items}, tax ${tax}, total ${total}, tax rate ${(tax / items * 100).toFixed(2)}%`);
expect(total).toBeCloseTo(items + tax, 2);
await page.getByRole('button', { name: 'Finish' }).click();
await expect(page.getByRole('heading', { name: 'Thank you for your order!' })).toBeVisible();
});
test('negative: a locked-out user sees a clear error', async ({ page }) => {
await login(page, 'locked_out_user');
await expect(page.locator('[data-test="error"]')).toContainText('locked out');
console.log('error shown:', await page.locator('[data-test="error"]').innerText());
});
test('negative: checkout without a first name is refused', async ({ page }) => {
await login(page, 'standard_user');
await addTwoItemsAndStartCheckout(page);
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.locator('[data-test="error"]')).toHaveText('Error: First Name is required');
});
test('the same checkout as problem_user', async ({ page }) => {
await login(page, 'problem_user');
await addTwoItemsAndStartCheckout(page);
await page.getByPlaceholder('First Name').fill('Asha');
await page.getByPlaceholder('Last Name').fill('Patel');
await page.getByPlaceholder('Zip/Postal Code').fill('380015');
console.log('last name field now holds:', JSON.stringify(await page.getByPlaceholder('Last Name').inputValue()));
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.locator('[data-test="error"]')).toHaveCount(0);
});
test('product images: standard_user vs problem_user', async ({ page }) => {
for (const user of ['standard_user', 'problem_user']) {
await login(page, user);
const srcs = await page.locator('.inventory_item_img img').evaluateAll(
(imgs) => imgs.map((i) => (i as HTMLImageElement).getAttribute('src')));
console.log(`${user}: ${srcs.length} products, ${new Set(srcs).size} distinct image(s)`);
await page.context().clearCookies();
}
});
test('login time: standard_user vs performance_glitch_user', async ({ page }) => {
for (const user of ['standard_user', 'performance_glitch_user']) {
await page.context().clearCookies();
const t = Date.now();
await login(page, user);
await expect(page.locator('[data-test="inventory-list"]')).toBeVisible({ timeout: 15000 });
console.log(`${user}: login to product list in ${Date.now() - t} ms`);
}
});
test('sorting: price low to high is really sorted', async ({ page }) => {
await login(page, 'standard_user');
await page.locator('[data-test="product-sort-container"]').selectOption('lohi');
const prices = (await page.locator('[data-test="inventory-item-price"]').allInnerTexts()).map(money);
console.log('prices after sorting:', prices.join(', '));
expect(prices).toEqual([...prices].sort((a, b) => a - b));
});Six tests passed and one failed. The failure is the most useful result, and it is the kind of defect a checklist exists to catch.
A real form defect, caught in a checkout
The problem_user account fills the delivery form exactly as a shopper would. Then the store refuses to continue, with the message "Error: Last Name is required". We read back what each field actually held:
The surname typed into Last Name ended up in First Name. Typed key by key, the way a person types, the Last Name field stayed empty and First Name held just "l". A shopper in that state cannot finish checkout, and nothing on the page explains why.
In a real shop this pattern usually means a form field is wired to the wrong piece of state. It slips through when tests only check that a page loads. Two rules catch it: always read back what a field holds, and always assert on the result of the action rather than on the absence of a crash.
The same account also showed one image for all six products, while standard_user saw six distinct images. The performance_glitch_user account took 5,205 ms to reach the product list, against 471 ms for standard_user. That was a single run, so treat the numbers as an illustration rather than a benchmark. The point is that the test measured it, so a regression would show.
Writing one checklist item as a test case
A checklist line tells you what to check. A test case tells anyone on the team exactly how, so two testers get the same answer. Here is one line from the cart section written out in full.
- ID: EC-CART-07
- Checks: a price change after an item is added is shown before payment.
- Preconditions: a test product priced at 20.00 in the back office; a logged-in test customer with an empty cart.
- Steps: add the product to the cart; in the back office, change its price to 22.00; return to the cart and go to checkout.
- Expected: the cart or checkout shows 22.00 and tells the shopper the price changed, before any payment is taken. The order, if placed, records 22.00.
- Clean-up: restore the price to 20.00 and empty the cart.
Write the expected result as something you can check, not as "works correctly". The expected result is what turns a checklist into a test that can fail.
Also read: Types of Functional Testing
2. Money maths: prices, tax, discounts and totals
Arithmetic defects are rarely visible on a screenshot, which is why they survive. Test the numbers, not the layout.
- The order total equals item total plus tax plus shipping, minus discounts, to the penny. In our run: item total 39.98, tax 3.20, total 43.18.
- Tax is calculated for the delivery address, not the billing address, if that is your rule, and changes when the address changes.
- Rounding happens once, in one place. Per-line rounding and per-order rounding give different totals on large baskets.
- Percentage and fixed discounts apply in the order your business rules say.
- Free-delivery thresholds work at exactly the threshold, one unit below and one unit above.
- Every currency you sell in shows the right symbol, decimal places and separator.
- The amount sent to the payment provider equals the amount shown to the shopper.
The last check matters most. A shopper who sees one total and is charged another contacts support, disputes the charge or never comes back. Assert both numbers in the same test.
3. Payments and what happens after
Payment testing has two halves: the payment itself, and everything your system does once the payment provider responds.
Test cards, not real cards
Payment providers publish test card numbers that trigger specific outcomes in their test mode. Use them to cover every outcome, not just success. These are from Stripe's testing documentation; other providers publish their own equivalents.
- A successful payment creates exactly one order and one confirmation.
- Each decline shows a message the shopper understands and keeps the cart intact.
- 3D Secure challenges complete, fail and get abandoned without leaving a half-made order.
- Saved cards, wallets and any buy-now-pay-later option work end to end.
- A slow or failed response from the provider leads to a clear state, never a duplicate charge.
After the payment
- The order appears in the back office with the correct items, prices and address.
- Stock goes down by the right amount, once.
- The confirmation email arrives, with the right order number and totals.
- Cancellations, refunds and partial refunds update the order, stock and payment together.
- Partial shipments, back orders and returns move the order through the right states.
These steps run in the background, so they fail quietly. A test that stops at the "Thank you for your order!" page has not tested the order.
4. Data integrity: what the shopper sees against what you store
The shop front shows prices, stock and order details. The database holds the version that counts. When they disagree, customers are charged one thing and shown another.
- Product prices and stock on the page match the database for a sample of products.
- An order's line items, totals and addresses in the database match the confirmation.
- Changes made in the back office appear on the shop front within the time you expect.
- Every environment you test in holds data that represents production, including edge cases such as products with no image or very long names.
5. Security
An online shop holds addresses, order histories and, directly or through a provider, payment data. Security testing is not optional, and part of it is a compliance matter.
- Login, password reset and account pages resist brute force and account enumeration.
- One customer can never see another customer's orders by changing an ID in the URL.
- Input fields reject script injection, including search, reviews and address fields.
- Sessions expire and log out properly on shared devices.
- Admin pages are not reachable from the public site.
Payment page scripts under PCI DSS
PCI DSS v4.0.1 added two requirements aimed at e-skimming, where malicious scripts on a payment page copy card details. Requirement 6.4.3 covers managing every script that runs on the payment page; 11.6.1 requires a mechanism that detects changes and tampering. The PCI Security Standards Council states that both became effective on 31 March 2025.
Merchants who validate with Self-Assessment Questionnaire A had both removed from that questionnaire. In their place, the Council added an eligibility criterion: merchants must "confirm their site is not susceptible to attacks from scripts that could affect the merchant's e-commerce system(s)." Either way, someone has to test the scripts on your payment pages. Which questionnaire applies to you is a question for your assessor, not your test plan.
Also read: Penetration Testing: How Does It Work
6. Performance
A slow shop loses sales quietly, and a shop that falls over during a sale loses them loudly. Plan performance testing for everyday speed and for peak load separately, because they fail for different reasons.
Page speed
Google's Core Web Vitals give three targets for a good user experience, measured at the 75th percentile of page loads.
- Measure product, category, cart and checkout pages, not just the home page.
- Test on a mid-range phone over a mobile connection, where most shoppers are.
- Check that third-party scripts, such as reviews, chat and tracking, do not push the page past the targets.
Peak load
- Run a load test against the traffic you expect at your biggest sale, plus a margin.
- Include the slow parts: search, add to cart and checkout, not just page views.
- Watch the payment provider, stock service and email service, not just your servers.
- Confirm what shoppers see when the system is saturated: a queue page beats an error.
Also read: Importance of Performance Testing
7. Accessibility
Shoppers who use screen readers, keyboards or magnification need to complete the same purchase as everyone else. Accessibility defects in checkout lock them out entirely.
- Every step of checkout can be completed with a keyboard alone, with a visible focus.
- Form fields have labels, and errors are announced, not just coloured red.
- Product images have alternative text that describes the product.
- Text contrast meets WCAG 2.2 level AA, including sale prices and disabled buttons.
- Pop-ups, cookie banners and chat widgets can be closed by keyboard and do not trap focus.
Automated scanners find only part of these. For the rest, and for formal conformance, see our guide to a WCAG compliance audit.
Also read: Best Practices in Accessibility Testing
8. Devices and browsers
Shoppers arrive on phones, tablets and laptops, in browsers you did not choose. Test the combinations your analytics say your customers use, not a generic list.
- The full purchase works on the top browser and device combinations in your analytics.
- Layouts hold at narrow widths: no hidden Pay buttons, no overlapping prices.
- Touch targets on mobile are large enough to tap without mistakes.
- Autofill for addresses and cards fills the right fields.
- Wallet payments appear only on devices that support them.
Also read: Tips for Cross Browser Testing
9. Content, design and trust signals
These checks rarely stop a sale on their own, but together they decide whether a shopper trusts you with their card.
- Prices, names and descriptions match the back office, with no placeholder text.
- Delivery costs and return terms are visible before checkout, not discovered at the end.
- Legal pages, contact details and cookie consent are present and work.
- Brand styling is consistent across pages, emails and invoices.
- Broken links and missing images are found by a crawl, not by customers.
10. What to automate, and what to keep manual
Automate the checks you run every release and that have a clear right answer. Keep people on the checks that need judgement.
The revenue path and the money maths are the best first candidates for automation testing: they run on every release, and a failure is unambiguous. The practice test above runs seven checks in about 26 seconds. Usability, visual design and exploratory testing stay manual, because their value is in noticing what nobody thought to check.
Also read: Automated Testing Tools: What Works Best for Web Apps?
A release checklist you can copy
Use this as the minimum gate for any release that touches the shop front.
Final thoughts
An e-commerce checklist earns its keep when it is ordered by cost. Test the revenue path and the money maths on every release, read back what forms and totals actually hold, and treat an order as finished only when it exists correctly in your back office. The rest of the list protects the trust that brings shoppers back.
If you want an experienced team to run this checklist against your shop, QAble's e-commerce testing service starts with the revenue path.