بنقرة واحدة
testing
// Guidance for writing and running tests in Simple History. Covers which framework to use, how to run existing tests, and how to create new ones (including the codegen recording workflow).
// Guidance for writing and running tests in Simple History. Covers which framework to use, how to run existing tests, and how to create new ones (including the codegen recording workflow).
| name | testing |
| description | Guidance for writing and running tests in Simple History. Covers which framework to use, how to run existing tests, and how to create new ones (including the codegen recording workflow). |
| allowed-tools | Read, Bash, Edit, Write |
| What you're testing | Framework | Why |
|---|---|---|
| Browser UI, admin pages, visual behaviour | Playwright | Fast, visible, modern — new default for UI tests |
| PHP logic, WordPress integration, database queries | Codeception / WPUnit | Full WordPress environment loaded in PHP |
| HTTP-level WordPress behaviour | Codeception / Functional | No browser needed |
Rule of thumb: If a human would test it by clicking around in a browser, use Playwright. If it's PHP logic, use Codeception.
# Playwright (UI tests) — runs on host machine against the dev WordPress
npm run test:playwright # headless, output in playwright-report/
npm run test:playwright:ui # interactive UI mode (recommended for writing/debugging)
# Codeception (PHP tests) — runs inside Docker
npm run test:wpunit # PHP unit + WordPress integration
npm run test:functional # HTTP-level tests
npm run test:acceptance # legacy browser tests (Selenium — prefer Playwright for new ones)
# Full PHP suite (Codeception only — does NOT include Playwright)
npm test
Note: npm test runs only the Codeception suite. To get full coverage, run both npm run test:playwright and npm test separately.
playwright.config.jstests/playwright/*.spec.jstests/playwright/.auth/admin.json (gitignored) — regenerated on every run by auth.setup.js before tests execute. If auth breaks (wrong credentials, WordPress unreachable), delete tests/playwright/.auth/admin.json and re-run.http://wordpress-stable-docker-mariadb.test:8282 (override with PLAYWRIGHT_BASE_URL env var)claude / claude (override with WP_ADMIN_USER / WP_ADMIN_PASSWORD)playwright-report/ after each run — open it to debug failures# Record a test by clicking through the browser — outputs ready-to-paste code
npx playwright codegen http://wordpress-stable-docker-mariadb.test:8282/wp-admin/
# Run a single spec file (faster iteration than the full suite)
npx playwright test tests/playwright/my-feature.spec.js
See https://playwright.dev/docs/getting-started-cli for the full CLI reference.
When the user asks for help creating a new Playwright test, walk them through this flow:
1. Record by clicking through the browser:
npx playwright codegen --load-storage=tests/playwright/.auth/admin.json http://wordpress-stable-docker-mariadb.test:8282/wp-admin/
--load-storage reuses the cached admin session, so codegen lands straight in wp-admin without making the user log in again. Without it, the recorded test will include the login form fill — noise you'd delete anyway.
In the inspector toolbar, use the Pick locator (cursor icon) and assertion tools (eye / ab / form) to add expect() calls — clicking through alone produces a click log, not a test.
2. Clean up the codegen output. The raw spec looks like this:
import { test, expect } from '@playwright/test';
test.use( { storageState: 'tests/playwright/.auth/admin.json' } ); // remove
test( 'test', async ( { page } ) => {
// rename
await page.goto( 'http://wordpress-stable-docker-mariadb.test:8282/...' ); // make relative
// ...
} );
Apply these conventions to match the rest of the suite:
require() (CommonJS), not import — matches log-page.spec.js, post-logging.spec.js.test.use({ storageState }) — the chromium project in playwright.config.js already sets it./wp-admin/...) — baseURL is configured.test.describe() and share setup in beforeEach()..SimpleHistoryLogitems.is-loaded before asserting on log rows — the list renders empty first, then hydrates from the REST API.3. Save to tests/playwright/<feature-name>.spec.js — testDir picks it up automatically.
4. Run just that file while iterating:
npx playwright test tests/playwright/<feature-name>.spec.js
Or use UI mode for fast edit-and-rerun: npm run test:playwright:ui.
5. Debug failures with npx playwright show-report — frame-by-frame trace replay.
Basic test (no test data needed) — import from @playwright/test:
const { test, expect } = require( '@playwright/test' );
test( 'my test', async ( { page } ) => {
await page.goto(
'/wp-admin/admin.php?page=simple_history_admin_menu_page'
);
// Always wait for the log list to finish loading before asserting.
await page.waitForSelector( '.SimpleHistoryLogitems.is-loaded' );
await expect(
page.locator( '.SimpleHistoryLogitem__text' ).first()
).toBeVisible();
} );
Test that needs to create WordPress data — import from ./fixtures to get requestUtils:
const { test, expect } = require( './fixtures' );
test.beforeEach( async ( { requestUtils } ) => {
post = await requestUtils.createPost( {
title: 'Test post',
status: 'publish',
} );
} );
test.afterEach( async ( { requestUtils } ) => {
// Delete by ID — never use deleteAllPosts() against the live dev site (see warning below).
await requestUtils.rest( {
path: `/wp/v2/posts/${ post.id }`,
method: 'DELETE',
params: { force: true },
} );
} );
test( 'logs post creation', async ( { page } ) => {
await page.goto(
'/wp-admin/admin.php?page=simple_history_admin_menu_page'
);
await page.waitForSelector( '.SimpleHistoryLogitems.is-loaded' );
await expect(
page
.locator( '.SimpleHistoryLogitem__text', { hasText: 'Test post' } )
.first()
).toBeVisible();
} );
requestUtils uses the saved admin session (cookie auth) — no application password needed.
beforeEach via requestUtils and clean up in afterEachrequestUtils methods (selection)requestUtils.createPost( payload ); // returns post object with .id
requestUtils.createPage( payload ); // returns page object with .id
requestUtils.createUser( payload ); // returns user object
requestUtils.rest( { path, method, params, data } ); // arbitrary REST API call
Warning: Do NOT use
deleteAllPosts(),deleteAllPages(), or similar bulk-delete methods. Tests run against the live dev WordPress — bulk deletes will wipe real content. Always delete by ID usingrequestUtils.rest().
codeception.dist.yml, tests/*.suite.ymltests/wpunit/, tests/functional/, tests/acceptance/tests/.env.testingdocker compose run --rm php-cliDon't migrate proactively. When you're already working on a feature that has a Codeception acceptance test (tests/acceptance/*Cest.php), migrate it to Playwright at that point. Leave the rest as-is.
Writes blog posts for simple-history.com matching the author's voice and style. Use when drafting posts, announcements, or marketing copy.
How to design and regenerate marketing screenshots for the wordpress.org plugin page (banner-1544x500.png, screenshot-1.png, etc). Covers the event mix that converts, the reproducible WordPress Playground pipeline that bakes it, and every non-obvious gotcha from previous shoots. Use when refreshing screenshot-1.png, banners, or any image showing the Simple History event log.
Query Google Analytics (GA4) for simple-history.com traffic, top pages, referrers, and the premium_* UTM campaigns that tag admin links. Use when the user asks about visits, traffic sources, which admin links lead to the site, upsell click-through, or campaign performance.
Adds changelog entries to readme.txt following keepachangelog format. Use when updating the Unreleased section or documenting changes for a release.
Surface and triage local issues that are safe and quick for an AI to implement and easy for a human to verify. Use when the user wants to "knock out issues", asks for "quick wins", "low-hanging fruit", "what's easy to do right now", or wants a batch of small tasks to work through in one session.
Guides implementation of structured action links on log events. Use when adding get_action_links() to a logger or migrating from get_log_row_details_output().