Multi-Store Testing Strategy for Magento Stores
AI generated
PASS
expect()
E2E Testing · Multi-Store · Cypress · Playwright
Multi-Store Testing Strategy for Magento Stores
Parameterize store context instead of duplicating suites

Maintaining a separate test copy for every language and store means losing both overview and runtime at the same time. A single store-context parameter in Cypress or Playwright is enough to check languages, currencies, and catalog differences, catching typical store-scope configuration bugs reliably before they reach production.

16 min. read Store View · Website · Store Group · CI Magento 2.4.8 · Cypress · Playwright

1. Why multi-store testing is a distinct problem

Magento stores with multiple store views rarely run identical configuration: different languages, currencies, tax display rules, and sometimes even different catalogs per website are the norm, not the exception. The instinctive reaction of many teams is to copy the existing E2E suite once per store and adjust it. That's tolerable for two stores, but turns into a maintenance burden at five or ten stores: every change to the checkout flow has to be replicated in every copy, and drift between the copies often goes unnoticed until a store-specific bug reaches production.

The actual goal of multi-store testing is not to fully cover every combination of store, language, and device, but to specifically check the places where store-scope configuration genuinely changes behavior: price display, translations, tax logic, payment and shipping methods. A working checkout flow doesn't need to be proven ten times over, but a misconfigured currency on a single store view needs to be caught reliably. This shift from full duplication to targeted, parameterized coverage is the core of a sustainable multi-store test strategy.

2. Store View, Website, and Store Group: understanding the test architecture

Magento has three scope levels: Website (its own domain, its own checkout, often its own catalog and prices), Store Group (bundles store views under a website, usually shares a root catalog), and Store View (language, locale, sometimes currency within a store group). For test automation it matters a great deal at which scope level a configuration value lives, because that determines which store/language combinations actually need testing. A value at website scope, such as the product catalog, only needs checking once per website; a value at store-view scope, such as a translation, needs its own test run per store view.

In practice, a central store configuration file that maps this hierarchy explicitly, rather than hiding it implicitly in test names or comments, pays off. Each entry carries a store code, locale, currency, base URL, and its website assignment. This file becomes the single source of truth for every test that checks store-dependent behavior, replacing scattered, hardcoded URLs and language strings across individual spec files.


{
  "stores": [
    {
      "code": "default",
      "website": "base",
      "locale": "de_DE",
      "currency": "EUR",
      "baseUrl": "https://shop.example.com/",
      "taxDisplay": "including_tax"
    },
    {
      "code": "austria",
      "website": "base",
      "locale": "de_AT",
      "currency": "EUR",
      "baseUrl": "https://shop.example.com/at/",
      "taxDisplay": "including_tax"
    },
    {
      "code": "uk",
      "website": "uk_website",
      "locale": "en_GB",
      "currency": "GBP",
      "baseUrl": "https://uk.example.com/",
      "taxDisplay": "excluding_tax"
    },
    {
      "code": "us",
      "website": "us_website",
      "locale": "en_US",
      "currency": "USD",
      "baseUrl": "https://us.example.com/",
      "taxDisplay": "excluding_tax"
    }
  ]
}

3. Parameterizing tests instead of duplicating them

Playwright's projects array in playwright.config.ts is exactly the mechanism that enables multi-store testing without spec duplication: each project gets its own baseURL, locale, and arbitrary custom properties, while the test code itself stays store-agnostic. A single spec like checkout.spec.ts then automatically runs against every configured store without a single line of test logic being duplicated. Store-specific expected values, such as currency symbol or tax display, are read from the store configuration file at runtime instead of being hardcoded in the test.

Cypress achieves the same goal via Cypress.env() combined with either multiple cypress.config.js environment variants or a single config file that reads the store code from an environment variable. The core architectural principle stays identical across both frameworks: the test code never knows store-specific values directly, but requests them through a shared fixture or a store-context helper. This shrinks the maintenance surface to a single configuration source instead of N copies of the entire suite.


// playwright.config.ts - one project per store, shared spec files
import { defineConfig } from '@playwright/test';
import stores from './fixtures/stores.json';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  projects: stores.stores.map((store) => ({
    name: store.code,
    use: {
      baseURL: store.baseUrl,
      locale: store.locale,
      extraHTTPHeaders: { 'X-Store-Code': store.code },
    },
    metadata: {
      currency: store.currency,
      taxDisplay: store.taxDisplay,
    },
  })),
});

// e2e/checkout.spec.ts - one spec, runs against every project/store
import { test, expect } from '@playwright/test';

test('shows the store currency on the product price', async ({ page }, testInfo) => {
  const currency = testInfo.project.metadata.currency as string;
  await page.goto('/catalog/product/view/id/42');
  await expect(page.getByTestId('product-price')).toContainText(currency === 'EUR' ? '€' : '$');
});

4. Testing translations and language fallbacks

Missing translations are one of the most common multi-store bugs, precisely because they usually don't show up in the development environment: that environment typically only carries the default language, and a missing i18n key surfaces only on a secondary store view, often only after launch. Instead of checking translations by hand, it's worth building a data-driven test that loads the expected core strings for each store view from a central fixture and checks them against the rendered text. It's important to explicitly test fallback behavior: does a missing translation show the raw translation key, the English source string, or an empty area? Each of these behaviors requires a different response in the test.

A robust approach doesn't check every single string on every page, but a curated list of critical touchpoints: product page, cart, checkout steps, and confirmation page. These are the places with the biggest business impact when a translation is missing or wrong, and test cost stays manageable because the whole content tree isn't traversed. A simple regex check that catches raw translation keys leaking into the rendered HTML adds a language-agnostic safety net on top.


// e2e/translations.spec.js - data-driven translation check per store view
import { test, expect } from '@playwright/test';
import translations from '../fixtures/translations.json';

test('renders the correct add-to-cart label for this store view', async ({ page }, testInfo) => {
  const storeCode = testInfo.project.name;
  const expectedLabel = translations[storeCode]['add_to_cart'];

  await page.goto('/catalog/product/view/id/42');
  await expect(page.getByRole('button', { name: expectedLabel })).toBeVisible();

  // Guard against raw, untranslated i18n keys leaking into the DOM
  const bodyText = await page.locator('body').innerText();
  expect(bodyText).not.toMatch(/[a-z0-9_]+\.[a-z0-9_]+\s*=\s*/i);
});

5. Testing currency formats and price display

Currency bugs rarely come from a wrong conversion rate, they come from formatting details: the wrong symbol, the wrong position of the symbol relative to the amount, the wrong decimal separator, or a missing thousands separator. 1.234,56 € and €1,234.56 are both correct, but only for their respective matching store view. A test that compares the price against a hardcoded string breaks on every catalog price change. A more robust test derives symbol, decimal separator, and position from the locale in the store configuration and only checks these formatting properties, not the concrete numeric value.

Beyond raw formatting, tax display needs to be checked per store: some store views show prices including VAT, others excluding it, depending on taxDisplay at store scope. A B2C store in Germany typically shows gross prices, while a B2B-oriented UK store often shows net prices with tax broken out separately. An E2E test that stubbornly applies a single price-format assertion across all stores either produces false positives or misses real regressions, depending on which store happened to run first.


// cypress/e2e/pricing.cy.js - currency and tax display assertion driven by store context
import stores from '../fixtures/stores.json';

const storeCode = Cypress.env('STORE_CODE') || 'default';
const store = stores.stores.find((s) => s.code === storeCode);

describe(`Pricing display for store: ${storeCode}`, () => {
  it('renders the price with the correct currency symbol and separators', () => {
    cy.visit(`${store.baseUrl}catalog/product/view/id/42`);

    cy.get('[data-testid="product-price"]').should(($el) => {
      const text = $el.text().trim();

      if (store.currency === 'EUR') {
        expect(text).to.match(/^\d{1,3}(\.\d{3})*,\d{2}\s?€$/);
      } else if (store.currency === 'GBP') {
        expect(text).to.match(/^£\d{1,3}(,\d{3})*\.\d{2}$/);
      } else if (store.currency === 'USD') {
        expect(text).to.match(/^\$\d{1,3}(,\d{3})*\.\d{2}$/);
      }
    });

    // Tax display must match the store scope configuration
    if (store.taxDisplay === 'excluding_tax') {
      cy.get('[data-testid="price-tax-note"]').should('contain.text', 'excl. VAT');
    } else {
      cy.get('[data-testid="price-tax-note"]').should('not.exist');
    }
  });
});

6. Testing catalog differences between websites

The product catalog lives at website scope in Magento: a product can be assigned to one website but not another, and a product's price can differ between websites even when both use the same currency. That creates a distinct class of bugs that plain UI tests on a single store easily miss: a product that's supposed to be visible in the UK shop but was accidentally assigned only to the base website, or a special price maintained only for one website that incorrectly falls back to the base price on a second website.

Meaningful tests therefore don't just check whether a product appears somewhere in the shop, but specifically whether it's visible on the expected website and whether the price there matches the value maintained for that website. A compact sample is usually enough: one or two reference products per website deliberately configured with different prices or visibility rules across websites. These reference products act as canaries for catalog synchronization bugs that would otherwise only surface through customer complaints.

7. Catching store-scope configuration bugs on purpose

The most expensive multi-store bugs don't originate in code, they originate in the store configuration itself: a default currency set incorrectly after a config:set deployment, a forgotten secure base URL for a new store view, a payment method accidentally disabled globally instead of at store scope. These bugs are especially insidious because they don't touch the code at all and therefore never get caught by classic unit or integration tests. They only show up in the actually rendered frontend of a specific store view.

A targeted configuration smoke test that runs against every store after each deployment closes exactly that gap: base URL reachable, correct currency visible on the homepage, at least one payment and one shipping method available in checkout, correct language attribute on the <html> tag. This smoke test is deliberately shallow and fast, because it doesn't check business logic, only pure configuration consistency between what was maintained in the admin and what actually reaches the frontend.

8. Smart test selection: full suite versus smoke tests in CI

As the number of stores grows, the obvious strategy of running the complete suite against every store quickly becomes a CI runtime problem: ten stores times thirty minutes of suite runtime add up to five hours, and even with parallel execution that usually turns into a runner-capacity bottleneck. The risk-based alternative: the full regression suite runs only against the default store, while every other store runs a lightweight smoke suite focused on store-specific aspects: language, currency, basic checkout capability.

This split rests on the observation that most functional bugs are store-independent and would therefore reproduce the same regression on every single store. Store-specific bugs, on the other hand, are almost always configuration issues rather than application logic, and a targeted smoke suite finds them with a high hit rate at low cost. In the CI pipeline this maps cleanly onto a matrix strategy, where one job type runs the full suite and another job type runs the smoke suite across all stores.


# .gitlab-ci.yml - full suite on default store, smoke suite as a matrix over all stores
stages:
  - test

e2e-full-default:
  stage: test
  image: mcr.microsoft.com/playwright:v1.48.0-jammy
  script:
    - npm ci
    - npx playwright test --project=default
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

e2e-smoke-all-stores:
  stage: test
  image: mcr.microsoft.com/playwright:v1.48.0-jammy
  parallel:
    matrix:
      - STORE_CODE: [austria, uk, us, canada, switzerland]
  script:
    - npm ci
    - npx playwright test --project=$STORE_CODE --grep @smoke
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

9. Multi-store test strategies compared

The table below sets the brittle, expensive approaches against the robust alternatives that have proven themselves in grown multi-store setups. The common thread in the robust column: store knowledge lives in configuration and fixtures, not in the test code itself.

Task Brittle / costly Robust / recommended Benefit
Suite per store Full spec copy per store Parameterized fixtures + one spec One change affects every store
Currency check Hardcoded symbol in the assertion Locale-driven format assertion Doesn't break on price changes
CI runtime Full suite on every store Full suite on default + smoke matrix Runtime stays predictable
Store target Hardcoded base URL in the spec Config-driven store matrix New stores need no code change
Translation check Manual click-through per language Data-driven fixture comparison Missing keys surface automatically

Mironsoft

E2E test automation, multi-store setups, and CI pipelines for Magento stores

Ready to test your multi-store setup reliably?

We build parameterized Cypress and Playwright suites for your store views, set up risk-based smoke tests in the CI pipeline, and surface language, currency, and catalog bugs before they go live.

Test architecture audit

Checking existing suites for duplication and store-scope gaps

Parameterization

Store fixtures, Playwright projects, and Cypress config setup

CI integration

Smoke matrix and full-suite strategy in GitLab CI or GitHub Actions

10. Summary

A sustainable multi-store testing strategy addresses one core problem: languages, currencies, and catalogs differ between store views, but the test logic behind them stays the same. Instead of duplicating the entire suite per store, store context is treated as a parameter, via Playwright projects, Cypress environment variables, or a central store fixture file with store code, locale, currency, and base URL. Translations and price formats are checked against this fixture in a data-driven way instead of being hardcoded in the test code.

For CI runtime, the rule of thumb is: not every store needs the full regression suite. The complete suite runs against the default store, while every other store runs a risk-based smoke suite that specifically checks store-scope configuration: currency, language, payment and shipping methods. This combination keeps runtime predictable while still catching the bugs that are actually store-specific, namely configuration errors rather than application logic.

Multi-Store Testing for Magento Stores - The Essentials at a Glance

Parameterize, don't duplicate

Store context via Playwright projects or Cypress env instead of one spec copy per store.

Central store fixture

Keep store code, locale, currency, and base URL in one place, not scattered across specs.

Locale-driven assertions

Derive currency symbol, decimal separator, and tax display from configuration, never hardcode.

Risk-based CI strategy

Full suite only on the default store, lightweight smoke suite as a matrix across every other store.

11. FAQ: Multi-Store Testing for Magento Stores

1What is the difference between Store View, Website, and Store Group?
Website bundles domain, checkout, and often its own catalog. Store Group groups store views under a website. Store View represents language, locale, and sometimes currency.
2Why not duplicate the suite per store?
Changes would need to be replicated manually in every copy. Beyond two or three stores, drift builds up and often surfaces only after launch.
3How do I parameterize Playwright tests?
Via the projects array, generated from a store fixture. Each project gets its own baseURL, locale, and metadata, while the spec code stays store-agnostic.
4How do I parameterize Cypress tests?
Via Cypress.env() plus a store configuration file. Store code, base URL, locale, and currency are requested through a shared helper instead of being hardcoded.
5How do I reliably test translations?
Check against a central translation fixture in a data-driven way, combined with a regex check for raw, untranslated i18n keys in the rendered HTML.
6How do I test currency formats and price display?
Derive symbol, decimal separator, and position from the locale instead of hardcoding, plus a separate check of tax display per store.
7How do I catch catalog differences between websites?
With reference products per website that deliberately have different prices or visibility, acting as canaries for synchronization bugs.
8What is smart test selection?
Full suite only on the default store, lightweight smoke suite on every other store. Keeps CI runtime predictable while keeping a high hit rate.
9How do I structure store configuration data as fixtures?
As a central JSON or YAML file with store code, website, locale, currency, base URL, and tax display per entry, acting as the single source of truth.
10Which store-scope bugs should I test for on purpose?
Wrong default currency after deployment, missing secure base URL, payment methods accidentally disabled globally, incorrect language attribute on the html tag.