|
| 1 | +# End-to-End Testing Plan |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The CK Join Form plugin ships with a Playwright end-to-end test suite that exercises the React join flow inside a real WordPress environment. Tests run against a wp-env (Docker WordPress) instance seeded with purpose-built test pages. |
| 6 | + |
| 7 | +### Test approach |
| 8 | + |
| 9 | +The suite validates the join form's UI behaviour and the data contracts it sends to the backend. It does not exercise live payment provider APIs. Instead, it uses helpers that simulate the outcome of payment flows (redirect-based success) and captures the `/join` request body to assert correctness. |
| 10 | + |
| 11 | +This is a deliberate trade-off: payment provider SDKs require live credentials, load scripts from CDNs, and introduce external redirect flows that are fragile in CI. By mocking the REST layer and simulating payment completion, the tests stay fast, deterministic, and credential-free while still covering the most important contract: what the frontend sends to the backend. |
| 12 | + |
| 13 | +### Infrastructure |
| 14 | + |
| 15 | +| Component | Location | Purpose | |
| 16 | +|-----------|----------|---------| |
| 17 | +| Test suite | `packages/join-e2e/tests/` | Playwright spec files | |
| 18 | +| Helpers | `packages/join-e2e/tests/helpers.ts` | Shared utilities for mocking, env injection, and payment simulation | |
| 19 | +| Seed script | `packages/join-e2e/scripts/setup.php` | Creates test pages with specific block configurations in WordPress | |
| 20 | +| Playwright config | `packages/join-e2e/playwright.config.ts` | Single Chromium project, serial execution, base URL `localhost:8889` | |
| 21 | + |
| 22 | +### Key helpers |
| 23 | + |
| 24 | +**`mockRestEndpoints(page)`** intercepts `/wp-json/join/v1/step` and `/wp-json/join/v1/join` with empty 200 responses so network errors never block form progression. |
| 25 | + |
| 26 | +**`injectEnvOverrides(page, urlPattern, overrides)`** intercepts the page HTML response and patches the `<script id="env">` JSON block before the browser receives it. This lets tests toggle feature flags (e.g. `USE_STRIPE`, `STRIPE_DIRECT_DEBIT_ONLY`) without changing WordPress plugin settings. |
| 27 | + |
| 28 | +**`captureJoinBodyViaStripeRedirect(page, pageUrl)`** simulates a completed Stripe payment by injecting a fake `stripePaymentIntentId` into `sessionStorage` and reloading with `?stripe_success=true`. The app detects this and jumps to the confirm stage. The helper intercepts the resulting `/join` POST and returns its body for assertions. |
| 29 | + |
| 30 | +### Test data |
| 31 | + |
| 32 | +Building with `USE_TEST_DATA=true` pre-fills all personal detail fields (name, email, phone, address). This lets tests skip manual field entry when the details page is not the focus. |
| 33 | + |
| 34 | +### Seed pages |
| 35 | + |
| 36 | +The setup script (`scripts/setup.php`) creates these WordPress pages: |
| 37 | + |
| 38 | +| Slug | Configuration | |
| 39 | +|------|---------------| |
| 40 | +| `e2e-standard-join` | Standard membership, GBP 5/month | |
| 41 | +| `e2e-free-join` | Free membership, GBP 0/month | |
| 42 | +| `e2e-donation-upsell` | Standard membership with `ask_for_additional_donation` enabled | |
| 43 | +| `e2e-supporter` | Supporter mode with 3 tiers (GBP 5, 10, 20/month) | |
| 44 | +| `e2e-supporter-custom` | Supporter mode with custom amount allowed | |
| 45 | +| `e2e-supporter-no-plans` | Supporter mode with no plans (triggers warning UI) | |
| 46 | + |
| 47 | + |
| 48 | +## What is covered |
| 49 | + |
| 50 | +The suite currently contains 52 passing tests across 7 spec files. |
| 51 | + |
| 52 | +| Spec file | Area | Tests | What it verifies | |
| 53 | +|-----------|------|------:|------------------| |
| 54 | +| `01-render.spec.ts` | Rendering | 3 | Form container present, env script injected, first name input visible, no console errors, progress breadcrumb renders | |
| 55 | +| `02-form-progression.spec.ts` | Form progression | 7 | Test data pre-fill, required field validation (name, email, phone, address), email format validation, country defaults to GB, details-to-plan advancement, plan selection, `/step` request body | |
| 56 | +| `03-free-membership.spec.ts` | Free membership | 4 | Payment stages skipped for GBP 0 plans, confirm stage reached from plan, confirm page content, `/join` request body | |
| 57 | +| `04-donation-upsell.spec.ts` | Donation upsell | 6 | Upsell page appears when enabled, "Not right now" skips to payment, tier selection advances, `recurDonation` flag toggling, supporter mode takes precedence over upsell | |
| 58 | +| `05-supporter-mode-monthly.spec.ts` | Supporter monthly | 9 | Donation page is first step, breadcrumb text, monthly toggle default, tier selection updates CTA text, donation-to-details progression, `/join` body fields, custom amount input | |
| 59 | +| `06-supporter-mode-oneoff.spec.ts` | Supporter one-off | 6 | One-off tab enabled under correct env flags, CTA text changes, `/join` body contains `recurDonation=false`, `paymentMethod=creditCard` forced, custom one-off amounts | |
| 60 | +| `07-supporter-mode-edge-cases.spec.ts` | Edge cases | 7 | One-off disabled under `STRIPE_DIRECT_DEBIT_ONLY`, explanatory note shown, monthly still works with DD-only, "no amounts configured" warning, standard vs supporter `/join` body differences | |
| 61 | + |
| 62 | +### Coverage strengths |
| 63 | + |
| 64 | +- **Form navigation logic** is well covered across standard, free, supporter, and upsell flows. |
| 65 | +- **Data contracts** (`/step` and `/join` request bodies) are asserted for all major flows. |
| 66 | +- **Feature flag behaviour** is tested by injecting env overrides, covering combinations of `USE_STRIPE`, `STRIPE_DIRECT_DEBIT`, `STRIPE_DIRECT_DEBIT_ONLY`, and `ASK_FOR_ADDITIONAL_DONATION`. |
| 67 | +- **Validation** for required fields and email format is exercised. |
| 68 | + |
| 69 | + |
| 70 | +## What is not covered |
| 71 | + |
| 72 | +### Payment UI rendering |
| 73 | + |
| 74 | +The `captureJoinBodyViaStripeRedirect` helper bypasses the payment stage entirely. If Stripe Elements stopped rendering, the payment method selector broke, or the intent creation API call failed, the current tests would not detect it. |
| 75 | + |
| 76 | +### GoCardless flow |
| 77 | + |
| 78 | +Zero coverage. GoCardless is the primary payment method for Direct Debit members. The hosted redirect flow, API flow, and mandate creation are all untested. There is no equivalent of `captureJoinBodyViaStripeRedirect` for GoCardless. |
| 79 | + |
| 80 | +### Chargebee flow |
| 81 | + |
| 82 | +Zero coverage. Neither the hosted pages flow nor the API flow is tested. |
| 83 | + |
| 84 | +### Update flow (`IS_UPDATE_FLOW=true`) |
| 85 | + |
| 86 | +No tests. When `IS_UPDATE_FLOW=true`, personal details are skipped and the form starts at the plan stage, with the member's email pre-filled via URL parameter. This distinct entry point has no coverage. |
| 87 | + |
| 88 | +### Payment method selection page |
| 89 | + |
| 90 | +The `payment-method` step (choosing between Direct Debit and Card when multiple providers are enabled) is never exercised. Tests either mock everything or bypass payment. |
| 91 | + |
| 92 | +### Optional data collection flags |
| 93 | + |
| 94 | +The following flags control whether additional fields appear on the details page, but none are tested: |
| 95 | + |
| 96 | +- `COLLECT_DATE_OF_BIRTH` |
| 97 | +- `COLLECT_COUNTY` |
| 98 | +- `COLLECT_HEAR_ABOUT_US` |
| 99 | +- `COLLECT_PHONE_AND_EMAIL_CONTACT_CONSENT` |
| 100 | + |
| 101 | +### Custom fields |
| 102 | + |
| 103 | +`CUSTOM_FIELDS` allows arbitrary text, checkbox, number, select, and radio fields on the details page. Not tested. |
| 104 | + |
| 105 | +### Address lookup |
| 106 | + |
| 107 | +`USE_POSTCODE_LOOKUP` enables a postcode-to-address autocomplete. Not tested. |
| 108 | + |
| 109 | +### `/join` response handling |
| 110 | + |
| 111 | +Tests verify what is sent to `/join` but not what happens with the response. Success redirects, error message rendering, and retry behaviour are all unverified. |
| 112 | + |
| 113 | +### Success redirect |
| 114 | + |
| 115 | +The `SUCCESS_REDIRECT` / `joined_page` block setting controls post-join navigation. Not tested. |
| 116 | + |
| 117 | +### Error states |
| 118 | + |
| 119 | +Network errors, server validation errors, Stripe card declines, and GoCardless mandate failures have no coverage. |
| 120 | + |
| 121 | +### Skip payment button |
| 122 | + |
| 123 | +`INCLUDE_SKIP_PAYMENT_BUTTON` renders a "skip to thank you" button. Not tested. |
| 124 | + |
| 125 | +### Backend integrations (out of scope) |
| 126 | + |
| 127 | +Mailchimp, Action Network, Zetkin, Auth0, and lapsing/unlapsing webhooks are backend concerns. Mailchimp is covered by PHPUnit (`JoinServiceMailchimpTest.php`). These are not appropriate for frontend e2e tests. |
| 128 | + |
| 129 | + |
| 130 | +## Future coverage workstreams |
| 131 | + |
| 132 | +Ordered roughly by value and feasibility. Each item is designed to be tackled independently. |
| 133 | + |
| 134 | +### 1. Update flow |
| 135 | + |
| 136 | +**Value:** High. This is a distinct user-facing flow with no coverage at all. |
| 137 | + |
| 138 | +**What is needed:** |
| 139 | +- [ ] Seed a new test page (`e2e-update-flow`) via `setup.php` |
| 140 | +- [ ] Use `injectEnvOverrides` to set `IS_UPDATE_FLOW=true` |
| 141 | +- [ ] Pass an `email` URL parameter to pre-fill the member email |
| 142 | +- [ ] Verify the form starts at the plan step (details page skipped) |
| 143 | +- [ ] Verify the pre-filled email appears on the confirm page |
| 144 | +- [ ] Assert the `/join` request body includes the URL-provided email |
| 145 | + |
| 146 | +### 2. Optional data collection fields |
| 147 | + |
| 148 | +**Value:** Medium. These are simple show/hide toggles but affect data sent to the backend. |
| 149 | + |
| 150 | +**What is needed:** |
| 151 | +- [ ] Use `injectEnvOverrides` to enable each flag individually |
| 152 | +- [ ] Verify the corresponding input fields appear on the details page |
| 153 | +- [ ] Fill in values and confirm they are included in the `/step` request body |
| 154 | +- [ ] Verify fields are absent when the flag is disabled |
| 155 | + |
| 156 | +Flags to cover: `COLLECT_DATE_OF_BIRTH`, `COLLECT_COUNTY`, `COLLECT_HEAR_ABOUT_US`, `COLLECT_PHONE_AND_EMAIL_CONTACT_CONSENT`. |
| 157 | + |
| 158 | +### 3. Payment method selection page |
| 159 | + |
| 160 | +**Value:** Medium. This is the gate between choosing Direct Debit vs Card when multiple providers are active. |
| 161 | + |
| 162 | +**What is needed:** |
| 163 | +- [ ] Use `injectEnvOverrides` to enable both `USE_GOCARDLESS=true` and `USE_STRIPE=true` |
| 164 | +- [ ] Verify the payment-method step appears between donation/plan and payment-details |
| 165 | +- [ ] Verify selecting "Direct Debit" and "Card" each advance to the correct next stage |
| 166 | +- [ ] Assert the selected `paymentMethod` value is stored in session state |
| 167 | + |
| 168 | +### 4. GoCardless redirect simulation |
| 169 | + |
| 170 | +**Value:** High, but requires more infrastructure. |
| 171 | + |
| 172 | +**What is needed:** |
| 173 | +- [ ] Write a `captureJoinBodyViaGoCardlessRedirect` helper that injects a fake `billing_request_id` into session state and reloads with `?gocardless_success=true` |
| 174 | +- [ ] Verify the confirm page renders and the `/join` request body includes GoCardless-specific fields |
| 175 | +- [ ] Determine the exact query parameters and session keys the app expects on GoCardless return (check the `useGoCardless` hook or equivalent) |
| 176 | + |
| 177 | +### 5. Chargebee redirect simulation |
| 178 | + |
| 179 | +**Value:** Medium. Similar pattern to GoCardless. |
| 180 | + |
| 181 | +**What is needed:** |
| 182 | +- [ ] Write a `captureJoinBodyViaChargebeeRedirect` helper that simulates the Chargebee hosted page return |
| 183 | +- [ ] Determine the expected query parameters on return (`?chargebee_success=true` or similar) |
| 184 | +- [ ] Use `injectEnvOverrides` to set `USE_CHARGEBEE=true` (and optionally `USE_CHARGEBEE_HOSTED_PAGES=true`) |
| 185 | +- [ ] Assert `/join` body includes Chargebee-specific fields |
| 186 | + |
| 187 | +### 6. `/join` response handling and error states |
| 188 | + |
| 189 | +**Value:** High for user experience confidence. Moderate implementation effort. |
| 190 | + |
| 191 | +**What is needed:** |
| 192 | +- [ ] Mock `/join` to return an error response (e.g. `{ success: false, message: "Email already registered" }`) |
| 193 | +- [ ] Verify the error message is displayed to the user |
| 194 | +- [ ] Mock `/join` to return a network error (use `route.abort()`) |
| 195 | +- [ ] Verify appropriate error UI is shown |
| 196 | +- [ ] Mock `/join` to return success and verify redirect to `SUCCESS_REDIRECT` URL |
| 197 | + |
| 198 | +### 7. Success redirect |
| 199 | + |
| 200 | +**Value:** Medium. Validates the post-join experience. |
| 201 | + |
| 202 | +**What is needed:** |
| 203 | +- [ ] Seed a test page with a `joined_page` / `SUCCESS_REDIRECT` pointing to a known URL |
| 204 | +- [ ] Complete a flow (e.g. free membership) and verify `page.url()` matches the redirect target |
| 205 | +- [ ] Test with and without a configured redirect |
| 206 | + |
| 207 | +### 8. Custom fields |
| 208 | + |
| 209 | +**Value:** Low-medium. Affects organisations that configure custom fields. |
| 210 | + |
| 211 | +**What is needed:** |
| 212 | +- [ ] Use `injectEnvOverrides` to inject a `CUSTOM_FIELDS` configuration with at least one of each type: text, checkbox, number, select, radio |
| 213 | +- [ ] Verify each field renders on the details page |
| 214 | +- [ ] Fill in values and verify they appear in the `/step` or `/join` request body |
| 215 | +- [ ] Verify validation (e.g. required custom fields block progression) |
| 216 | + |
| 217 | +### 9. Address lookup |
| 218 | + |
| 219 | +**Value:** Low. Nice to have, but the feature depends on a third-party postcode API. |
| 220 | + |
| 221 | +**What is needed:** |
| 222 | +- [ ] Mock the postcode lookup API endpoint |
| 223 | +- [ ] Use `injectEnvOverrides` to set `USE_POSTCODE_LOOKUP=true` |
| 224 | +- [ ] Type a postcode and verify the autocomplete dropdown appears with mocked results |
| 225 | +- [ ] Select an address and verify the address fields are populated |
| 226 | + |
| 227 | +### 10. Skip payment button |
| 228 | + |
| 229 | +**Value:** Low. Single feature flag toggle. |
| 230 | + |
| 231 | +**What is needed:** |
| 232 | +- [ ] Use `injectEnvOverrides` to set `INCLUDE_SKIP_PAYMENT_BUTTON=true` |
| 233 | +- [ ] Verify the skip button appears on the payment page |
| 234 | +- [ ] Click it and verify the user reaches the confirm page |
| 235 | +- [ ] Assert the `/join` body reflects that payment was skipped |
| 236 | + |
| 237 | + |
| 238 | +## Running the tests |
| 239 | + |
| 240 | +```bash |
| 241 | +# Start the wp-env environment |
| 242 | +yarn wp-env start |
| 243 | + |
| 244 | +# Seed the test pages |
| 245 | +yarn wp-env run tests-cli wp eval-file /var/www/html/wp-content/e2e-scripts/setup.php |
| 246 | + |
| 247 | +# Build the frontend with test data pre-fill |
| 248 | +USE_TEST_DATA=true yarn build |
| 249 | + |
| 250 | +# Run the suite |
| 251 | +cd packages/join-e2e |
| 252 | +npx playwright test |
| 253 | + |
| 254 | +# Run a specific spec |
| 255 | +npx playwright test tests/03-free-membership.spec.ts |
| 256 | + |
| 257 | +# View the HTML report after a run |
| 258 | +npx playwright show-report |
| 259 | +``` |
0 commit comments