Tests E2E avec Playwright et WordPress Playground
Les tests de bout en bout vérifient que votre extension ou thème WordPress fonctionne correctement du point de vue de l'utilisateur — cliquer sur des boutons, remplir des formulaires et naviguer sur les pages dans un véritable navigateur. Ce guide montre comment combiner Playwright avec la CLI WordPress Playground pour écrire des tests E2E fiables sans Docker, bases de données ou configuration manuelle.
Ce guide suppose une familiarité avec le développement d’extensions ou de thèmes WordPress. Pour une introduction à l'utilisation de Playground dans votre flux de développement, consultez WordPress Playground pour développeurs de plugins. Pour les détails de configuration des Blueprints, voir Démarrage avec Blueprints.
Prérequis
- Node.js 20+ et supérieur
- Une extension/thème WordPress ou un site WordPress complet à tester
- Recommandé : activez la règle ESLint
@typescript-eslint/no-floating-promisespour détecter lesawaitmanquants dans les appels asynchrones Playwright
Configuration du projet
Installer les dépendances
Depuis le répertoire racine de votre extension ou thème :
npm init -y
npm install --save-dev @playwright/test @wp-playground/cli
npx playwright install chromium
Cela installe Playwright comme exécuteur de tests, la CLI Playground pour créer des instances WordPress et le navigateur Chromium pour l'exécution des tests.
Configurer Playwright
Créez un fichier playwright.config.ts à la racine de votre projet :
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./tests/e2e",
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: "html",
timeout: 120_000,
expect: {
timeout: 30_000,
},
use: {
screenshot: "only-on-failure",
trace: "on-first-retry",
},
});
WordPress Playground a besoin de plus de temps pour démarrer qu'une application web typique. Le délai d'attente des tests de 120 secondes et le délai d'assertion de 30 secondes tiennent compte du temps de démarrage de WordPress et du chargement des pages. La valeur workers: 1 évite les conflits de ports lorsque plusieurs tests partagent un serveur Playground.
Par défaut, Playground utilisera le port 9400. Si vous souhaitez sélectionner un port différent, passez port: [NOUVEAU_NUMÉRO_DE_PORT] dans les options de runCLI pour sélectionner un port différent :
const cli = await runCLI({ command: "server", port: 9500, blueprint });
Puis ajoutez baseURL: "http://localhost:9500" à la section use ci-dessus. Notez que testMatch utilise par défaut **/*.spec.ts — personnalisez-le si vos fichiers de test utilisent un autre schéma de nommage.
Le projet WordPress Playground utilise des délais encore plus longs (300s pour les tests, 60s pour les assertions) pour ses propres tests. Commencez avec les valeurs ci-dessus et augmentez-les si votre environnement CI est plus lent.
Premier fichier de test
Créez tests/e2e/plugin.spec.ts :
import { test, expect } from "@playwright/test";
import { runCLI } from "@wp-playground/cli";
let cli: Awaited<ReturnType<typeof runCLI>>;
test.beforeAll(async () => {
cli = await runCLI({
command: "server",
blueprint: {
preferredVersions: { php: "8.3", wp: "latest" },
login: true,
},
});
});
test.afterAll(async () => {
await cli?.server?.close();
});
test("WordPress dashboard loads", async ({ page }) => {
await page.goto(`${cli.serverUrl}/wp-admin/`);
// WordPress core admin elements lack ARIA roles — CSS selectors are acceptable here
await expect(page.locator("#wpbody-content")).toBeVisible();
await expect(page).toHaveTitle(/Dashboard/);
});
Exécutez le test :
npx playwright test
Choisir les localisateurs
Playwright offre plusieurs façons de trouver les éléments sur la page. Préférez les localisateurs qui reflètent la façon dont les utilisateurs voient la page, en utilisant les sélecteurs CSS uniquement en dernier recours.
Priorité des localisateurs (du plus au moins recommandé) :
page.getByRole()— boutons, titres, liens, contrôles de formulairepage.getByLabel()— champs de formulaire avec étiquettes associéespage.getByText()— contenu texte visiblepage.getByTestId()— éléments avec attributsdata-testidque vous ajoutez à votre pluginpage.locator()— sélecteurs CSS ou XPath en dernier recours
Conseils spécifiques à WordPress
Dans l'admin WordPress, certains éléments principaux (barre d'administration, meta boxes) utilisent des ID et classes CSS plutôt que des rôles ARIA. Cependant, de nombreux éléments fonctionnent bien avec des localisateurs sémantiques. Cela signifie :
- Utilisez des localisateurs sémantiques pour les boutons, titres, liens, champs de formulaire et éléments du menu admin — WordPress rend des éléments standards
<button>,<input>,<a>et<h1>quegetByRoleetgetByLabelpeuvent trouver. - Utilisez
data-testidpour le markup de votre propre plugin — vous contrôlez le HTML, ajoutez donc des attributs testables. - Utilisez des sélecteurs CSS pour les éléments de mise en page principaux de WordPress comme
#wpadminbarou#wpbody-content— ils n'ont pas d'alternatives ARIA.
Même élément, trois approches
// ✅ Préféré : localisateur sémantique (fonctionne car WP rend un vrai <button>)
await page.getByRole("button", { name: "Enregistrer les modifications" }).click();
// ⚠️ Acceptable : ID de test que vous avez ajouté au markup de votre plugin
await page.getByTestId("save-settings").click();
// ❌ À éviter : sélecteur CSS fragile lié au markup WordPress
await page.locator("#submit").click();
Exécutez npx playwright codegen localhost:9400/wp-admin/ pour ouvrir un navigateur et enregistrer les interactions. Playwright génère le code des localisateurs pendant que vous cliquez, vous aidant à découvrir quels localisateurs sémantiques fonctionnent pour chaque élément.
Auto-attente et assertions orientée web
Les localisateurs Playwright attendent automatiquement que les éléments apparaissent, deviennent visibles et actionnables. Dans la plupart des cas, vous n'avez pas besoin d'appels manuels à waitForSelector.
Assertions orientée web
Les assertions orientée web réessaient automatiquement jusqu'à ce que la condition soit remplie ou que le délai expire. Préférez-les toujours aux vérifications manuelles :
// ✅ Assertion orientée web (réessaie jusqu'à visible ou timeout)
await expect(page.getByText("Paramètres enregistrés")).toBeVisible();
// ❌ Vérification manuelle (sans retry — instable si l'élément apparaît en retard)
expect(await page.getByText("Paramètres enregistrés").isVisible()).toBe(true);
Assertions souples
Utilisez expect.soft() pour vérifier plusieurs éléments sur une page sans s'arrêter au premier échec. Tous les échecs apparaissent dans le rapport de test :
await expect.soft(page.getByLabel("API Key")).toHaveValue("test-key-123");
await expect.soft(page.getByText("Settings saved")).toBeVisible();
await expect.soft(page.getByRole("heading", { level: 1 })).toContainText("Settings");
Écrire des tests
Démarrer un serveur Playground
La fonction runCLI démarre un serveur Playground local et retourne un objet avec serverUrl (la chaîne URL) et server (l'instance du serveur HTTP). Passez un Blueprint pour configurer l'instance WordPress :
const cli = await runCLI({
command: "server",
blueprint: {
preferredVersions: { php: "8.3", wp: "latest" },
login: true,
steps: [
{
step: "installPlugin",
pluginData: {
resource: "wordpress.org/plugins",
slug: "woocommerce",
},
},
],
},
});
Cycle de vie du serveur : partagé vs. par test
Serveur partagé (beforeAll/afterAll) — une instance Playground sert tous les tests d'un bloc describe. Plus rapide, mais les tests peuvent s'influencer mutuellement :
test.describe("Plugin settings", () => {
test.beforeAll(async () => {
cli = await runCLI({ command: "server", blueprint });
});
test.afterAll(async () => {
await cli?.server?.close();
});
// Tests share the same WordPress instance
});
Serveur par test (beforeEach/afterEach) — chaque test obtient une instance fraîche. Plus lent, mais entièrement isolé :
test.beforeEach(async () => {
cli = await runCLI({ command: "server", blueprint });
});
test.afterEach(async () => {
await cli?.server?.close();
});
Utilisez des serveurs partagés lorsque les tests ne font que lire l'état (vérification du rendu des pages). Utilisez des serveurs par test lorsque les tests modifient l'état (création d'articles, modification des paramètres).
Utiliser les Blueprints comme fixtures de test
Les Blueprints définissent l'état WordPress nécessaire à chaque scénario de test. Voici les modèles courants :
Installer un plugin depuis wordpress.org
const blueprint = {
preferredVersions: { php: "8.3", wp: "latest" },
login: true,
steps: [
{
step: "installPlugin",
pluginData: {
resource: "wordpress.org/plugins",
slug: "contact-form-7",
},
},
],
};
Installer un plugin local
Montez le répertoire de votre plugin local dans l'instance Playground :
const cli = await runCLI({
command: "server",
mount: {
"./": "/wordpress/wp-content/plugins/my-plugin",
},
blueprint: {
preferredVersions: { php: "8.3", wp: "latest" },
login: true,
steps: [
{
step: "activatePlugin",
pluginPath: "my-plugin/my-plugin.php",
},
],
},
});
Cela mappe votre répertoire actuel au chemin du plugin dans WordPress, puis active le plugin. Les modifications de vos fichiers locaux se reflètent immédiatement. L'utilisateur peut configurer la propriété autoMount pour identifier les plugins et thèmes, mais la propriété mount offrira plus de contrôle à l'utilisateur pour définir différents dossiers dans le projet.
Définir les options et créer du contenu
const blueprint = {
login: true,
steps: [
{
step: "setSiteOptions",
options: {
blogname: "Test Site",
permalink_structure: "/%postname%/",
},
},
{
step: "runPHP",
code: `<?php
require '/wordpress/wp-load.php';
wp_insert_post([
'post_title' => 'Test Post',
'post_content' => '<!-- wp:paragraph --><p>Hello World</p><!-- /wp:paragraph -->',
'post_status' => 'publish',
]);
`,
},
],
};
Utilisez la Playground Step Library ou Pootle Playground pour prototyper votre configuration Blueprint visuellement avant de l'ajouter à votre code de test.
Tester les pages d'administration WordPress
Naviguez vers les pages d'administration et interagissez avec l'interface WordPress :
test("plugin settings page saves options", async ({ page }) => {
await page.goto(`${cli.serverUrl}/wp-admin/options-general.php?page=my-plugin`);
await page.getByLabel("API Key").fill("test-key-123");
await page.getByRole("button", { name: "Enregistrer les modifications" }).click();
await expect(page.getByText("Paramètres enregistrés")).toBeVisible();
await expect(page.getByLabel("API Key")).toHaveValue("test-key-123");
});
Gérer les éléments courants de l'interface d'administration
// Fermer les notifications admin WordPress (WP ajoute aria-label aux boutons de fermeture)
await page.getByRole("button", { name: "Dismiss this notice" }).first().click();
// Attendre le chargement de la barre admin — pas de rôle ARIA disponible, utiliser locator
await page.locator("#wpadminbar").waitFor();
// Naviguer via le menu admin
await page.getByRole("link", { name: "My Plugin" }).first().click();
Tester le front-end
test("plugin shortcode renders on front end", async ({ page }) => {
// Navigate to a page with the shortcode
await page.goto(`${cli.serverUrl}/?p=2`);
// Recommend: add data-testid="my-plugin-widget" to your plugin markup
await expect(page.getByTestId("my-plugin-widget")).toBeVisible();
await expect(page.getByTestId("my-plugin-widget")).toContainText(
"Expected content"
);
// Or use CSS if you don't control the markup:
// await expect(page.locator(".my-plugin-widget")).toBeVisible();
});
test("theme displays post correctly", async ({ page }) => {
await page.goto(`${cli.serverUrl}/test-post/`);
await expect(page.getByRole("heading", { level: 1 })).toContainText("Test Post");
await expect(page.getByText("Hello World", { exact: true })).toBeVisible();
});
Modèle Page Object Model
Le Page Object Model (POM) encapsule les interactions avec les pages dans des classes réutilisables. Cela réduit la duplication et facilite la maintenance des tests lorsque l'interface de votre plugin change.
// tests/e2e/pages/plugin-settings.ts
import { type Page, type Locator, expect } from "@playwright/test";
export class PluginSettingsPage {
readonly page: Page;
readonly apiKeyInput: Locator;
readonly saveButton: Locator;
readonly successNotice: Locator;
constructor(page: Page) {
this.page = page;
this.apiKeyInput = page.getByLabel("API Key");
this.saveButton = page.getByRole("button", { name: "Enregistrer les modifications" });
this.successNotice = page.getByText("Paramètres enregistrés");
}
async goto(baseUrl: string) {
await this.page.goto(
`${baseUrl}/wp-admin/options-general.php?page=my-plugin`
);
}
async setApiKey(key: string) {
await this.apiKeyInput.fill(key);
await this.saveButton.click();
}
async expectSaved() {
await expect(this.successNotice).toBeVisible();
}
}
Utilisez le POM dans les tests :
import { PluginSettingsPage } from "./pages/plugin-settings";
test("save plugin settings", async ({ page }) => {
const settings = new PluginSettingsPage(page);
await settings.goto(cli.serverUrl);
await settings.setApiKey("test-key-123");
await settings.expectSaved();
});
Le projet Playground utilise ce modèle avec une classe WebsitePage qui fournit des méthodes comme goto(), wordpress() et getSiteTitle() — encapsulant la navigation et les interactions spécifiques à WordPress.
Tester sur différentes versions de PHP et WordPress
Les tests paramétrés couvrent plusieurs combinaisons de versions sans dupliquer le code de test :
const versionMatrix = [
{ php: "8.1", wp: "6.5" },
{ php: "8.2", wp: "6.7" },
{ php: "8.3", wp: "latest" },
];
for (const { php, wp } of versionMatrix) {
test.describe(`PHP ${php} + WP ${wp}`, () => {
let versionCli: Awaited<ReturnType<typeof runCLI>>;
test.beforeAll(async () => {
versionCli = await runCLI({
command: "server",
blueprint: {
preferredVersions: { php, wp },
login: true,
steps: [
{
step: "activatePlugin",
pluginPath: "my-plugin/my-plugin.php",
},
],
},
});
});
test("admin page loads without errors", async ({ page }) => {
await page.goto(
`${versionCli.serverUrl}/wp-admin/options-general.php?page=my-plugin`
);
// WordPress core elements use CSS selectors — no ARIA roles available
await expect(page.locator(".error")).not.toBeVisible();
await expect(page.locator("#wpbody-content")).toBeVisible();
});
test("front-end output renders", async ({ page }) => {
await page.goto(versionCli.serverUrl);
await expect(page.getByTestId("my-plugin-widget")).toBeVisible();
});
test.afterAll(async () => {
await versionCli?.server?.close();
});
});
}
La propriété preferredVersions dans le Blueprint contrôle les versions PHP et WordPress utilisées par l'instance Playground. Plages prises en charge : PHP 7.4–8.5, WordPress 6.3–6.8+, ainsi que latest, nightly et beta. Pour des valeurs de version PHP typées, utilisez le type SupportedPHPVersion de @php-wasm/universal.
Exécuter les tests en CI/CD
GitHub Actions
Créez .github/workflows/e2e-tests.yml :
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Cache Playwright browsers
uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install chromium --with-deps
- name: Run E2E tests
run: npx playwright test
- name: Upload test report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Ce workflow installe les dépendances, télécharge Chromium, exécute les tests et télécharge le rapport HTML comme artefact. L'option --with-deps installe les bibliothèques système nécessaires à Chromium sur Ubuntu.
Répartissez les tests sur plusieurs jobs CI avec la fragmentation intégrée de Playwright :
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
Créez trois jobs parallèles dans la matrice de votre workflow, chacun exécutant un fragment différent. Cela réduit proportionnellement le temps total de CI.
Pour les tests manuels des PR en complément des tests E2E automatisés, voir Ajouter des boutons d'aperçu PR avec GitHub Actions.
Dépannage
Erreurs de timeout — Augmentez timeout dans playwright.config.ts. Le temps de démarrage de WordPress varie selon l'environnement. Les exécuteurs CI ont souvent besoin de 120–180 secondes.
Conflits de ports — Laissez Playground attribuer les ports automatiquement. Ne codez pas en dur les numéros de port dans votre configuration. La propriété serverUrl retourne la bonne URL.
Navigateur non trouvé — Exécutez npx playwright install chromium pour télécharger le binaire du navigateur. Sur CI, ajoutez --with-deps pour les bibliothèques système.
WordPress ne charge pas — Vérifiez la syntaxe de votre Blueprint par rapport au schéma Blueprint. Les étapes invalides échouent silencieusement dans certains cas.
Les tests passent en local mais échouent en CI — Les exécuteurs CI ont moins de mémoire et de CPU. Augmentez les délais d'expiration, réduisez les processus parallèles et assurez-vous que workers: 1 est dans la config.
Déboguer les tests
Lorsqu'un test échoue, Playwright fournit plusieurs outils pour investiguer :
Playwright Inspector — parcourez les tests de manière interactive avec un débogueur intégré :
npx playwright test --debug
Visualiseur de traces — examinez une chronologie des actions, des snapshots DOM et des requêtes réseau d'un test échoué. La configuration trace: "on-first-retry" ci-dessus capture automatiquement les traces :
npx playwright show-trace test-results/plugin-spec-ts/trace.zip
Mode UI — exécutez les tests dans une interface visuelle où vous pouvez regarder, filtrer et relancer les tests :
npx playwright test --ui
Capture d'écran en cas d'échec — la configuration screenshot: "only-on-failure" dans le configuration sauvegarde une capture d'écran à chaque échec de test. Trouvez les captures dans le répertoire test-results/.
Combinez --debug avec un fichier de test spécifique pour concentrer votre investigation : npx playwright test tests/e2e/settings.spec.ts --debug
Prochaines étapes
- Documentation de la CLI WordPress Playground — référence complète de la CLI
- Documentation Playwright — guide d'écriture de tests et référence API
- Référence Blueprints — toutes les étapes Blueprint disponibles
- Ajouter des boutons d'aperçu PR — combinez les tests automatisés avec les aperçus PR manuels