Setting Up Continuous Integration for MediaWiki Extensions with GitHub Actions

Introduction

MediaWiki extensions add powerful new features to the wiki platform, but they also increase the maintenance burden. A single regression can break a production wiki, affect downstream extensions, or expose security holes. Continuous Integration (CI) mitigates these risks by automatically building, linting and testing every change before it reaches the main branch.

While Wikimedia’s internal CI infrastructure relies on Zuul and Jenkins, most open‑source extension developers host their code on GitHub. GitHub Actions provides a lightweight, fully‑managed CI environment that can be configured entirely in a .github/workflows file. This guide shows how to set up a robust CI pipeline for a MediaWiki extension using only GitHub‑provided resources and the official setup‑mediawiki action.

Why CI is essential for MediaWiki extensions

  • Early feedback – Unit tests, PHP lint and JavaScript style checks run on every push, catching errors before reviewers see the code.
  • Cross‑version safety – MediaWiki releases follow a regular schedule (REL1_39, REL1_40, …). CI can run the same test suite against multiple core versions to guarantee forward compatibility.
  • Dependency awareness – Many extensions depend on other extensions or on specific core APIs. CI can verify that the declared requires constraints are satisfied.
  • Security hygiene – Automated static analysis (PHPCS, Phan, etc.) detects insecure patterns and deprecated functions.

Prerequisites

  1. A GitHub repository that contains the extension source.
  2. A composer.json that declares the MediaWiki version constraint (e.g. "mediawiki/core": ">=1.39") and any PHP library requirements.
  3. Optional JavaScript tooling (a package.json with a test script for linting).
  4. An understanding of the extension’s entry point – either an extension.json file or a legacy PHP file named after the extension.

GitHub Actions workflow structure

A typical workflow for a MediaWiki extension consists of the following jobs:

  • Setup MediaWiki – Install a fresh MediaWiki instance (SQLite backend) and any required skins or extensions.
  • Composer install & cache – Resolve PHP dependencies and cache the vendor directory for speed.
  • PHP lint & static analysis – Run php -l, phpcs and phan.
  • PHPUnit tests – Execute the extension’s unit tests against the installed MediaWiki.
  • JavaScript lint – If the extension ships JS, run npm test (usually an ESLint run).

Using the setup-mediawiki action

The community‑maintained setup‑mediawiki action installs MediaWiki core, optional skins and extensions, then starts a lightweight PHP built‑in server. It exports a set of outputs that downstream steps can consume, such as api-url and install-directory. The action defaults to an SQLite database, which keeps the CI environment fast and self‑contained.

Key inputs

  • version – Core version to test against (e.g. REL1_43 or a tag like 1.43.0).
  • extensions – A newline‑separated list of extensions to clone. For the extension under test you typically leave this blank and load it manually from the repository checkout.
  • local-settings – Arbitrary PHP that is appended to LocalSettings.php. Useful for enabling test‑only configuration flags.

Example usage

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout extension
        uses: actions/checkout@v4
        with:
          path: extensions/MyExtension

      - name: Set up MediaWiki
        uses: lucaswerkmeister/setup-mediawiki@v1
        id: wiki
        with:
          version: REL1_43
          extensions: |
            # No external extensions needed for this CI run
          local-settings: |
            # Load the extension from the checked‑out directory
            wfLoadExtension( 'MyExtension' );

      - name: Cache Composer packages
        uses: actions/cache@v3
        with:
          path: ${{ steps.wiki.outputs.install-directory }}/vendor
          key: composer-${{ hashFiles('composer.lock') }}
          restore-keys: |
            composer-

      - name: Install PHP dependencies
        run: |
          cd ${{ steps.wiki.outputs.install-directory }}
          composer install --prefer-dist --no-progress --no-interaction

      - name: PHP lint
        run: |
          php -l extensions/MyExtension/**/*.php

      - name: PHPCS
        run: |
          cd ${{ steps.wiki.outputs.install-directory }}
          vendor/bin/phpcs --standard=MediaWiki extensions/MyExtension

      - name: Phan static analysis
        run: |
          cd ${{ steps.wiki.outputs.install-directory }}
          vendor/bin/phan

      - name: Run PHPUnit tests
        run: |
          cd ${{ steps.wiki.outputs.install-directory }}
          php vendor/bin/phpunit --testsuite extensions

      - name: JavaScript lint (optional)
        if: files('**/*.js')
        run: |
          cd extensions/MyExtension
          npm ci
          npm test

The workflow above demonstrates a fully self‑contained CI run. All MediaWiki files live under ${{ steps.wiki.outputs.install-directory }}, which is also the working directory for the PHP tools.

Running PHP unit tests against MediaWiki

MediaWiki ships a phpunit wrapper that bootstraps the core environment. The wrapper is invoked through the composer phpunit:entrypoint script. When testing an extension you typically use the extensions test suite, which loads the core, the extension and any declared dependencies.

cd ${{ steps.wiki.outputs.install-directory }}
composer phpunit:entrypoint -- --testsuite extensions

If the extension defines its own test suite in phpunit.xml.dist, you can reference it directly:

php vendor/bin/phpunit --configuration extensions/MyExtension/phpunit.xml.dist

Static analysis tools

MediaWiki provides a set of pre‑configured static analysis tools that are compatible with the setup-mediawiki environment.

  • PHP lintphp -l catches syntax errors.
  • PHP_CodeSniffer – The mediawiki-codesniffer ruleset enforces MediaWiki coding standards.
  • Phan – MediaWiki ships a mediawiki-phan-config package that knows about the core’s dynamic properties.
  • Minus‑X – Checks for trailing whitespace, missing final newlines and other simple style issues.

All of these can be executed via Composer scripts defined in composer.json. Adding a test script that runs the full PHP test matrix makes the workflow easier to read:

{
  "scripts": {
    "test": [
      "parallel-lint . --exclude vendor",
      "phpcs -sp",
      "phan",
      "minus-x check .",
      "php vendor/bin/phpunit --testsuite extensions"
    ]
  }
}

JavaScript linting and localisation checks

Extensions that ship client‑side code usually include a package.json with a test script that runs ESLint and the MediaWiki banana‑checker localisation validator. The setup-mediawiki action does not interfere with Node.js, so you can safely install the npm dependencies in the extension checkout directory.

{
  "devDependencies": {
    "eslint": "^8",
    "eslint-config-wikimedia": "^0.15"
  },
  "scripts": {
    "test": "eslint . && grunt banana"
  }
}

Running this step is optional but highly recommended for extensions that provide UI strings.

Caching Composer dependencies

Composer downloads can dominate CI runtime. GitHub Actions offers a built‑in cache action that stores the vendor directory between runs. The cache key should be based on composer.lock so that a change in dependencies automatically invalidates the cache.

- name: Cache Composer packages
  uses: actions/cache@v3
  with:
    path: ${{ steps.wiki.outputs.install-directory }}/vendor
    key: composer-${{ hashFiles('composer.lock') }}
    restore-keys: |
      composer-

Running tests against multiple MediaWiki versions

To guarantee forward compatibility you can define a matrix that runs the same job for several core releases. The setup-mediawiki action accepts any valid ref (branch, tag, or release branch name).

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        mw_version: [REL1_39, REL1_40, REL1_41]
    steps:
      - uses: actions/checkout@v4
        with:
          path: extensions/MyExtension
      - name: Set up MediaWiki ${{ matrix.mw_version }}
        uses: lucaswerkmeister/setup-mediawiki@v1
        id: wiki
        with:
          version: ${{ matrix.mw_version }}
          local-settings: |
            wfLoadExtension( 'MyExtension' );
      # …remaining steps identical to the single‑version example…

GitHub will spawn three parallel runners, each testing the extension against a different core version.

Debugging CI failures

If a job fails, the most useful information is usually the LocalSettings.php that was generated by the action. You can add a step that prints the configuration (masking any secrets) to help developers reproduce the environment locally.

- name: Show LocalSettings (debug)
  if: failure()
  run: |
    echo "--- LocalSettings.php ---"
    cat ${{ steps.wiki.outputs.install-directory }}/LocalSettings.php

For deeper debugging you can start an interactive SSH session using the ssh‑action or simply add a sleep 3600 command to keep the runner alive while you SSH into it via the Actions console.

Beyond GitHub Actions – Zuul integration

Large‑scale Wikimedia projects still use Zuul/Jenkins for cross‑project testing, but the workflow described here mirrors the same concepts: a fresh MediaWiki install, extension loading, and a set of standardized entry points (php-lint, phpcs, phan, phpunit, npm test). By keeping the CI definition in .github/workflows you get a portable, repository‑local CI that works for external contributors without requiring access to Wikimedia’s internal infrastructure.

Conclusion

Setting up CI for a MediaWiki extension on GitHub is straightforward once you adopt the community‑approved setup‑mediawiki action and the standardized entry points described in the MediaWiki CI documentation. The resulting pipeline provides:

  • Fast, reproducible builds using SQLite and the built‑in PHP server.
  • Full PHP static analysis (lint, PHPCS, Phan) and unit testing via the core’s PHPUnit wrapper.
  • Optional JavaScript linting and localisation validation.
  • Version‑matrix support for forward‑compatibility testing.
  • Caching of Composer dependencies to keep runtimes under five minutes.

With these pieces in place, every pull request is automatically vetted, reducing the chance of regressions reaching production wikis. The workflow lives entirely in the repository, so new contributors can see the exact steps that will be executed, and the same file can be reused for private MediaWiki deployments, making the CI configuration a single source of truth for both open‑source and internal projects.

Subscribe to MediaWiki Tips and Tricks

Don’t miss out on the latest articles. Sign up now to get access to the library of members-only articles.
jamie@example.com
Subscribe