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
requiresconstraints are satisfied. - Security hygiene – Automated static analysis (PHPCS, Phan, etc.) detects insecure patterns and deprecated functions.
Prerequisites
- A GitHub repository that contains the extension source.
- A
composer.jsonthat declares the MediaWiki version constraint (e.g."mediawiki/core": ">=1.39") and any PHP library requirements. - Optional JavaScript tooling (a
package.jsonwith atestscript for linting). - An understanding of the extension’s entry point – either an
extension.jsonfile 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
vendordirectory for speed. - PHP lint & static analysis – Run
php -l,phpcsandphan. - 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_43or a tag like1.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 toLocalSettings.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 extensionsIf 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.distStatic analysis tools
MediaWiki provides a set of pre‑configured static analysis tools that are compatible with the setup-mediawiki environment.
- PHP lint –
php -lcatches syntax errors. - PHP_CodeSniffer – The
mediawiki-codesnifferruleset enforces MediaWiki coding standards. - Phan – MediaWiki ships a
mediawiki-phan-configpackage 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.