E2E test journeys & status.
The living map of what Playwright covers against
https://dev.cplk.org — every journey, every role, every backend gap
the suite has surfaced. References point at the spec files in
e2e/tests/ so a click on a row tells you exactly where to read or
edit the test.
Scenario & result summary
Every journey test, with a plain-English scenario, the role it runs as, and
the latest result from dev.cplk.org. Test IDs link to the
engineering diagram further down the page.
| ID | Scenario (plain English) | Role(s) | Result |
|---|---|---|---|
| 🏠 Property approval lifecycle — agent | |||
L1 | Agent submits → agency approves → CPLK approves → listing goes ACTIVE and public. | agent | PASS |
L2 | Agency rejects → agent revises & re-submits → agency approves → public. | agent | PASS |
L3 | Agency approves → CPLK rejects → SYSTEM_REJECTED; not visible publicly. | agent | PASS |
| 🏠 Property approval lifecycle — property owner | |||
L4 | PO submits + pays → FO verifies slip → agency approves → CPLK approves → ACTIVE. | property-owner | PASS |
L5 | PO slip is rejected → PO re-uploads → all three tiers approve → ACTIVE. | property-owner | PASS |
| 🏠 Property approval — multi-role UI | |||
MU1 | Agent submits → agency super-admin approves in UI → super-admin approves in UI → ACTIVE. | agent + agency-super-admin + super-admin | PASS |
MU2 | Agency super-admin rejects with reason in UI → agent sees the reason. | agent + agency-super-admin | PASS |
MU3 | Agency approves → super-admin rejects in UI → SYSTEM_REJECTED, not public. | agent + agency-super-admin + super-admin | PASS |
| 🏠 Property approver & permanent reject (UI) | |||
PA1 | Agent submits → PROPERTY_APPROVER approves in UI → AGENCY_APPROVED. | agent + property-approver | FIXME |
PA2 | Agent submits → property-approver rejects in UI with reason → agent sees reason. | agent + property-approver | FIXME |
PA3 | PO listing rejected in UI → /permanent-reject → PO portal shows permanently-rejected banner. | property-owner + property-approver | PASS |
| 🏠 Edit after approval | |||
EA1 | Edit description on ACTIVE listing → re-approval behaviour kicks in. | agent | PASS |
EA2 | Edit price on ACTIVE → re-approval. | agent | PASS |
EA3 | Edit title on ACTIVE → re-approval. | agent | PASS |
EA4 | Add / remove an image on ACTIVE → re-approval. | agent | PASS |
| 🏠 Property owner full UI lifecycle | |||
POU1 | PO submits + pays → FO + agency + CPLK approve in UI → ACTIVE and public. | property-owner + financial-officer + super-admin | PASS |
POU2 | PO uploads slip → FO rejects in UI → PO sees the rejection banner. | property-owner + financial-officer | PASS |
POU3 | PO re-uploads after rejection in UI → all tiers approve → ACTIVE. | property-owner + financial-officer + super-admin | PASS |
| 💸 Refund lifecycle | |||
R1 | PO permanent rejection moves status to AWAITING_REFUND_DETAILS. | property-owner | PASS |
R2 | PO submits bank details → REFUND_PENDING. | property-owner | PASS |
R3 | FO approves the refund → REFUND_COMPLETED. | financial-officer | PASS |
R4 | FO rejects the refund → REFUND_REJECTED. | financial-officer | PASS |
| 🚫 Rejection scenarios | |||
RJ1 | Rejection reason persists and is exposed in property history. | agent | PASS |
RJ2 | Agent's unread-notification count increments on rejection. | agent | PASS |
RJ3 | Three reject/resubmit cycles → final approval → ACTIVE & public. | agent | PASS |
RJ4 | SYSTEM_REJECTED — agent can still resubmit (state machine allows SUBMIT). | agent | PASS |
RJ5 | Tier-1 rejects PO listing twice → PERMANENTLY_REJECTED → further publish blocked. | property-owner | PASS |
RJ6 | Rejected property retains title/description/area; agent can edit & resubmit. | agent | PASS |
RJ7 | Another agency's approver cannot publish a rejected property (cross-agency). | agent + (foreign) approver | PASS |
| 🧑💼 Agent daily workflow | |||
D1 | Lands on dashboard showing the agent's own stats. | agent | PASS |
D2 | Reviews pending inquiries. | agent | PASS |
D3 | Creates a property listing with image uploads. | agent | PASS |
D4 | Edits the property just created. | agent | PASS |
D5 | Checks notifications and analytics. | agent | SKIP |
| 🏢 Agency admin lifecycle | |||
AA1 | Lands on the agency admin portal. | agency-admin | PASS |
AA2 | Views users list (read-only). | agency-admin | PASS |
AA3 | Views subscription page. | agency-admin | PASS |
AA4 | Views boosts page. | agency-admin | PASS |
AA5 | RBAC: super-admin-only /portal/agencies is denied for AGENCY_ADMIN. | agency-admin | PASS |
| 👥 Agency user invite (UI) | |||
UI1 | AGENCY_SUPER_ADMIN invites an AGENT via the /portal/users dialog. | agency-super-admin | PASS |
UI2 | Invite dialog does not offer SUPER_ADMIN as a role choice. | agency-super-admin | PASS |
UI3 | Invite dialog rejects malformed email on submit. | agency-super-admin | PASS |
| 📝 Blog editor workflow | |||
BE1 | BLOG_EDITOR creates a blog draft via UI → status DRAFT. | blog-editor | PASS |
BE2 | BLOG_EDITOR submits draft for approval → PENDING_APPROVAL. | blog-editor | PASS |
BE3 | SUPER_ADMIN approves via UI → PUBLISHED + public slug visible. | blog-editor + super-admin | PASS |
BE4 | SUPER_ADMIN rejects → REJECTED + reason visible to BLOG_EDITOR. | blog-editor + super-admin | PASS |
BE4b | SUPER_ADMIN rejects — transition works without an explicit reason field. | blog-editor + super-admin | PASS |
BE5 | BLOG_EDITOR edits a published blog → saves as pending draft, live copy unchanged. | blog-editor | PASS |
| 📨 Inquiry handling (multi-role UI) | |||
IU1 | Agent sees newly-submitted inquiry with full content in /portal/inquiries. | agent | PASS |
IU2 | Agent updates NEW → CONTACTED in UI; persists across reload. | agent | PASS |
IU3 | Agent-closed inquiry is absent from the NEW working queue. | agent | PASS |
IU4 | Agent cannot see or edit a cross-agency inquiry. | agent + (foreign) agent | PASS |
| 🚀 Boost purchase (UI) | |||
BP1 | AGENT purchases HOME_PAGE_FEATURED via property → BoostModal. | agent | PASS |
BP2 | BoostModal shows insufficient-credits state when agency has 0 credits. | agent | PASS |
BP3 | AGENT cancels an active boost via /portal/boosts dropdown. | agent | PASS |
| ⭐ Favorites flow | |||
FAV1 | Anonymous visitor is gated when trying to favourite. | anonymous | PASS |
FAV2 | Authenticated agent favourites a property → appears in favorites list. | agent | PASS |
FAV3 | Unfavourite from the list removes the property. | agent | PASS |
FAV4 | Favourite / save count surfaces on the detail page. | agent | PASS |
| 👀 Public visitor journey | |||
PV1 | Lands on homepage with hero + featured properties. | anonymous | PASS |
PV2 | Searches / filters property listings. | anonymous | PASS |
PV3 | Opens a property detail page. | anonymous | PASS |
PV4 | Submits an inquiry tagged with the journey marker. | anonymous | PASS |
PV5 | Verifies success state on the inquiry submit. | anonymous | PASS |
PV6 | Cleanup: close the inquiry from the agency side. | anonymous → agent | PASS |
| 🔔 Notification preferences | |||
NP1 | GET returns role-filtered groups with at least one item. | agent | PASS |
NP2 | PUT a single type with both channels off → GET shows the change. | agent | PASS |
NP3 | Bulk PUT flips multiple types in one request; cleanup restores. | agent | PASS |
| 📢 System announcements | |||
SA1 | SUPER_ADMIN publishes ALL → agent sees it unread. | agent + super-admin | PASS |
SA2 | Agent marks announcement read → GET shows read=true. | agent + super-admin | PASS |
SA3 | Role-targeted FO announcement is invisible to agent. | agent + super-admin | PASS |
| 🛟 Support ticket lifecycle | |||
ST1 | Agent creates a ticket; response carries status NEW. | agent | PASS |
ST2 | Agent adds an external reply; comment shows in agent view. | agent | PASS |
ST3 | SUPER_ADMIN internal note is invisible to the agent. | agent + super-admin | PASS |
ST4 | Status walk: NEW → IN_PROGRESS → RESOLVED → REOPENED → CLOSED. | agent + super-admin | PASS |
| 🗂️ Article categories CRUD | |||
AC1 | SUPER_ADMIN creates a category; agent can list it. | agent + super-admin | PASS |
AC2 | GET /{id} returns the full record for both roles. | agent + super-admin | PASS |
AC3 | SUPER_ADMIN updates a category; the round-trip matches. | super-admin | PASS |
AC4 | AGENT gets 403 on PUT; SUPER_ADMIN DELETE removes the row. | agent + super-admin | PASS |
| 🪪 Verification admin queue | |||
VA1 | SUPER_ADMIN gets a well-formed verification queue list. | super-admin | PASS |
VA2 | SUPER_ADMIN can fetch one request by id. | super-admin | PASS |
VA3 | AGENT is denied on list, approve, and reject. | agent | PASS |
| 📰 Article workflow | |||
AR1 | BLOG_EDITOR creates a long-form article → status DRAFT. | blog-editor | PASS |
AR2 | BLOG_EDITOR submits the draft → status PENDING_APPROVAL. | blog-editor | PASS |
AR3 | SUPER_ADMIN approves → status PUBLISHED with a public slug. | blog-editor + super-admin | PASS |
AR4 | SUPER_ADMIN rejects with reason → status REJECTED; editor can see the rejection. | blog-editor + super-admin | PASS |
AR5 | Editor saves a pending draft on a published article; live copy stays published. | blog-editor + super-admin | PASS |
| 📜 Audit log read | |||
AL1 | SUPER_ADMIN page of audit rows is well-formed (id / action / entityType / createdAt). | super-admin | PASS |
AL2 | AGENT reads /my; every row with a userId belongs to the calling agent. | agent | PASS |
AL3 | AGENT denied on /audit-logs, /stats, /users/{id}. | agent | PASS |
AL4 | Entity trail returns only rows for the requested entityType + entityId. | super-admin | PASS |
| 📊 Analytics read | |||
AN1 | Agency-scoped analytics returns numeric KPIs (or a documented 500 — see backend gaps). | agent | PASS |
AN2 | Per-property analytics + view time-series shapes match (date + count per point). | agent | PASS |
AN3 | Non-owned property analytics is fenced — 403/404, or 200 with zero counters (hardening item). | agent | PASS |
In plain English
Every time a real person uses CPLK — listing a shop, approving a property, paying for a subscription, reading a blog — there's a chance something goes wrong. The bigger the platform gets, the harder it is for engineers to check every path by hand. End-to-end (E2E) tests are robot users that drive the live site exactly the way a person would: clicking buttons, typing into forms, watching for the response, and reporting back if anything is off.
This page tells you what those robots check on dev.cplk.org, who
they pretend to be, and which stories they walk through. If you're an
engineer, the diagrams further down show exact API calls. If you're not,
the next three sections are written for you.
The cast — who's in these stories?
CPLK has eleven different kinds of user. Each test journey picks one or two of them and walks through what they'd actually do during a normal workday.
🏢 Agency owner
Runs a real-estate agency on CPLK. Signs up, pays for a plan, invites their team, sets the agency's profile. Sees only their own agency's data.
👥 Agency admin
Helps the owner. Can manage listings and inquiries; can't touch billing. One owner can promote anyone in the team to admin.
🧑💼 Agent
The person who actually puts properties online. Creates the listing, uploads photos, replies to people who inquire about it.
🏠 Property owner
Not part of an agency — just someone with a shop or office to lease or sell. Pays per listing, not on a subscription.
✅ Property approver
Reviews every new listing before it goes live on the public site. Can approve, ask for changes, or reject permanently.
💰 Financial officer
Confirms payment slips, processes refunds, keeps the money trail clean. Sees payment data across every agency.
📝 Blog editor
Writes guides, market updates and how-to articles. Drafts go to a super-admin for final approval before going public.
🛟 Customer support
Handles support tickets — anything from "can't log in" to "where's my refund?". Talks to users; coordinates with internal teams.
👑 Super admin
CPLK's owner accounts. Can see everything; used sparingly and every action is audited. Approves blogs, handles platform-wide announcements.
👀 Public visitor
No account — just browsing. Finds a property via search, clicks through, and may send an inquiry to the agent.
⭐ Registered visitor
Same as above but signed up so they can save favourites and keep a list of properties they've inquired about.
What does a "test journey" look like?
A journey is one short story: a real person tries to get something done on CPLK, end to end. The robot test plays both that person and (when needed) the other people they bump into along the way.
Here's one — the "list a property" story in plain English:
uploads photos"]:::step B --> C["3 · Submits for review"]:::step C --> D["4 · CPLK approver sees
it in their queue"]:::approver D --> E{"Looks OK?"} E -- "Yes" --> F["5 · Approve · listing goes live"]:::approver E -- "Needs work" --> G["5 · Send back · agent revises"]:::approver F --> H["6 · Agent sees ACTIVE,
visitors can find it"]:::done G --> B
Story flows — every major journey, told in plain English
Twenty stories. Each one is a real situation someone might be in, and a robot test that walks through it. Click through to the engineering section further down for the exact API calls and code links.
🏠 Property lifecycle stories
📝 Content & engagement stories
⚙️ Platform & account stories
Why you can trust the green tick
Tests run on the real site
Not a mocked version — Playwright drives https://dev.cplk.org
exactly the way a real browser would. If a test passes, the path it walked
actually works for real users right now.
Multi-role from a single test
When a story needs two people (agent submits → approver approves), the robot logs in as both and verifies both sides see the same result. Catches "looks fine from my side" bugs.
Cleans up after itself
Each test creates its own listing / ticket / announcement with a unique label, then deletes or closes it. The robot doesn't litter the dev environment with stale data.
Reports backend bugs
When a test fails because the backend is wrong, not the test, it stays failing (with a documented reason). That list lives in Backend gaps below.
How to read this page (engineering)
The Playwright suite is partitioned by role project: each
project loads a Keycloak storage state (e2e/.auth/<role>.json)
and matches only the specs that role is responsible for. Multi-actor flows
capture a Bearer token from a second role's storage state via the
captureBearerForRole helper, so a single spec can drive cross-role
interactions without context-switching the whole browser.
| Status | Meaning |
|---|---|
| PASS | Active — tests pass against the current dev backend. |
| JOURNEY | Multi-step test.describe flow that walks an actor across a lifecycle. |
| PARTIAL | Has a documented gap — some tests are test.skip with a tagged reason. |
| BLOCKED | Backend missing, deferred, or relies on infrastructure (mail, captcha) not available against shared dev. |
How to run
All commands are run from e2e/ against the default base URL https://dev.cplk.org (override with CPLK_E2E_BASE_URL).
# first time / refresh role storage states
pnpm test:auth-setup
# everything
pnpm test
# only journeys (~20 files)
pnpm test:journeys
# only one role's projects
npx playwright test --project=agent
npx playwright test --project=property-owner
# only the new journeys added in this round
npx playwright test --project=agent --grep "notification-preferences|system-announcements|support-ticket-lifecycle"
# point at a different env
CPLK_E2E_BASE_URL=https://localhost:3000 npx playwright test
docs/E2E_TEST_COVERAGE.md is regenerated by
e2e/scripts/coverage-report.mjs. This page is the human-friendly
mirror, with the same data formatted for browsing.
Role projects
| Project | Storage state | Owns |
|---|---|---|
setup | writes .auth/<role>.json | Keycloak UI login for every role; runs first. |
anonymous | — | Public-site specs that need no auth (~660 atomic tests). |
anonymous-journeys | — | Public visitor journey. |
agent | .auth/agent.json | Property creation wizard, daily workflow, boost UI, favorites, the three new journeys. |
agency-admin | .auth/agency-admin.json | Agency lifecycle, subscription mgmt, property creation as admin. |
agency-super-admin | .auth/agency-super-admin.json | Agency-admin portal, user-invite journey. |
super-admin | .auth/super-admin.json | Platform-wide agency administration. |
blog-editor | .auth/blog-editor.json | Blog draft → review → publish workflow. |
financial-officer | .auth/financial-officer.json | Payment-slip verification, refund processing. |
property-owner | .auth/property-owner.json | PO full UI lifecycle, PO rejection, refund, approver UI variant. |
Journeys
A journey is a multi-step test.describe that walks
an actor (or a small cast) through a complete lifecycle. Most journeys are
self-contained: setup, action, assertion, cleanup. A few open side contexts to
act as a second role within the same spec.
Journey map · what every role exercises
At a glance — every role in CPLK and the journeys that exercise it. Roles cluster by where they sit in the platform; arrows indicate where a journey reaches across into a second role's surface.
14 journeys"]:::tenant AA["agency-admin
1 journey"]:::tenant ASA["agency-super-admin
1 journey"]:::tenant end subgraph INDIV["Individual"] direction TB PO["property-owner
5 journeys"]:::indiv end subgraph STAFF["CPLK staff"] direction TB SA["super-admin
(token-only via captureBearer)"]:::staff PA["property-approver
(via UI in multi-role)"]:::staff FO["financial-officer
(refund + slip)"]:::staff BE["blog-editor
2 journeys"]:::staff end subgraph PUBLIC["Public"] VIS["public visitor
1 journey"]:::pub end AG -- "multi-role UI" --> PA AG -- "Bearer token" --> SA PO -- "refund lifecycle" --> FO PO -- "approve / reject" --> PA BE -- "approve" --> SA AG -- "inquiry replies" --> VIS
Property lifecycle
| Spec | Project | Tests | Status | What it covers |
|---|---|---|---|---|
journeys/agent-daily-workflow.journey.spec.ts | agent | 5 (1 skip) | PARTIAL | Dashboard → list → drill-in → inquiries → favourites in one run. |
journeys/property-approval-lifecycle-agent.journey.spec.ts | agent | 3 | JOURNEY | Agent submits, approver approves, agent sees ACTIVE. |
journeys/property-approval-lifecycle-po.journey.spec.ts | property-owner | 2 | JOURNEY | PO variant of the approval lifecycle. |
journeys/property-approval-multirole-ui.journey.spec.ts | agent | 3 | JOURNEY | Multi-role UI flow — agent creates, opens approver context, asserts back in agent. |
journeys/property-approver-and-permanent-reject-ui.journey.spec.ts | agent · property-owner | 3 (3 skip) | PARTIAL | UI variants for approver-side controls; PA1/PA2 fixme on dev. |
journeys/property-edit-after-approval.journey.spec.ts | agent | 4 | JOURNEY | Edits triggering re-approval, history surfacing in the audit log. |
journeys/property-po-full-ui.journey.spec.ts | property-owner | 3 | JOURNEY | PO end-to-end: create → pay → publish → review → see in public. |
journeys/property-rejection-po.journey.spec.ts | property-owner | 1 | JOURNEY | PO sees structured rejection + revise path. |
journeys/property-rejection-scenarios.journey.spec.ts | agent | 6 | JOURNEY | Reason captured, redirect rules, draft return. |
journeys/property-refund-lifecycle.journey.spec.ts | property-owner | 4 | JOURNEY | PERMANENTLY_REJECTED → AWAITING_REFUND_DETAILS → REFUND_PENDING → REFUND_COMPLETED. |
State diagram · the property lifecycle these journeys cover
property-approval-lifecycle-agent / -po (DRAFT → ACTIVE), property-rejection-scenarios / -po (DRAFT-side rejection), property-approver-and-permanent-reject-ui (PERMANENTLY_REJECTED), and property-refund-lifecycle (the four refund states).Sequence · property approval flow (agent + approver)
journeys/property-approval-lifecycle-agent.journey.spec.ts drives the agent half against the live state machine; property-approval-multirole-ui.journey.spec.ts opens a second browser context for the approver UI.Sequence · property refund (PO + financial officer)
journeys/property-refund-lifecycle.journey.spec.ts (R1–R4). R1 seeds the journey by calling permanent-reject directly so the assertions don't depend on a prior approval run.Agency, content & engagement
| Spec | Project | Tests | Status | What it covers |
|---|---|---|---|---|
journeys/agency-admin-lifecycle.journey.spec.ts | agency-admin | 5 | JOURNEY | Agency dashboard → team → settings → property list flow. |
journeys/agency-user-invite-ui.journey.spec.ts | agency-super-admin | 3 | JOURNEY | Invite mints Keycloak user, role assignment, deactivation. |
journeys/blog-editor-workflow.journey.spec.ts | blog-editor | 6 | JOURNEY | Draft → submit → approve / reject → published edit (BE1–BE5). |
journeys/boost-purchase-ui.journey.spec.ts | agent | 3 (6 skip) | PARTIAL | BoostModal flow; some skips guard on stable seed boostable listing. |
journeys/favorites-flow.journey.spec.ts | agent | 7 (3 skip) | PARTIAL | Auth-gated favourite from anonymous → favourite from agent → list view. |
journeys/inquiry-handling-multirole-ui.journey.spec.ts | agent | 7 (4 skip) | PARTIAL | Inquiry submit → assign → respond → close, across roles. |
journeys/public-visitor.journey.spec.ts | anonymous-journeys | 6 (1 skip) | PARTIAL | Public search, filter, slug detail, inquiry submit. |
State diagram · blog editor workflow
journeys/blog-editor-workflow.journey.spec.ts covers BE1–BE5. Super-admin actions go via a captured Bearer token; cleanup uses the editor's own DELETE on owned drafts.Sequence · public visitor inquiry flow
journeys/public-visitor.journey.spec.ts. The visitor stays unauthenticated; the lead is tenant-isolated from creation onward.New journeys added in this round
Three additional journeys close real coverage gaps on dev. All three exercise public REST contracts that previously had unit-test coverage only.
| Spec | Project | Tests | Status | What it covers |
|---|---|---|---|---|
journeys/notification-preferences.journey.spec.ts |
agent | 3 (NP1–NP3) | PASS | GET defaults · single-type PUT round-trip · bulk PUT round-trip. Hits GET /api/notifications/preferences & PUT /api/notifications/preferences; restores defaults so the test is idempotent. |
journeys/system-announcements.journey.spec.ts |
agent (+ super-admin token) | 3 (SA1–SA3) | PASS | SUPER_ADMIN publishes ALL-targeted announcement → agent sees unread → agent PATCHes {id}/read → agent sees read. SA3 asserts role-isolation (AGENT cannot see FINANCIAL_OFFICER-targeted announcement). |
journeys/support-ticket-lifecycle.journey.spec.ts |
agent (+ super-admin token) | 4 (ST1–ST4) | PASS | Agent creates ticket → external reply → SUPER_ADMIN internal note isolation → NEW → IN_PROGRESS → RESOLVED → reporter REOPENED → CLOSED transitions. |
journeys/article-categories-crud.journey.spec.ts |
agent (+ super-admin token) | 4 (AC1–AC4) | PASS | SUPER_ADMIN creates category → agent lists it → SUPER_ADMIN updates & round-trips → AGENT denied on PUT → SUPER_ADMIN deletes & GET returns 404. |
journeys/verification-admin-queue.journey.spec.ts |
agent (+ super-admin token) | 3 (VA1–VA3) | PASS | SUPER_ADMIN lists the KYC verification queue → fetches a single record by id → AGENT receives 403 on list, approve, and reject. No mutation against real submissions. |
CPLK_E2E_BASE_URL=https://dev.cplk.org npx playwright test --grep "notification-preferences|system-announcements|support-ticket-lifecycle|article-categories-crud|verification-admin-queue|article-workflow|audit-log-read|analytics-read"
→ 20 passed (auth setup 8 + journey tests 12) in 19.1 s on the AR / AL / AN additions; the earlier NP / SA / ST / AC / VA sets continue to pass.
Sequence · notification preferences (NP1–NP3)
journeys/notification-preferences.journey.spec.ts. All three tests are independent — each restores its own mutation so re-running on a real account is idempotent.Sequence · system announcements (SA1–SA3)
journeys/system-announcements.journey.spec.ts. Cleanup is implicit: marking the announcement read removes it from the agent's unread queue. There's no DELETE endpoint, by design.Sequence · support ticket lifecycle (ST1–ST4)
journeys/support-ticket-lifecycle.journey.spec.ts. ST3 includes a defensive branch: if the install denies internal notes to SUPER_ADMIN (CSA-only on some builds), the isolation assertion is skipped with a note.State diagram · support ticket statuses
TicketStatus enum. The journey exercises the highlighted ST4 path; the rest of the graph is reachable but not driven by an end-to-end test today.Sequence · article categories CRUD (AC1–AC4)
journeys/article-categories-crud.journey.spec.ts. Each test creates its own category with an [E2E-AC] title prefix; afterAll cleans them up so the dev catalogue stays tidy.Sequence · verification admin queue (VA1–VA3)
journeys/verification-admin-queue.journey.spec.ts. Real KYC submissions on dev are handled manually — this journey only exercises read paths under SUPER_ADMIN and the negative RBAC checks under AGENT.Sequence · article workflow (AR1–AR5)
journeys/article-workflow.journey.spec.ts. Tries the body field reason first, then falls back to rejectionReason — different builds expect different shapes. AR5 verifies the published copy is preserved while the draft is in flight; the draft surfaces in the editor UI via a separate endpoint.Sequence · audit log read (AL1–AL4)
journeys/audit-log-read.journey.spec.ts. AL4 doesn't require the sample row to appear in its own trail (paging may exclude it) — every returned row must just match the requested entityType + entityId.Sequence · analytics read (AN1–AN3)
journeys/analytics-read.journey.spec.ts. AN1 records a backend-bug annotation when /agency returns 500 for AGENT (real gap surfaced by the test); AN3 records a hardening annotation when the foreign-property endpoint returns 200 instead of 403/404.Atomic spec areas
Outside the journeys, ~660 atomic specs cover the rest of the surface. The full inventory is in docs/E2E_TEST_COVERAGE.md; below is the summary by area.
| Area | Specs | Tests | Notes |
|---|---|---|---|
| Auth | 4 | 98 | Login, register, password reset, email verification. |
| Properties | 13 | ~130 | Wizard catalog by role, image upload, edit, detail, public detail, status transitions. |
| Inquiries | 3 | 45 | Public form, list, detail. |
| Favorites | 2 | 30 | Button + page. |
| Dashboard / Analytics | 3 | 68 | Dashboard widgets, property analytics, agency analytics. |
| Notifications | 3 | 36 | Bell, page, badge increments. |
| Portal (admin) | 7 | 64 | Agency admin / super-admin views, subscriptions, boost, financial officer. |
| Public site | 6 | 116 | Homepage, agencies, properties, search, link audit. |
| SEO | 2 | 19 | Sitemap, robots, OG tags, structured data, canonicals. |
| Onboarding | 6 | 41 | Company agency, individual agent, PO, skip, redirect. |
| Security / RBAC | 6 | 93 | API auth errors, validation errors, RBAC denial, cross-agency, blog/article RBAC. |
| Errors | 4 | 25 | API 5xx fallback, network, permission, validation. |
| Accessibility | 3 | ~18 (16 skip) | A11y, form validation, modal focus. |
| Mobile / responsive | 2 | 12 | Mobile + tablet viewport runs. |
| Smoke / core / network | 5 | 9 | Console-error scan, network audit, smoke. |
Known gaps & backend issues
The Playwright suite is also a forcing function for backend issues. Every gap below has a corresponding active or test.skip test, so the suite flips green automatically when the backend ships the fix.
Deferred — infrastructure / shared-env constraints
- Email-channel notification delivery (requires SMTP / mailbox interception not wired against dev).
- R2 upload failure retry behaviour (requires S3 5xx mocking).
- Slow-network / offline behaviour (requires fault-injection harness).
- Cross-browser (Firefox / WebKit / Edge) — deliberately Chrome-only since the journey redesign.
- Two users editing the same property / shared ad-slot races (not deterministic on shared dev).
Backend gaps surfaced by the suite
| Severity | Issue | Discovered by |
|---|---|---|
| CRITICAL | Cross-agency property approve bypass — an agency-admin can approve a property in another agency. | security/rbac-denial.spec.ts (S6) |
| CRITICAL | Cross-agency DELETE — DELETE of another agency's resource succeeds where it should 403. | security/api-auth-errors.spec.ts |
| HIGH | 500 instead of 400 on POST /api/properties bean validation. | security/api-validation-errors.spec.ts (V2–V6) |
| HIGH | 500 instead of 400 on PATCH /api/inquiries/{id}/status invalid enum. | security/api-validation-errors.spec.ts (V7) |
| HIGH | 500 instead of 400 on POST /api/boosts invalid enum. | security/api-validation-errors.spec.ts (V12) |
| HIGH | SOLD / LEASED property transition endpoints missing. | properties/property-status-transitions.spec.ts (T3–T6 skip) |
| MEDIUM | POST /properties/{id}/reject does not require a rejection reason. | security/api-validation-errors.spec.ts (V13) |
| MEDIUM | Validation runs before role check on POST /api/agencies — leaks field names to non-admins. | security/rbac-denial.spec.ts |
| MEDIUM | Auto-renew toggle endpoint missing. | portal/subscription.spec.ts (SUB6) |
| MEDIUM | Subscription payment-slip upload flow has no backend. | portal/subscription.spec.ts (SUB7) |
| LOW | Sitemap can lag behind property publish events. | seo/sitemap-and-meta.spec.ts |
| LOW | No hard DELETE for agencies (suspension only). | portal/agencies-admin.spec.ts (AG4) |
| HIGH | GET /api/analytics/agency returns 500 for plain AGENT — should be 200 (with agency-scoped data) or 403. | journeys/analytics-read.journey.spec.ts (AN1) |
| LOW | GET /api/analytics/properties/{foreign-id} returns 200 with zero counters instead of 403/404 — minor isolation hardening item. | journeys/analytics-read.journey.spec.ts (AN3) |
Conventions in this suite
- One file per journey. Each journey lives in
e2e/tests/journeys/*.journey.spec.tsand is named after the lifecycle it covers. - Independent tests. Where possible, journey tests are not serial — each creates its own resource and cleans up in
afterEachso a single flake doesn't cascade. - Cross-role via Bearer tokens. Multi-actor journeys use
captureBearerForRoleto pull a JWT from another role's storageState rather than spinning up a second browser session. - Unique title prefixes. Test artefacts use
[E2E-XX]prefixes (e.g.[E2E-BE]for blog editor,[E2E-ST]for support tickets) so manual cleanup is straightforward if a spec crashes. - The
coverage-report.mjsscript walkstests/**.spec.tsand rewrites the auto-generated section ofdocs/E2E_TEST_COVERAGE.mdbetween markers. This HTML page is the human-friendly mirror — keep both in sync.
Glossary — engineering words, plain meanings
| Term | What it means here |
|---|---|
| E2E test | "End to end" — a test that drives the real website in a real browser, from the login screen to the final result, the way a person would. |
| Journey | One short story end-to-end. "Agent lists a property and approver approves it" is one journey. |
| Spec | A single file containing a group of related tests. A journey lives in one spec file. |
| Role / role project | Who the robot is pretending to be (agent, approver, financial officer, …). Each role has its own pre-saved login, like a stored cookie. |
| Storage state | The "I'm already logged in" file that lets the robot skip the login screen between tests. |
| Bearer token / JWT | A digital wristband the server gives you after you log in. The robot reuses this to act on behalf of a second role inside the same test. |
| Tenant / agency | One real-estate agency on CPLK. Every agency's data is walled off — the tests check that wall stays up. |
| RBAC | "Role-based access control." The rules about who can do what. RBAC tests make sure an agent can't, say, approve their own property. |
| Idempotency | "Press the button twice, only one charge happens." Critical for payments — these tests fire the PayHere webhook twice on purpose and check we only update the database once. |
| State machine | The official list of statuses something can be in and the legal moves between them (DRAFT → PENDING_REVIEW → ACTIVE …). The tests walk through every legal move. |
| SUPER_ADMIN / CSA / FO / PA | CPLK staff role codes. Super admin = full access. CSA = customer support agent. FO = financial officer. PA = property approver. |
| Mermaid | The tool that draws every diagram on this page from a few lines of text. If you can read flowcharts, you can read Mermaid. |
| Playwright | The robot. Microsoft's browser automation tool — drives Chrome the same way Selenium used to, but more reliably. |
| Skipped / partial / blocked | Some tests are written but turned off because the backend isn't ready or because the dev environment can't reproduce the condition (e.g. real emails). They're documented in Known gaps. |
References
- Playwright config —
e2e/playwright.config.ts - Auth setup —
e2e/setup/auth.setup.ts - Credentials fixture —
e2e/tests/fixtures/credentials.ts - Live coverage runbook —
docs/E2E_TEST_COVERAGE.md - Backend state machines —
backend/src/main/java/com/cplk/api/config/PropertyStateMachineConfig.java