Leider viel zu häufig sehe ich bei meinen Runden im WordPress Supportforum Mitteilungen von Anwendern von Plugins, dass diese nach einem Update einen Fehler zeigen. Am schlimmsten sind natürlich die, wo der Anwender seine Website mit einem Plugin-Update sein ganzes Web für ihn nicht mehr erreichbar gemacht hat. Ich frage mich oft, wie Plugin-Entwicklern so etwas passieren kann zumal es ausreichend Methoden gibt, um genau das zu verhindern. Einige davon, die ich auch selbst für meine Plugins verwende, möchte ich hier vorstellen.
Hintergrund
Entwickler von WordPress Plugins müssen sich an einige Regeln halten, die durch die WordPress Community in Form der Plugin Guidelines vorgegeben ist. Darin gibt es u.a. diese Maßgabe:
3. A stable version of a plugin must be available from its WordPress Plugin Directory page.
Leider achten hierauf einige nicht, was dann beim Anwender zu z.B. solchen Fehlern führt:

Die meisten Anwender sind dann völlig aufgeschmissen. Vielfach muss ein Experte ran, um das Problem zu lösen.
Plugin-Releases richtig bauen
Entwickler haben tatsächlich die Wahl zwischen verschiedenen Methoden um abzusichern, dass ihr frisch zu veröffentlichendes Plugin-Update auch wirklich lauffähig ist. Ich möchte hier einige davon vorstellen, die ich bei meinen eigenen Plugins bereits aktiv einsetze.
Vorbemerkung
Wie ein Release gebaut ist, ist jedem Entwickler selbst überlassen. Ziel muss es sein, eine ZIP-Datei oder ein Verzeichnis zu haben, in dem alle zu veröffentlichenden Dateien liegen – sprichwörtlich die Dateien die zum Plugin gehören. Ich gehe daher hier nicht darauf ein WIE ein solches Release bereitgestellt wird sondern wie dieses getestet werden kann.
Ich selbst erstelle immer auf meinem Entwicklungs-PC eine ZIP-Datei, die ich dann wiederum entpackt per SVN einchecke. So viel nur als Vorbemerkung damit ihr in den kommenden Beschreibungen einen Weg für euch finden könnt.
Der manuelle Weg
Diesen Weg gehe ich seit ich Plugins entwickle bei sprichwörtlich jedem Release. Sobald ich meine ZIP-Datei fertig habe, öffne ich eine Test-Umgebung mit einem frisch installierten WordPress und installiere dort das Plugin mit dieser ZIP-Datei.
Beim Aktivieren des Plugins in dieser Umgebung darf es keine Auffälligkeiten geben. Auch keinen Eintrag im Error-Log. Nur wen das der Fall ist, veröffentliche ich das Release.
Je nachdem um welche Änderung es geht, schaue ich mir auch diese ggfs. in dem Testsystem manuell nochmal an. Nur so kann ich selbst sicher sein, dass das was ich geändert habe auch wirklich funktionsfähig ist und bei niemandem zu einem Fehler führt.
Der Smoke-Test
In der Software-Entwicklung sind diese Art Tests durchaus weit verbreitet. Dank dem WordPress Playground kann man diesen Test nun auch am selbst entwickelten Plugin durchführen. Voraussetzung dafür ist lediglich npm (auch wenn es nur für die eigene Entwicklungsumgebung genutzt ist). Für den Playground verwendet man Blueprint-Dateien, die vorgeben was in der Umgebung passieren soll.
Hier ein Beispiel für eine smoke.json Blueprint-Datei:
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"preferredVersions": { "php": "8.4", "wp": "latest" },
"login": true,
"steps": [
{
"step": "activatePlugin",
"pluginPath": "my-plugin-slug/my-plugin-slug.php"
},
{
"step": "runPHP",
"code": "<?php require_once '/wordpress/wp-load.php'; if ( ! defined( 'MY_PLUGIN_CONST' ) ) { throw new Exception( 'plugin constants missing' ); } file_put_contents( '/wordpress/wp-content/plugins/my-plugin-slug/smoke-result.txt', 'SMOKE_OK' );"
}
]
}
Installiert als erstes das benötigte npm Paket:
npm install @wp-playground/cli
Erstellt ein temporäres Verzeichnis, welches für die Ausführung des lokalen Playgrounds genutzt wird:
mkdir -p /tmp/smoke
Entpackt die Release-ZIP dorthin:
unzip my-plugin-slug.zip -d /tmp/smoke
Und jetzt kann man den Test selbst ausführen:
npx @wp-playground/cli@latest run-blueprint --blueprint=/path/to/the/smoke.json --mount=/tmp/smoke/my-plugin-slug:/wordpress/wp-content/plugins/my-plugin-slug --verbosity=debug --debug 2>&1
Beachte, dass du bei bei allen Kommandos deinen eigenen Plugin-Slug mit einfügen sowie den Pfad zu deiner smoke.json angegeben musst.
Sobald du das Kommando erfolgreich ausgeführt hast, kommt eine Ausgabe ähnlich dieser hier:
Native temp dir for VFS root: /tmp/node-playground-cli-site-415009--415009-4dhGtXMUZvYM
Mount before WP install: /home -> /tmp/node-playground-cli-site-415009--415009-4dhGtXMUZvYM/home
Mount before WP install: /tmp -> /tmp/node-playground-cli-site-415009--415009-4dhGtXMUZvYM/tmp
Mount before WP install: /tools -> /tmp/node-playground-cli-site-415009--415009-4dhGtXMUZvYM/tools
Mount before WP install: /wordpress -> /tmp/node-playground-cli-site-415009--415009-4dhGtXMUZvYM/wordpress
Mount after WP install: /wordpress/wp-content/plugins/my-plugin-slug -> /tmp/smoke/my-plugin-slug
Resolved WordPress release URL: https://downloads.w.org/release/wordpress-x.y.z.zip
Done
Das Ergebnis des Tests wurde entsprechend den Angaben in der smoke.json nach /tmp/smoke/my-plugin-slug/smoke-result.txt geschrieben. Im Idealfall steht dort
SMOKE_OK
Falls nicht, dann konnte das Plugin im Playground nicht gestartet werden – etwas was jedem der Anwender des Plugins bei dessen Installation passieren könnte. In dem Fall musst du die Ursache genauer untersuchen und beheben bis der Test erfolgreich durchgeführt werden kann.
Diesen Test kann man natürlich auch automatisieren. Wer sein Plugin bei GitHub verwaltet, kann dort GitHub Actions zu dessen Ausführung verwenden. Hier ein Beispiel für die yml-Datei des Releases wie es nach Zusammenstellen der Release-ZIP ausgeführt werden könnte:
- name: Smoke test the release zip in WordPress Playground
run: |
set -o pipefail
mkdir -p /tmp/smoke
unzip -q ${{ github.event.repository.name }}_*.zip -d /tmp/smoke
npx @wp-playground/cli@latest run-blueprint \
--blueprint=./.github/blueprints/smoke.json \
--mount=/tmp/smoke/${{ github.event.repository.name }}:/wordpress/wp-content/plugins/${{ github.event.repository.name }} \
--verbosity=debug \
--debug \
2>&1 | tee /tmp/smoke.log
echo "----- Vollständiges Playground-Log -----"
cat /tmp/smoke.log
echo "----- Inhalt smoke-result.txt -----"
cat /tmp/smoke/${{ github.event.repository.name }}/smoke-result.txt || echo "(File not found)"
if ! grep -q "SMOKE_OK" /tmp/smoke/${{ github.event.repository.name }}/smoke-result.txt; then
echo "::error::SMOKE_OK not found in smoke-result.txt - check log above."
exit 1
fi
Sollte es hier zu einem Fehler kommen, würden nachfolgende Actions (wie das bereitstellen der ZIP oder gar der Upload ins SVN) gar nicht ausgeführt werden.
Das hier gezeigte Beispiel für einen Smoke-Test eines WordPress-Plugins ist natürlich wirklich minimalistisch. Dank der Möglichkeiten mit dem Playground, könnte man jedoch auch verschiedene Funktionen des Plugins nach dessen Aktivierung testen. Sobald eine davon nicht geht, wirft man einen Fehler.
Eine Grenze gibt es jedoch beim Test von echten grafischen Abläufen des Plugins. Dafür könnte aber die nachfolgende Methode eine Lösung bieten.
Playwright
Playwright ist ein von Microsoft im Jahr 2020 veröffentlichtes Tool mit dem man grafische Abläufe in Software testen kann. Es somuliert dazu einen Chromium-basierten Browser, führt darin Klick-Kommandos aus und prüft das Ergebnis. Dadurch kann man automatisiert Abläufe im eigenen Plugin testen und so sicherstellen, dass diese noch vor Veröffentlichung eines Updates problemlos funktionieren.
Voraussetzung für dieses Tool ist ebenfalls npm. Und auch dieses muss nicht ins Release kommen sondern kann wie folgt in der eigenen Entwicklungsumgebung genutzt werden.
Installiere zunächst das benötigte npm Paket:
npm install @playwright/test
Direkt danach wirst du bereits das Kommando zum Ausführen der Tests ausführen können:
npx playwright test
Es wird jedoch melden:
Error: No tests found
Die muss man erst noch definieren und hinterlegen. Lege dazu ein Verzeichnis tests an und speichere dort eine Datei namens hello_world.spec.ts mit folgendem Inhalt:
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Click the get started link.
await page.getByRole('link', { name: 'Get started' }).click();
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Wenn du jetzt das o.g. Kommando ausführst, kommt als Ergebnis:
Running 2 tests using 1 worker
✓ 1 tests/hello_world.spec.ts:3:5 › has title (1.4s)
✓ 2 tests/hello_world.spec.ts:10:5 › get started link (1.8s)
2 passed (5.5s)
Damit hast du den Ablauf einmal erlebt. Allerdings testet dieser Aufruf weder deine Website noch dein Plugin sondern die Website von Playwright selbst. Damit du dein Plugin testen kannst, benötigst du einen Server über den dieser für den simulierten Browser erreichbar ist. Und was eignet sich dafür besser als wieder der WordPress Playground :)
Dazu verwende ich den folgenden Aufbau. Zunächst eine Datei, die als Rahmen für jeden Test dient:
// @ts-ignore
import { test as base } from '@playwright/test';
// @ts-ignore
import { runCLI } from '@wp-playground/cli';
// @ts-ignore
import { execSync } from 'node:child_process';
// @ts-ignore
import { existsSync, statSync } from 'node:fs';
// @ts-ignore
import path from 'node:path';
// @ts-ignore
const pluginRoot = path.resolve(__dirname, '../..');
const autoloadPath = path.join(pluginRoot, 'vendor', 'autoload.php');
const lockPath = path.join(pluginRoot, 'composer.lock');
function needsComposerInstall(): boolean {
if (!existsSync(autoloadPath)) return true;
if (!existsSync(lockPath)) return false;
return statSync(lockPath).mtimeMs > statSync(autoloadPath).mtimeMs;
}
type CliServer = Awaited<ReturnType<typeof runCLI>>;
export const test = base.extend<{}, { cli: CliServer }>({
// worker-scoped: läuft einmal pro Test-Worker, nicht pro Test
cli: [
// @ts-ignore
async ({}, use) => {
const cli = await runCLI({
command: 'server',
mount: [
{
hostPath: './',
vfsPath: '/wordpress/wp-content/plugins/my-plugin-slug',
},
],
blueprint: {
preferredVersions: { php: '8.4', wp: 'latest' },
login: true,
steps: [
{
step: 'activatePlugin',
pluginPath: 'my-plugin-slug/my-plugin-slug.php',
},
],
},
});
await use(cli);
await cli?.server?.close();
},
{ scope: 'worker' },
],
});
// @ts-ignore
export { expect } from '@playwright/test';
Denk dran bei der Übernahme des Codes den Plugin-Slug an deinen anzupassen.
Jetzt brauchen wir natürlich noch einen Test der prüft, ob das eigene Plugin aktiviert ist. Das ist im Backend bei jedem Plugin anders. In meinem Beispiel hier gehe ich davon aus, dass es eine Einstellungsseite des Plugins gibt, dessen Seiten-Titel „Hallo Welt“ sein muss:
import { test, expect } from './fixtures';
// @ts-ignore
test('Plugin active', async ({ page, cli }) => {
// @ts-ignore
await page.goto(`${cli.serverUrl}/wp-admin/options-general.php?page=my-plugin-slug`);
await expect(page).toHaveTitle(/Hallo Welt/);
});
Die Angaben hier müssen individuell angepasst werden. Welche Möglichkeiten zu derartigen Tests es gibt, sind in der umfangreichen Dokumentation beschrieben: https://playwright.dev/docs/writing-tests
Auch Playwright kann man über GitHub Actions vor jedem Release ausführen lassen. Die Tests muss man jedoch wirklich sehr individuell erstellen, was einmalig einen größeren Aufwand bedeuten kann. Der große Vorteil ist jedoch, dass man sprichwörtlich das testen kann, was der Anwender des Plugins sieht und verwendet. Das eröffnet viele Möglichkeiten, die das spätere Supportaufkommen erheblich verringern können.
Ich setze Playwright z.B. bei meinem Plugin Externe Dateien für die Mediathek ein um grundlegende Aufgaben zu testen. Die Tests sind im GitHub Repository zu finden: https://github.com/threadi/external-files-in-media-library/tree/master/tests/Playwright
PHP Unit Tests
Auch diese möchte ich zumindest erwähnen, auch wenn sie nicht gegen eine fertige Release-Datei laufen. Dennoch können PHP Unit Tests dabei helfen mögliche Fehler von vornherein auszuschließen. Ich würde Entwicklern, gerade von größeren Plugins, unbedingt dazu raten diese zu verwenden.
Fazit
Es gibt, wie man sieht, viele Möglichkeiten um die Lauffähigkeit eines Plugins zu testen. Abstürze und Fehlermeldungen beim Anwender könnten der Vergangenheit angehören, wenn Entwickler diese Möglichkeiten mehr verwenden würden.