No results found.

CI & testing

Magento 2 GitHub Actions Workflows

One shared set of lint, static analysis and PHPUnit workflows, so a Magento module's CI is a short list of uses: lines and a new release is added in one place.

Source on GitHub ↗Magento CI testing imagesGitHub Actions docs

Modules calling it
7
Magento versions mapped
70
Releases in the default matrix
5

Every Magento module I publish needs the same CI: validate composer.json, lint the PHP, run PHPCS and PHPStan, then PHPUnit on each release I support. This repository holds that once, as reusable GitHub Actions workflows. A module’s CI becomes a short file of uses: lines, and when Magento ships a new release the supported list changes here and every module picks it up on its next run.

As of 11th October 2026, seven of my modules call it: Verbose DB Status, Media Proxy, Fetch Priority, Auto Login Admin, TinyMCE Anchors, and the example banner and footer modules used to validate deployments.

What does a module’s CI look like with it?

Four jobs. This is the whole of Media Proxy’s .github/workflows/ci.yml, minus the on: block:

jobs:
  lint:
    name: Linter
    uses: samjuk/github-actions/.github/workflows/magento2-test-lint.yaml@master

  static:
    name: Static Analysis
    uses: samjuk/github-actions/.github/workflows/magento2-test-static.yaml@master
    needs: lint

  supported-versions:
    name: Supported Versions
    uses: samjuk/github-actions/.github/workflows/magento-supported-versions.yaml@master

  phpunit-tests-ghas:
    name: PHPUnit Tests (GHAS)
    uses: samjuk/github-actions/.github/workflows/magento2-test-phpunit-module-ghas.yaml@master
    needs: [static, supported-versions]
    with:
      magento_version: ${{ matrix.version }}
      magento_distribution: ${{ matrix.distribution }}
      run_unit_tests: true
      run_integration_tests: true
    strategy:
      fail-fast: false
      matrix:
        include: ${{ fromJson(needs.supported-versions.outputs.versions) }}

magento-supported-versions returns the release list as JSON, and the PHPUnit job fans out across it. By default that is Magento 2.4.9, 2.4.8-p5, 2.4.7-p10 and 2.4.6-p15, plus Mage-OS 3.5.0. A module that needs something different passes include or exclude lines (exclude takes a glob such as 2.4.9*) rather than keeping its own copy of the list.

What is in it?

Reusable workflows:

WorkflowWhat it runs
magento2-test-lintcomposer validate, then php -l on every PHP file. PHP 8.4 by default.
magento2-test-staticPHPCS with the Magento coding standard and PHPCompatibility, through a shared ruleset (140 character lines, docblock rules relaxed) at severity 6. PHPStan at level 5 with bitexpert/phpstan-magento.
magento-supported-versionsThe shared release list above.
magento2-test-phpunit-module-ghasUnit tests, setup:di:compile and integration tests for a module.
magento2-test-phpunit-project-ghasThe same for a whole project, globbing app/code.
magento2-test-unit-*-ghas, *-wardenThe older unit-only workflows, in a GitHub services or a Warden flavour. The PHPUnit workflows replace the GHAS ones.

Actions they build on:

  • magento/compute-software-requirements maps a Magento or Mage-OS version to its PHP, Composer, MySQL, MariaDB, OpenSearch, Redis, Valkey, Varnish and RabbitMQ versions. The data is a manifest.json covering 70 Magento versions from 2.4.4 to 2.4.9, scraped from Adobe’s system requirements page with a browser console script kept next to it.
  • warden/create-env writes a Warden .env from inputs with defaults, and warden/setup-environment checks out Warden at a tag and brings the environment up on the runner.

How does the PHPUnit workflow get a Magento install?

It does not install one. The job runs inside the pre-installed Magento CI testing image for that release, samjuk/magento-ci-testing-env:<version>-php<php> (or the Mage-OS equivalent), with MySQL, Redis and OpenSearch as service containers at the versions compute-software-requirements picks.

From there it requires the module from the checkout through a Composer path repository, runs Test/Unit, runs setup:di:compile, points the integration test config at the services and runs Test/Integration. Either test directory can be missing and is skipped. If tests were asked for and neither exists, the job fails rather than reporting green with nothing run, because that almost always means a wrong module_path.

How is it tested?

Against fixture modules in the repository. Each reusable workflow has an _internal-test-* workflow that calls it on changes:

  • the module workflow runs against demo-package for unit and for integration tests, against demo-package-unit-only with both flags on (to prove the missing integration directory is skipped), and again for an older PHPUnit, the Mage-OS image swap and a PHP version override;
  • the project workflow runs against demo-project;
  • the supported versions workflow is called three ways (default, include and exclude, Mage-OS scoped) and an assert job compares each output with the exact JSON expected.

The demo-package-no-tests fixture, for the fail-when-nothing-ran path, was checked by hand. GitHub Actions does not allow continue-on-error on a job that calls a reusable workflow, so an expected failure would leave the self-test permanently red.

Is this the same as magento.works?

No. It answers one question: does my module still work on every release I support? The images it runs on are the Magento CI testing images, which take the Magento install out of each run. magento.works asks a different one: which service versions each Magento release actually runs on.

Cache Debounce still runs its own CI.

What is still rough?

  • Fail on PHPStan. The step ends in || true, so findings show in the log and never block a merge.
  • Lint YAML or XML. Both jobs exist but exit straight away, marked TODO.
  • Offer a version to pin. There are no tags, and callers use @master, so a change here reaches every module on its next run.
  • Run integration tests on 2.4.4 or earlier. Those releases default to Elasticsearch 7 settings the workflow does not configure, which is why 2.4.4 is out of the default list.