Обновление ядра WordPress, установка нового плагина или правка в functions.php часто приводят к скрытым поломкам: перестают работать шорткоды, отваливается форма обратной связи или ломается верстка на мобильных устройствах. Ручная проверка всех сценариев перед каждым деплоем занимает часы и не гарантирует отсутствия ошибок.
Решение — двухуровневое автоматическое тестирование. PHPUnit проверяет серверную логику (PHP-функции, хуки, REST API), а Playwright эмулирует действия пользователя в браузере (E2E-тесты), проверяя, что фронтенд корректно взаимодействует с бэкендом. В связке с CI/CD пайплайном это создает надежный барьер на пути багов в продакшн.
Уровень 1: Unit и Integration тесты через PHPUnit
PHPUnit позволяет тестировать изолированные куски кода. В экосистеме WordPress для этого используется официальная утилита @wordpress/env, которая разворачивает локальный Docker-контейнер с чистой базой данных для каждого прогона тестов.
Шаг 1: Установка окружения
В корне темы или плагина инициализируем Node.js и устанавливаем wp-env:
npm init -y
npm install @wordpress/env --save-dev
npm install phpunit/phpunit --save-devСоздаем файл .wp-env.json для конфигурации тестового окружения:
{
"core": "WordPress/WordPress#master",
"phpVersion": "8.2",
"plugins": [ "." ],
"mappings": {
"wp-content/themes/customcode": "./"
}
}Шаг 2: Настройка PHPUnit
Создаем конфигурационный файл phpunit.xml:
<?xml version="1.0"?>
<phpunit
bootstrap="tests/bootstrap.php"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite name="Unit">
<directory prefix="test-" suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>Файл tests/bootstrap.php загружает ядро WordPress в память для тестов:
<?php
// Загрузка тестового окружения WP
$_tests_dir = getenv( 'WP_TESTS_DIR' ) ?: '/tmp/wordpress-tests-lib';
require_once $_tests_dir . '/includes/functions.php';
// Активируем нашу тему/плагин до загрузки WP
tests_add_filter( 'muplugins_loaded', function() {
require dirname( __DIR__ ) . '/functions.php';
} );
require $_tests_dir . '/includes/bootstrap.php';Шаг 3: Написание первого теста
Допустим, в теме есть функция расчета времени чтения статьи (которая использовалась в Headless-архитектуре). Создаем файл tests/test-reading-time.php:
<?php
class Test_Reading_Time extends WP_UnitTestCase {
public function test_empty_content() {
$time = customcode_calculate_reading_time( '' );
$this->assertEquals( 0, $time );
}
public function test_standard_reading_speed() {
// Генерируем текст из 400 слов (при скорости 200 слов/мин = 2 минуты)
$text = str_repeat( 'word ', 400 );
$time = customcode_calculate_reading_time( $text );
$this->assertEquals( 2, $time );
}
public function test_html_tags_stripped() {
// HTML-теги не должны учитываться в подсчете слов
$text = '<h1>Заголовок</h1><p>' . str_repeat( 'word ', 200 ) . '</p>';
$time = customcode_calculate_reading_time( $text );
$this->assertEquals( 1, $time );
}
}Запуск тестов:
# Запускаем тестовый контейнер
npx wp-env start
# Выполняем PHPUnit внутри контейнера
npx wp-env run phpunit "vendor/bin/phpunit"Уровень 2: E2E тесты интерфейса через Playwright
PHPUnit не проверит, что JavaScript-меню открывается по клику, или что форма контактной формы реально отправляет письма (без моков). Для этого используется Playwright от Microsoft — современный инструмент для браузерной автоматизации.
Шаг 1: Инициализация Playwright
npm init playwright@latest
# Выбираем TypeScript, папку tests/e2e, включаем GitHub Actions workflowВ файле playwright.config.ts указываем базовый URL нашего локального wp-env:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
retries: 1,
use: {
baseURL: 'http://localhost:8888', // Стандартный порт wp-env
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 13'] } },
],
webServer: {
command: 'npm run wp-env start',
url: 'http://localhost:8888',
reuseExistingServer: !process.env.CI,
},
});Шаг 2: Тест авторизации и публикации поста
Создаем файл tests/e2e/admin-flow.spec.ts. Этот сценарий проверяет, что администратор может войти в панель и создать черновик:
import { test, expect } from '@playwright/test';
test.describe('WordPress Admin Flow', () => {
test.beforeEach(async ({ page }) => {
// Авторизация перед каждым тестом
await page.goto('/wp-login.php');
await page.fill('#user_login', 'admin');
await page.fill('#user_pass', 'password');
await page.click('#wp-submit');
await expect(page).toHaveURL(/wp-admin/);
});
test('should create a new draft post', async ({ page }) => {
await page.goto('/wp-admin/post-new.php');
// Заполняем заголовок (учитываем блочный редактор Gutenberg)
const titleField = page.locator('h1[contenteditable="true"], textarea[aria-label="Add title"]').first();
await titleField.fill('Тестовая статья Playwright');
// Сохраняем как черновик
await page.click('button:has-text("Save draft")');
// Проверяем, что появилось уведомление об успешном сохранении
await expect(page.locator('.components-snackbar')).toBeVisible();
});
test('frontend should render critical CSS correctly', async ({ page }) => {
await page.goto('/');
// Проверяем, что шапка загрузилась и имеет правильный цвет фона (VS Code style)
const header = page.locator('.title-bar');
await expect(header).toBeVisible();
await expect(header).toHaveCSS('background-color', 'rgb(45, 45, 48)'); // #2d2d30
});
});Уровень 3: Интеграция в CI/CD (GitHub Actions)
Тесты должны запускаться автоматически при каждом Pull Request, блокируя мерж кода, который ломает функционал. Интеграция с GitHub Actions описана в руководстве по настройке CI/CD, здесь приводится специфичный workflow для тестов.
Создаем файл .github/workflows/tests.yml:
name: WordPress Tests
on:
pull_request:
branches: [ main, develop ]
jobs:
phpunit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node & wp-env
run: |
npm ci
npm run wp-env start
- name: Run PHPUnit
run: npm run wp-env run phpunit "vendor/bin/phpunit --testdox"
playwright:
runs-on: ubuntu-latest
needs: phpunit
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Start WP Environment
run: npm run wp-env start
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 7Тестирование REST API и Headless-сценариев
Для проектов, использующих WordPress как headless CMS, критично тестировать JSON-ответы. Playwright отлично справляется с проверкой API:
test('REST API should return posts with custom fields', async ({ request }) => {
const response = await request.get('/wp-json/wp/v2/posts?per_page=1');
expect(response.ok()).toBeTruthy();
const posts = await response.json();
expect(posts.length).toBeGreaterThan(0);
// Проверяем наличие кастомного поля, зарегистрированного через register_rest_field
expect(posts[0]).toHaveProperty('custom_fields');
});Чек-лист настройки тестирования
- ✅ Установлен
@wordpress/envдля изолированного тестового окружения - ✅ Настроен
phpunit.xmlи написаны базовые unit-тесты для кастомных функций - ✅ Инициализирован Playwright с конфигурацией под локальный WP
- ✅ Написаны E2E-сценарии для критичных путей (логин, публикация, отправка формы)
- ✅ Добавлены проверки CSS-свойств для предотвращения регрессий верстки (CLS)
- ✅ Настроен GitHub Actions workflow для автозапуска тестов на каждый PR
- ✅ Тесты REST API покрывают кастомные endpoints
Решение частых проблем
Тесты «протекают» (состояние БД сохраняется между прогонами)
PHPUnit в WordPress по умолчанию оборачивает каждый тест в транзакцию MySQL и откатывает её в конце. Если используются нестандартные движки таблиц (например, MyISAM) или внешние API-вызовы, транзакции не работают. Решение: использовать setUp() и tearDown() для ручного удаления созданных постов и опций через wp_delete_post() и delete_option().
Playwright не может авторизоваться из-за 2FA или Captcha
Плагины безопасности (Wordfence, iThemes) часто блокируют автоматические входы. В тестовом окружении wp-env эти плагины не должны быть активированы. Если тестируется прод-копия, используйте механизм storageState в Playwright: один раз авторизуйтесь вручную, сохраните куки в JSON и подгружайте их в тестах, обходя страницу логина.
Тесты падают из-за медленного рендеринга (Flaky tests)
Если Playwright пытается кликнуть по кнопке до того, как она отрисовалась, тест падает. Избегайте жестких задержек (page.waitForTimeout). Вместо этого используйте умные ожидания: await expect(page.locator('.button')).toBeEnabled(); или await page.waitForLoadState('networkidle');.
Как тестировать отправку email (SMTP)?
Не отправляйте реальные письма в тестах. Перехватывайте вызовы wp_mail() через мокирование или используйте плагин WP Mail Logging в тестовом окружении, а в Playwright проверяйте наличие записи в логе через прямой SQL-запрос к тестовой БД.
Итог
Автоматическое тестирование WordPress перестает быть уделом энтерпрайз-проектов. Связка @wordpress/env, PHPUnit и Playwright разворачивается за 15 минут и полностью закрывает риски регрессий. Unit-тесты гарантируют, что PHP-логика работает корректно, а E2E-тесты подтверждают, что пользовательский интерфейс не сломан после очередного обновления плагинов. Интеграция этих инструментов в CI/CD пайплайн превращает деплой из стресса в рутинную, предсказуемую процедуру.