Cypress Setup and Fundamentals for Magento Frontends
AI generated
PASS
expect()
Testing · Cypress · E2E Testing · Magento 2
Cypress Setup and Fundamentals for Magento Frontends
Getting started with stable E2E tests for Hyvä stores

Running Magento and Hyvä stores without reliable end-to-end tests means trusting pure luck at checkout, in product search, and with every deployment instead of solid confidence. This article covers Cypress fundamentals for Magento frontends from the ground up: installation, configuring baseUrl and environment variables for dev, staging, and production, a clean project structure, and your first working test against a real page.

12 min. read Cypress · npm · cypress.config.js Magento 2.4.8 · Hyvä Theme · CI/CD

1. Why end-to-end tests with Cypress are essential for Magento frontends

Magento frontends built with Hyvä Theme and Alpine.js look simple at first glance, but hide complex interaction chains: cart updates via AJAX, mini-cart states, configurable products with dynamic price calculation, and multi-step checkout flows. PHPUnit tests reliably verify individual classes and services, but say nothing about whether a customer can actually add an item to the cart and click through to order confirmation in a real browser. That exact gap is what end-to-end testing with real browser rendering closes.

Cypress differs from classic Selenium setups through automatic waiting on DOM changes, built-in time-travel debugging with a snapshot of every test step, and an architecture that runs directly inside the same process as the browser instead of communicating over an external WebDriver. For Magento teams, that means noticeably fewer flaky tests and much faster feedback loops on every deployment, especially in cases where checkout or product search had to be manually re-checked after every release.

2. Installing Cypress: npm, Node version, and project setup

Cypress installs as a regular devDependency via npm or yarn and ships its own Electron browser plus support for Chrome, Firefox, and Edge. A current Node version (Node 18 or newer) is the only real prerequisite; no PHP- or Magento-specific toolchain is required. After installation, the first call to npx cypress open automatically scaffolds the default folder structure with example tests, which can safely be deleted once real tests exist.

In Magento projects, a dedicated package.json outside the Composer ecosystem is recommended, either in the project root or in a dedicated tests/e2e directory, keeping frontend tooling and PHP dependencies cleanly separated. That way Cypress can be updated independently of the Magento build without touching Composer locks, and CI pipelines can cache the test step in isolation.


# Install Cypress as a dev dependency for the Magento frontend project
npm install --save-dev cypress

# Alternatively with yarn
yarn add --dev cypress

# Open the interactive Test Runner once to scaffold the cypress/ folder
npx cypress open

# Check the installed Cypress version
npx cypress version

3. cypress.config.js: baseUrl and environments for dev, staging, and production

The central configuration file cypress.config.js defines, among other things, baseUrl, specPattern, and supportFile. The most important rule for Magento projects: baseUrl must never be hardcoded inside individual tests. It belongs exclusively in the config, so the same test can run against a local Docker environment, staging, or production without changing a single line of test code.

Two approaches work well for multiple environments: either set the CYPRESS_BASE_URL environment variable at invocation time, or load a matching JSON file inside setupNodeEvents based on an --env environment=staging flag. The latter works especially well when credentials, feature flags, or API endpoints also differ per environment alongside the URL, without those values ever landing in the repository.


const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    // Base URL depends on the environment, override via CYPRESS_BASE_URL
    baseUrl: process.env.CYPRESS_BASE_URL || 'https://magento.local',
    specPattern: 'cypress/e2e/**/*.cy.js',
    supportFile: 'cypress/support/e2e.js',
    viewportWidth: 1280,
    viewportHeight: 800,
    retries: {
      runMode: 2,
      openMode: 0
    },
    setupNodeEvents(on, config) {
      // Load environment specific settings, e.g. staging or production
      const environmentName = config.env.environment || 'dev';
      const environmentConfig = require(`./cypress/config/${environmentName}.json`);
      return { ...config, ...environmentConfig };
    }
  },
  env: {
    environment: 'dev'
  }
});

4. Project structure: organizing e2e/, fixtures/, and support/

Cypress suggests three central folders by default: cypress/e2e for the actual test files, cypress/fixtures for static test data such as sample products or customer records in JSON format, and cypress/support for global hooks and reusable commands. In Magento projects it has proven useful to organize e2e not by page type but by business domain, for example catalog/, checkout/, and customer/, so test cases stay close to the business flows that can actually break.

Spec files should consistently end in *.cy.js and cover one clearly scoped topic per file, rather than maintaining a single giant spec with hundreds of test cases. Environment-specific configuration can additionally live in a dedicated cypress/config folder, keeping dev.json, staging.json, and production.json cleanly separated and available to load selectively from cypress.config.js.


cypress/
  e2e/
    catalog/
      product-listing.cy.js
      product-detail.cy.js
    checkout/
      guest-checkout.cy.js
      add-to-cart.cy.js
    customer/
      login.cy.js
      registration.cy.js
  fixtures/
    product.json
    customer.json
  support/
    commands.js
    e2e.js
  config/
    dev.json
    staging.json
    production.json
cypress.config.js
package.json

5. Your first test: checking the homepage and product listing

The most sensible starting point is a simple smoke test against the homepage: cy.visit('/') loads the page through the configured baseUrl, and assertions then verify that the main navigation is visible and the page title contains the expected store name. This first test finishes in a few seconds but already surfaces fundamental rendering and routing problems, such as a broken full page cache or a broken layout handle after a deployment.

The next logical step is a test against a category page: Cypress visits the product listing page and checks that at least one product renders in the grid, with name and price visible on each product tile. Tests against real pages with real data like this are more valuable than isolated component tests, because they verify the interplay of layout XML, blocks, and Alpine.js interactions exactly the way a customer experiences it.


// cypress/e2e/catalog/product-listing.cy.js
describe('Magento homepage and product listing', () => {
  it('loads the homepage and shows the main navigation', () => {
    cy.visit('/');
    cy.get('[data-cy="main-navigation"]').should('be.visible');
    cy.title().should('include', 'Mironsoft');
  });

  it('shows products on the category page', () => {
    cy.visit('/catalog/category/view/id/20');
    cy.get('[data-cy="product-grid-item"]').should('have.length.greaterThan', 0);
    cy.get('[data-cy="product-grid-item"]').first().within(() => {
      cy.get('[data-cy="product-name"]').should('be.visible');
      cy.get('[data-cy="product-price"]').should('be.visible');
    });
  });
});

6. Custom commands and support files for recurring Magento flows

Login, cart interactions, and dismissing cookie banners show up in almost every test. Instead of repeating these steps in every spec, they belong in cypress/support/commands.js as custom commands, registered via Cypress.Commands.add('login', ...) or Cypress.Commands.add('addProductToCart', ...). This drastically reduces duplication and means changes to the login flow, say after a form redesign, only need to be applied in one place.

The cypress/support/e2e.js file loads globally before every test and is the right place for project-wide beforeEach hooks, for example accepting the cookie consent layer by default or registering cy.intercept() rules meant to apply across all tests. This keeps each individual spec file focused on its actual test case instead of dealing with boilerplate for recurring Magento quirks.

7. Headless vs. headed: cypress run and cypress open in daily development

cypress open launches the interactive Test Runner UI with a visible browser, time-travel debugging, and live reload on every file change. This mode is the right place to write new tests and interactively verify selectors, since every test step remains inspectable as a DOM snapshot and network requests are directly visible in the panel. For day-to-day work on a new checkout test, this visual mode is practically indispensable.

cypress run executes the same tests headless with no UI, noticeably lighter on resources and faster, which makes it the default mode for CI pipelines. Flags like --browser chrome, --spec "cypress/e2e/checkout/**", or --record let you narrow the run or connect it to Cypress Cloud. Screenshots on failure and optional videos of every run are automatically stored under cypress/screenshots and cypress/videos, which considerably simplifies debugging failed CI runs.

8. Selector strategy: data-cy attributes instead of fragile CSS classes

A common mistake in early Cypress setups: tests target Tailwind utility classes like .flex or .bg-lime-600, which change on the next Hyvä theme redesign even though the underlying business behavior hasn't changed at all. Every styling tweak then silently breaks tests that were only ever meant to verify that a button exists and is clickable. This coupling between visual design and test code is the most common cause of brittle E2E suites.

The robust fix is dedicated data-cy attributes right inside the phtml templates, for example data-cy="add-to-cart-button", which exist exclusively for tests and stay completely untouched by styling changes. These attributes should be applied sparingly and deliberately at interaction points such as buttons, forms, and status indicators, not sprinkled across the entire markup, to keep the template readable and keep the test focus on elements that actually matter.

9. CI integration: running Cypress in GitHub Actions and GitLab CI

In the CI pipeline, Cypress practically always runs headless via cypress run, ideally against a freshly deployed staging environment rather than production data. The official cypress-io/github-action automatically handles installation, node_modules caching, and the test run in a single step, while GitLab CI covers the same flow via a Docker image with Cypress pre-installed and a regular npm run cypress:run job.

On failure, screenshots and videos should be uploaded as artifacts so a failed CI run can be understood without a local reproduction. For larger suites, parallelization across multiple CI jobs with --record --parallel is worth it to keep total runtime low despite a growing number of tests. The table below summarizes the most common pitfalls in a Cypress setup and the recommended fix for each.


name: e2e-tests
on:
  pull_request:
  push:
    branches: [main]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Run Cypress against staging
        uses: cypress-io/github-action@v6
        with:
          browser: chrome
          headless: true
        env:
          CYPRESS_BASE_URL: https://staging.mironsoft-shop.example
          CYPRESS_environment: staging

      - name: Upload screenshots on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: cypress-screenshots
          path: cypress/screenshots
Area Recommended approach Typical mistake Why it matters
Base URL baseUrl + CYPRESS_BASE_URL per environment URL hardcoded in every test One test runs against dev, staging, and production
Selectors data-cy attributes in phtml templates CSS classes/Tailwind utilities as selectors Tests survive Hyvä theme redesigns
Waiting cy.intercept() + targeted assertions cy.wait(5000) as a fixed delay Faster, more stable tests without guesswork
Execution cypress run --headless in CI Only tested locally with cypress open Regressions surface before deployment
Test data Fixtures + API seeding before the run Tests depend on manually created products Reproducible runs independent of data state

In practice, these five points reinforce each other: a clean baseUrl configuration doesn't help much if selectors break on every styling update, and stable selectors don't help much if tests still flake due to fixed wait times. Applying all five recommendations consistently produces a Cypress suite that stays maintainable for months, instead of being ignored after a few weeks.

Mironsoft

E2E testing, Cypress setup, and CI/CD integration for Magento stores

Ready to set up Cypress testing for your Magento store?

We build a stable Cypress setup for your Hyvä store, from project structure through meaningful custom commands to full CI integration with actionable failure reports.

Cypress setup audit

Analysis of existing tests, prioritized by stability and maintainability

Hyvä test coverage

data-cy attributes, custom commands, and stable checkout tests

CI/CD integration

Headless runs in GitHub Actions or GitLab CI with artifacts

10. Summary

A solid Cypress setup for Magento frontends starts with a clean separation of test code and configuration: npm install cypress for installation, cypress.config.js with an environment-dependent baseUrl instead of hardcoded URLs, and a project structure that clearly separates cypress/e2e, cypress/fixtures, and cypress/support by business domain. The first test against the homepage and product listing delivers real value within minutes, because it surfaces rendering and routing bugs that PHPUnit tests fundamentally cannot catch.

The decisive difference between a Cypress suite that stays reliable for months and one that gets ignored after a few weeks lies in the details: stable data-cy selectors instead of Tailwind classes, cy.intercept() instead of fixed wait times, and a consistent headless run in the CI pipeline instead of purely manual checks before every release. Getting these fundamentals right from the start saves months of rework on a brittle test suite later.

Cypress Setup for Magento Frontends - The Essentials at a Glance

Installation & config

npm install --save-dev cypress, baseUrl in cypress.config.js instead of hardcoded URLs.

Project structure

Organize cypress/e2e, cypress/fixtures, and cypress/support by business domain.

First test & selectors

Smoke test against homepage/PLP, stable data-cy attributes instead of CSS classes.

CI integration

cypress run --headless in GitHub Actions/GitLab CI with screenshot artifacts on failure.

11. FAQ: Cypress setup for Magento frontends

1What is the difference between cypress open and cypress run?
cypress open shows the interactive UI with a visible browser and time-travel debugging. cypress run executes headless, faster and lighter on resources, the default for CI pipelines.
2How do I set up different baseUrl values for dev, staging, and production?
Via CYPRESS_BASE_URL as an environment variable, or via setupNodeEvents, which loads a matching JSON configuration based on an environment flag.
3Where should Cypress tests live in a Magento project?
In a dedicated tests/e2e directory with its own package.json, separated from the Composer ecosystem, organized by business domain such as catalog or checkout.
4Why should I use data-cy attributes instead of CSS classes as selectors?
CSS/Tailwind classes change with redesigns and silently break tests. data-cy attributes exist exclusively for tests and stay stable.
5How do I avoid fixed wait times like cy.wait(5000)?
Use cy.intercept() to wait specifically on network requests instead of guessing at fixed delays. Cypress also waits automatically for DOM changes.
6Can I test Hyvä/Alpine.js components with Cypress?
Yes, Cypress automatically waits for Alpine.js-driven DOM changes. Stable data-cy attributes make these interactions reliably testable.
7How do I integrate Cypress into GitHub Actions or GitLab CI?
Via the official cypress-io/github-action in GitHub Actions, or a Docker image with Cypress pre-installed and an npm run cypress:run job in GitLab CI.
8Do I need a separate fixture file for every test?
No, group fixtures by data type, e.g. product.json, and reuse them across multiple tests. Add API seeding before the run for dynamic data.
9Is Cypress a replacement for PHPUnit tests?
No, the two complement each other. PHPUnit checks classes in isolation quickly, Cypress checks the real-browser interplay the way a customer experiences it.
10Should Cypress run against staging or against production?
Against a freshly deployed staging environment, not production data, so even destructive actions can be tested safely.