| 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 |
Testing in Simple History
Which framework to use
| 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.
Running tests
npm run test:playwright
npm run test:playwright:ui
npm run test:wpunit
npm run test:functional
npm run test:acceptance
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 setup
- Config:
playwright.config.js
- Tests:
tests/playwright/*.spec.js
- Auth: login cached in
tests/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.
- Target: dev WordPress at
http://wordpress-stable-docker-mariadb.test:8282 (override with PLAYWRIGHT_BASE_URL env var)
- Admin credentials:
claude / claude (override with WP_ADMIN_USER / WP_ADMIN_PASSWORD)
- HTML report: written to
playwright-report/ after each run — open it to debug failures
CLI shortcuts
npx playwright codegen http://wordpress-stable-docker-mariadb.test:8282/wp-admin/
npx playwright test tests/playwright/my-feature.spec.js
See https://playwright.dev/docs/getting-started-cli for the full CLI reference.
Creating a new test (codegen workflow)
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' } );
test( 'test', async ( { page } ) => {
await page.goto( 'http://wordpress-stable-docker-mariadb.test:8282/...' );
} );
Apply these conventions to match the rest of the suite:
- Use
require() (CommonJS), not import — matches log-page.spec.js, post-logging.spec.js.
- Drop
test.use({ storageState }) — the chromium project in playwright.config.js already sets it.
- Use relative URLs (
/wp-admin/...) — baseURL is configured.
- Give the test a descriptive name — it shows up in reports.
- Group related tests with
test.describe() and share setup in beforeEach().
- Always wait for
.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.
Writing a new Playwright test
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'
);
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 } ) => {
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.
Test data and state
- Tests run against the live dev WordPress — your existing log events and content stay untouched
- Write assertions that are true regardless of existing data ("at least one event", not "exactly 5 events")
- When a test needs specific data, create it in
beforeEach via requestUtils and clean up in afterEach
- Cleanup deletions are also logged by Simple History — that's expected and correct, just accept it
Available requestUtils methods (selection)
requestUtils.createPost( payload );
requestUtils.createPage( payload );
requestUtils.createUser( payload );
requestUtils.rest( { path, method, params, data } );
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 using requestUtils.rest().
Codeception PHP tests
- Config:
codeception.dist.yml, tests/*.suite.yml
- Tests:
tests/wpunit/, tests/functional/, tests/acceptance/
- Environment:
tests/.env.testing
- All PHP tests run inside Docker via
docker compose run --rm php-cli
Migrating old acceptance tests to Playwright
Don'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.