ARTICLE

Testing Alpine.js Storefront Interactions on Hyva With Playwright: End-to-End Coverage for Magento Checkout

Testing Alpine.js Storefront Interactions on Hyva With Playwright: End-to-End Coverage for Magento Checkout

Playwright is the most reliable way to test a Hyva Magento storefront end to end. Because Hyva renders through Alpine.js and loads customer data client-side, Playwright’s auto-waiting and web-first assertions fit where older tools fight the timing. The catch is that a naive suite still flakes for Hyva-specific reasons this guide fixes.

We build and maintain Hyva development projects, and storefront tests are part of the release gate. Below is the setup we use, a real guest checkout flow, and the two Hyva-specific traps that cause most flaky failures.

Why Playwright, and where MFTF falls short

Magento ships its own browser test tool, the Magento Functional Testing Framework (MFTF), built on Selenium and Codeception and authored entirely in XML. It is still maintained on its 4.x line, and Adobe still requires MFTF tests for Marketplace and EQP extension submissions, so it is not going away. But its model was designed for Luma’s server-rendered pages driven by Knockout and jQuery.

Hyva is a different frontend. It replaces RequireJS, Knockout, and jQuery with inline Alpine.js and Tailwind, so a lot of the storefront behaves like a small single-page app: state lives in x-data components, DOM updates are reactive, and the cart and customer data arrive after the page loads. MFTF’s XML and Selenium waits are an awkward fit for that. Playwright, per the official best-practices guide, was built around auto-waiting actionability checks and web-first assertions that retry until the DOM settles, which is exactly the behavior a reactive storefront needs.

MFTF Playwright
Test language XML TypeScript / JavaScript
Engine Selenium + Codeception Chromium / WebKit / Firefox driver
Waiting model Explicit waits Auto-waiting + web-first assertions
Fit for Alpine reactivity Poor (built for Luma) Strong
Still required for Marketplace extension QA Your own release gate
Debugging Allure reports Trace Viewer, Inspector, VS Code

The honest read: keep MFTF where Adobe requires it, and use Playwright for your storefront regression suite on Hyva.

The setup for a Hyva store

Scaffold with npm init playwright@latest in TypeScript. Playwright is on the 1.6x line (1.63 at time of writing) and the browser install step, npx playwright install --with-deps, pulls the browsers and OS dependencies.

Two configuration decisions matter more than the rest. First, point baseURL at a staging Hyva environment, never production, because these tests create accounts and orders. Second, use Playwright projects: a setup project that authenticates once, and browser projects that reuse that state and depend on setup.

// playwright.config.ts (excerpt)
export default defineConfig({
  use: { baseURL: process.env.BASE_URL, trace: 'on-first-retry' },
  retries: process.env.CI ? 2 : 0,
  projects: [
    { name: 'setup', testMatch: /auth\.setup\.ts/ },
    { name: 'chromium', use: { storageState: 'playwright/.auth/customer.json' }, dependencies: ['setup'] },
  ],
});

A seed step should disable login CAPTCHA and admin multi-login lockouts on staging, and create the fixture products the suite expects, so tests are deterministic. The open-source elgentos/magento2-bdd-e2e-testing-suite and ProxiBlue/m2-hyva-playwright both follow this pattern and are worth reading before you write your own harness.

Reuse the session with storageState

Logging in through the storefront form on every test is slow and flaky. Playwright’s authentication guide recommends logging in once and saving the browser context, which includes cookies and localStorage, to a file. Reusing that state commonly cuts total run time by well over half.

// auth.setup.ts
setup('authenticate customer', async ({ page }) => {
  await page.goto('/customer/account/login');
  await page.getByLabel('Email').fill(process.env.TEST_EMAIL!);
  await page.getByLabel('Password').fill(process.env.TEST_PASSWORD!);
  await page.getByRole('button', { name: /sign in/i }).click();
  await expect(page.getByRole('heading', { name: /my account/i })).toBeVisible();
  await page.context().storageState({ path: 'playwright/.auth/customer.json' });
});

Keep one state file per role, put the .auth directory in .gitignore, and regenerate it each run so an expired Magento session does not cause silent failures. A guest has no state file, which is the point of testing guest checkout separately.

Trap one: strict mode versus hidden Alpine elements

This is the single most common reason a Hyva Playwright suite flakes, and it surprises people coming from Luma.

Hyva scatters conditional Alpine elements through the DOM that are present but hidden until state changes, for example a message block like <div x-show="displayErrorMessage" class="message error">. A CSS locator such as page.locator('.message.error') matches both the hidden template instance and any visible one, which trips Playwright’s strict mode and throws a “resolved to multiple elements” error even when the page looks correct.

The fix is to stop selecting by class and select by what the user perceives. Follow Playwright’s locator priority: getByRole with an accessible name first, then getByLabel and getByText, then getByTestId as a deliberate escape hatch, and CSS or XPath only as a last resort. When you must use a class, either assert visibility with toBeVisible(), which ignores the hidden instance, or scope the locator to the active component root with page.locator('[x-data]').getByText(...). This same DOM-inspection discipline is what makes debugging Alpine.js on Hyva tractable in the first place.

Trap two: cart and customer data render after the page

Under Magento full page cache, the cart count, mini-cart contents, customer name, and success messages are not in the server HTML. They are injected client-side by Magento_Customer/js/customer-data.js, which calls the /customer/section/load/ endpoint, caches the result in the mage-cache-storage localStorage key, and is gated by the private_content_version cookie. Adobe documents this in the private content guide.

A test that asserts on cart state immediately after add-to-cart reads the page before that AJAX resolves, and flakes. The correct pattern is a web-first assertion on the rendered value, which auto-retries until the section data hydrates:

await page.getByRole('button', { name: /add to cart/i }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1'); // waits for sectionData

When you need an explicit signal, wait for the section load response with page.waitForResponse(r => r.url().includes('/customer/section/load/')) before asserting. What you should not lean on is waitForLoadState('networkidle'). On Hyva, Alpine timers and section polling keep the network busy, so network idle is an unreliable gate. Web-first assertions are the replacement. Fast interaction timing, which is also what a real INP-focused Hyva build optimizes for, makes these assertions resolve quickly rather than sitting at the timeout.

A guest checkout flow, start to finish

Guest checkout is the highest-value path to protect because it converts the most revenue and touches the most Alpine components.

test('guest can complete checkout', async ({ page }) => {
  await page.goto('/hyva-test-product');
  await page.getByRole('button', { name: /add to cart/i }).click();
  await expect(page.getByTestId('cart-count')).toHaveText('1');

  await page.goto('/checkout');
  await page.getByLabel('Email').fill('guest@example.test');
  await page.getByLabel('First Name').fill('Test');
  await page.getByLabel('Last Name').fill('Buyer');
  await page.getByLabel('Street Address').fill('1 Example St');
  // ...city, region, postcode, phone
  await page.getByText(/flat rate|standard shipping/i).click();
  await page.getByRole('button', { name: /next|payment/i }).click();

  await page.getByText(/check ?\/ ?money order|test payment/i).click();
  await page.getByRole('button', { name: /place order/i }).click();
  await expect(page.getByRole('heading', { name: /thank you/i })).toBeVisible();
});

Logged-in checkout is a separate spec that runs under the saved customer state, and it should assert the components that only exist when authenticated, such as the saved-address selector. Guest and logged-in checkout diverge in their Alpine component tree on Hyva, so testing one does not cover the other.

Which flows to protect first

You will not cover the whole storefront at once, and you should not try. Prioritize by revenue risk and by how often the code changes. The order we start with on most stores is: guest checkout, logged-in checkout, add to cart and mini-cart, customer login and registration, and search with at least one layered-navigation filter. Those five paths carry the majority of revenue and touch the Alpine components most likely to break during a theme change or an extension update.

Below that tier, add coupon and cart price rule application, the account dashboard, and any B2B flows such as requisition lists or quote requests, which have their own Alpine components and their own private-content sections. Resist writing dozens of assertions per test. A focused test that proves one user goal completed is more valuable and less brittle than one that checks every element on the way.

Isolation is what keeps a growing suite trustworthy. Each test should create or claim its own data rather than share a fixture another test mutates, and parallel workers should not compete for the same customer account. The ProxiBlue/m2-hyva-playwright framework handles this with a per-worker admin account helper, and the pattern is worth copying: derive the account or SKU from the worker index so two tests never collide. Shared state is the second most common cause of flakes on a Hyva suite, after the strict-mode and hydration traps above.

Debugging a failure with the Trace Viewer

When a test fails in CI, the fastest path to a cause is the Playwright trace, which is why the config above sets trace: 'on-first-retry'. The trace records a timeline of actions, network calls, console output, and a DOM snapshot at each step, so you can open the exact moment an assertion failed without reproducing it locally.

For a Hyva flake this is decisive. You can see whether the /customer/section/load/ call had returned when the cart assertion ran, whether an Alpine component had hydrated, and whether a locator matched a hidden element. Upload the playwright-report and the trace as CI artifacts on every run, and a red build becomes a five-minute diagnosis instead of a rerun-until-it-passes guess. The Inspector and the VS Code extension give the same visibility while writing tests locally.

Running it in CI

Add .github/workflows/playwright.yml that runs on push and pull request. The steps are standard: check out, set up Node 20.19 or newer, npm ci, npx playwright install --with-deps, then npx playwright test. For a large suite, shard across jobs with a matrix, and upload the HTML report and traces as artifacts so a failure is debuggable from the Trace Viewer without rerunning locally. Point baseURL at an ephemeral or staging Magento, never production. A green storefront suite belongs in the same release pipeline as the rest of a Magento and Adobe Commerce build.

FAQ

Can I use Playwright instead of MFTF on Magento?

For your own storefront regression tests, yes, and on Hyva it is the better fit because Playwright’s auto-waiting suits Alpine’s reactive DOM. Keep MFTF only where Adobe still requires it, such as Marketplace extension submissions. The two can coexist in one project.

Why do my Hyva Playwright tests flake on cart and message assertions?

Almost always because the value is rendered client-side after an AJAX call to /customer/section/load/, and the test asserts too early. Use a web-first assertion like toHaveText, which retries until the section data hydrates, instead of a fixed wait or a screenshot taken immediately after the click.

What causes “locator resolved to multiple elements” on Hyva?

Hyva keeps conditional Alpine elements in the DOM but hidden with x-show, so a class-based locator matches both the hidden and visible copies and violates strict mode. Select by role or text, or assert with toBeVisible(), which targets only the visible instance.

Should I test against production?

No. End-to-end checkout tests create real accounts and orders. Run against a staging or ephemeral environment with CAPTCHA disabled and fixture data seeded, and never point baseURL at the live store.

How much faster is reusing login state?

Reusing an authenticated context through storageState typically removes most of the per-test login overhead, commonly cutting total suite time by more than half, because each test starts already signed in instead of driving the login form.

Where this fits

Bemeir is the USA’s leading official Hyva partner and a full-service Magento and Adobe Commerce agency with a large technology partner network. Testing is part of how we ship storefronts that stay fast under change, alongside disciplined release engineering. We also build on Shopify, Shopware, and BigCommerce, so our QA practices carry across platforms. To see how we work as an extension of your team, read about Bemeir or get in touch.

Let us help you get started on a project with Testing Alpine.js Storefront Interactions on Hyva With Playwright: End-to-End Coverage for Magento Checkout and leverage our partnership to your fullest advantage. Fill out the contact form below to get started.

more articles about ecommerce

Read on the latest with Shopify, Magento, eCommerce topics and more.