15 — Testing and QA#
15.1 Strategy#
| Level | Tool | What it covers | Share |
|---|---|---|---|
| Feature | Pest (Laravel) | HTTP + Livewire + database behaviour: the bulk of the suite | ~70 % |
| Unit | Pest | pure logic with branches worth isolating: PartResolver, SchemaValidator, Sanitizer, RedirectMatcher, TokenCompiler, path building |
~20 % |
| Browser | Playwright | the handful of flows that only exist in a real browser: drag and drop, autosave, mega-menu hover, drawer, lightbox | ~10 % |
Principles:
- Reproduce first. A bug gets a failing test before a fix. Then re-break the fix and confirm the test catches it. A test that has never been seen to fail proves nothing.
- One report is a bug class. When a bug is found in one place, grep for the pattern and fix every sibling in the same commit, with a test covering the class (e.g. every date render, not just the one that was reported).
- Both sides of a duplicated rule. Server validation and client hints, renderer and editor,
pagesandtemplate_parts— a rule that exists twice is changed twice, in one commit. - Test what the user does.
Livewire::test()for component logic is fine, but a flow that a user performs (log in, create, publish, view) is tested through real HTTP requests and real routes. - Tests never depend on each other or on execution order.
RefreshDatabaseeverywhere. - Factories produce valid content trees via a
TreeFactoryhelper, so a test never hand-writes 40 lines of JSON.
15.2 Feature-test map#
| Area | Must-have tests |
|---|---|
| Install | kodepress:install on an empty database; idempotent re-run; non-interactive flags; seeded roles, theme, templates, default header/footer |
| Auth | login, logout, reset, named throttle, 2FA enrol and challenge, 4-digit email code, changed admin path |
| Authorization | the full role sweep over every admin route (11) |
| Blocks | registry discovery; a block added at runtime appears; invalid schema skipped and logged; context filtering; tree validation; sanitisation; upcaster renders a v1 document |
| Editor | create from template; edit a block; autosave writes draft only; conflict modal path; undo/redo state; publish validation failures named; device preview token |
| Publish | version row created; current_version_id set; cache cleared for that page only; public URL shows the new content; unpublish removes it |
| Versions | list, restore into draft, publish creates a new version, retention prune keeps labelled versions |
| Pages | list filters, search (Bengali), duplicate, trash, restore, permanent delete, hierarchy path rewrite with 301s for descendants, homepage switch |
| Blog | post uses the same pipeline; scheduling publishes on cron; archives (index, nested category, tag, author); RSS validity; post_list filters, pagination and noindex past page 1 |
| SEO | meta output and fallbacks; hreflang; JSON-LD on a post; sitemap contents and noindex exclusion; sitemap index past 5,000; robots in maintenance mode |
| Redirects | auto 301 on slug change; manual rules; regex rules; loop refusal; hop limit; query preservation; hit counting throttled |
| Menus | the list in 07 |
| Header/footer | the list in 08 |
| Theme | the list in 09 |
| Media & forms | the list in 10 |
| i18n | the list in 12 |
| Cache | the list in 13 |
| Global blocks | editing one updates every page that uses it; usage count; warning before publish |
| Approval | submit, approve, request changes, Writer blocked from publishing while pending |
| Audit | a row per logged action, scalar diffs only, read-only screen, prune |
| Backup | chunked run resumes; restore refuses a newer manifest; archive not web-reachable |
| Doctor | fails on APP_DEBUG=true, a missing cron tick, a world-readable .env |
15.3 Browser tests#
Playwright, against a seeded local install. Workers = 1 (shared database).
| Spec | Flow |
|---|---|
smoke.spec |
homepage, inner page, post, 404, header/footer present, no console errors |
onboarding.spec |
fresh install -> tour -> pick template -> edit text -> publish -> view site, asserting 10 clicks or fewer |
editor-dnd.spec |
drag a block between columns, reorder sections, duplicate, delete with the modal |
autosave.spec |
type, wait, reload, the draft survived; the unload warning appears while dirty |
megamenu.spec |
hover opens the panel, the diagonal path does not close it, keyboard opens and Escape closes, mobile width shows the accordion |
header-sticky.spec |
sticky after scroll, shrink, hide-on-scroll-down |
media.spec |
drag-drop upload, alt text prompt, insert into a block, replace |
form.spec |
submit the contact form, see the success message, the submission appears in the admin |
roles.spec |
Writer cannot see Publish; Designer cannot see Blog |
inline-edit.spec |
logged-in public page shows Edit, in-place text edit, publish banner |
Every manual test round ends by turning what was tested into one of these specs. A bug found by clicking is fixed and gets a spec in the same commit — otherwise the next regression is found by clicking again.
15.4 Final acceptance checklist#
The gate for calling KodePress done. Each row is a command or a scripted flow, not an opinion.
| Check | Passes when |
|---|---|
| New user publishes in 10 minutes | from a template, no training, no documentation; onboarding.spec asserts ≤ 10 clicks |
| Add a new block type | one new folder; git diff touches nothing under app/Livewire or resources/views/admin |
| 3-level menu + mega menu | panel on desktop, accordion on mobile, keyboard-operable |
| Header and footer | preset switch, condition-based assignment, draft, preview on any page, version restore |
| Public page load | cached page under 1 second TTFB on the target host, zero queries |
| Shared cPanel deploy | SSH only: git pull, composer install, migrate, kodepress:install; doctor green |
| Roles and permissions | Writer cannot publish; Designer touches design only; the sweep test is green |
| Bengali | every screen in bn, no missing keys, fonts render, dates in Bengali months |
| All tests | php artisan test green; npx playwright test green |
| Security | kodepress:doctor green in production; no .env, .git or storage reachable over HTTP |
15.5 Running the suites#
php artisan test # everything
php artisan test --filter=PartResolver # one area
php artisan test --parallel # local only; the browser suite stays serial
npx playwright test # all browser specs
npx playwright test megamenu --headed # watch one
php artisan kodepress:doctor # environment, local or production
CI (GitHub Actions) on every push and PR: PHP 8.3 + MySQL 8 service, composer install,
php artisan test, npm ci && npm run build, then Playwright against the built app. A red suite
blocks merge. Asset build in CI is a check that the committed public/build is current — CI fails if
building produces a diff, which is how we keep a committed build honest.
15.6 Test data#
TreeFactory::page(),::section(),::block('heading', [...])compose valid trees.PageFactorystates:draft(),published(),scheduled(),post(),withTree($tree).- Bengali strings appear in fixtures deliberately — titles, slugs, form inputs, search queries — so encoding bugs surface in CI rather than on a client's site.
- Fixture images: three small real images (JPEG, PNG, WebP) plus one
.phpdisguised as.jpgand one SVG with a script, for the upload-rejection tests. - No test reaches the network. Mail uses the array driver; captcha and any provider calls are faked.
15.7 What is deliberately not tested#
| Not tested | Why |
|---|---|
| Third-party library internals | trust the dependency; test our usage |
| Exact rendered CSS values | brittle; we test that a token change produces a new hash and a new linked file |
| Visual appearance pixel-by-pixel | no screenshot diffing in core; the budget tests and the browser smoke cover regressions that matter |
| Every block's markup in detail | one render test per block asserting its key output, not its whole HTML |
| Shared-host-specific quirks | cannot be reproduced in CI; covered by doctor and the post-deploy checklist |
KodePress documentation · generated from the Markdown sources by
tools/build-docs-site.py · internal preview, not indexed.