You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b085d3f
Browse filesBrowse the repository at this point in the historyBrowse files
refactor(tests): clean-code rewrite — verb POM, support layer, comment-free
Apply GRASP/KISS/DRY/YAGNI to the suite while keeping the same scenarios:
- Page objects expose verb-only actions over private locators; remove all
noun-method locator builders. Shared low-level interaction lives in BasePage
(fillReactively, toggleHiddenControl) — kills the 5x evaluate/dispatchEvent
duplication. Names carry intent; zero comments in tests/** and fixtures.
- New tests/support layer (Dependency Inversion): purchaseVpn flow over
OfferLandingPage/PaymentStep interfaces dedupes the RU/EN twin; specs are
declarative. Pure reachedPaymentProvider oracle replaces the fake
PaymentRedirectPage (which masqueraded as a page) and rejects */failed pages.
- acceptTerms scoped to the payment form's checkboxes via the shared helper
(was a document-wide querySelectorAll sweep).
- YAGNI: delete unused payment-draft factory, user-factory transients, dead
LoginPage/SignupPage locators, BasePage.currentUrl.
- Relocate load-bearing 'why' (PrimeVue events, hidden controls, oracle,
known gaps) to docs/ARCHITECTURE.md §5. Wire @support alias + layer.
Suite shrinks ~1073→877 LOC. Validated: typecheck, lint, validate, format
green; RU-stripe + EN paths pass live; RU card_ru is the documented live-site
flake (retries handle it in CI).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The repo is built around a 5-layer convention pinned in [`tests-config.json`](tests-config.json) and mechanically enforced by validators under [`tools/`](tools/). Imports flow one direction only — reverse imports across layers are forbidden. A deeper walkthrough lives in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
89
+
The repo is built around a 6-layer convention pinned in [`tests-config.json`](tests-config.json) and mechanically enforced by validators under [`tools/`](tools/). Imports flow one direction only — reverse imports across layers are forbidden. A deeper walkthrough lives in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
90
90
91
91
```mermaid
92
92
flowchart TD
93
93
specs["specs/<br/>the actual tests"] --> fixtures["fixtures/<br/>mergeTests composition"]
|`pages/`| page objects — verb actions over private locators | no `expect`, no specs imports |
123
+
|`factories/`| Fishery test-data factories | no network, no `Date.now`, no `Math.random`|
124
+
|`fixtures/`| Playwright fixture composition (`mergeTests`) | no business logic |
125
+
|`specs/`| the actual tests (AAA structure) | no inline locators, no raw `fetch`, no `waitForTimeout`|
126
+
|`tools/`| static-analysis validators | run as pre-commit checks and in CI |
124
127
125
128
The validators (`tools/{validate-layout.sh, factory-rules.ts, fixture-rules.ts, lint-ui-spec.ts}`) catch boundary violations at lint time so they don't slip into a PR. See [`tools/README.md`](tools/README.md) for exactly what each one guards.
126
129
@@ -154,8 +157,9 @@ The validators (`tools/{validate-layout.sh, factory-rules.ts, fixture-rules.ts,
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+44-12Lines changed: 44 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,36 +6,42 @@ It is the deep companion to the short contract in [`../CLAUDE.md`](../CLAUDE.md)
6
6
7
7
---
8
8
9
-
## 1. The 5 layers
9
+
## 1. The 6 layers
10
10
11
-
The repository is organised into exactly five layers, pinned in [`../tests-config.json`](../tests-config.json). Imports flow in **one direction only**; a reverse import (e.g. a page object importing a spec) is a build failure, not a code-review nit.
11
+
The repository is organised into six layers, pinned in [`../tests-config.json`](../tests-config.json). Imports flow in **one direction only**; a reverse import (e.g. a page object importing a spec) is a build failure, not a code-review nit.
|`factories/`|`tests/factories`| type-safe synthetic data via Fishery + Faker | touch the network, `Date.now`, `Math.random`, top-level mutables |
38
+
|`fixtures/`|`tests/fixtures`| compose page objects + factories into injected deps | hold business logic |
39
+
|`specs/`|`tests/specs`| the actual tests — Arrange / Act / Assert | inline locators, raw `fetch`/`axios`, `waitForTimeout`|
36
40
37
41
There is **no**`components/`, `api/`, `clients/`, `generated/`, or `data/` layer. Those were removed during the reorganization; this project is black-box UI E2E with no API contract under our control (see [`CONSTRAINTS.md`](CONSTRAINTS.md) §2.1).
38
42
43
+
The `support/` layer depends on page objects only through interfaces (`OfferLandingPage`, `PaymentStep`) it declares itself — Dependency Inversion, so `purchaseVpn` is reusable across the RU and EN sites without knowing their concrete classes.
Site-->>PO: navigation to payment provider boundary
61
-
Spec->>PO: assert payment URL / DOM oracle (STOP — no Pay)
67
+
Spec->>Spec: poll reachedPaymentProvider(url) (STOP — no Pay)
62
68
```
63
69
64
70
Key invariants visible above:
65
71
66
72
-**No `baseURL`.** Three origins are in play, so each page object carries an absolute URL derived from `env.BASE_URL_*`. `goto()` is always explicit.
67
73
-**Determinism.** All synthetic data comes from Faker seeded once in [`../tests/factories/_seed.ts`](../tests/factories/_seed.ts) (`SEED=1234`), so the same email/password is produced every run.
68
-
-**The payment boundary is a hard stop.** Specs assert the provider page is reached and never submit card/crypto data ([`CONSTRAINTS.md`](CONSTRAINTS.md) §2.3). `PaymentRedirectPage` is an oracle only — it deliberately has no card-form locators.
74
+
-**The payment boundary is a hard stop.** Specs poll the pure oracle `reachedPaymentProvider(url, origins)` in [`../tests/support/payment-boundary.ts`](../tests/support/payment-boundary.ts)and never submit card/crypto data ([`CONSTRAINTS.md`](CONSTRAINTS.md) §2.3). The oracle has no card-form interaction by construction.
69
75
70
76
---
71
77
@@ -139,3 +145,29 @@ The skills carry portable copies of the same validators (e.g. `.claude/skills/pl
139
145
### Disabled-by-constraint
140
146
141
147
For this black-box project, [`CONSTRAINTS.md`](CONSTRAINTS.md) §4 disables the OpenAPI-oriented capabilities (`api-client-from-openapi`, `playwright-test-author-api`, `/spec-sync`, `contract-drift-watch`). They remain committed to demonstrate that the workflow _supports_ contract testing when a repo owns a spec — they are simply switched off here.
148
+
149
+
---
150
+
151
+
## 5. Implementation notes (the non-obvious "why")
152
+
153
+
The test code is comment-free; intent is carried by names. The few facts a name cannot
154
+
convey live here, so the knowledge is not lost.
155
+
156
+
-**`BasePage.toggleHiddenControl(locator)`** — the offer/gateway radios and the terms
157
+
checkbox are native inputs that are visually hidden behind custom-styled cards and sit
158
+
outside the layout viewport. A normal `.check()` can't reach them, so the helper sets
159
+
`checked` and dispatches a bubbling `change` event to drive the site's JS state.
160
+
-**`BasePage.fillReactively(locator, value)`** — Site A's `/order/` form (PrimeVue) only
161
+
enables the Next + payment buttons after `input`, `change`, **and**`blur` fire;
162
+
Playwright's native `.fill()` does not reliably emit `change` headless. The helper
163
+
dispatches the full event chain.
164
+
-**`SignupPage.fillEmail` waits for the plan toggle first** — that toggle renders only
165
+
after the Vue form hydrates, so its visibility is a reliable "listeners are attached"
166
+
marker; without the wait, events fire on an un-hydrated input when arriving from `/login/`.
167
+
-**`reachedPaymentProvider(url, origins)`** — the boundary oracle. Success = navigated off
168
+
our origins to a provider (or stayed in-house with a `gateway=` invoice param), and the
169
+
URL is not a `*/failed|error|declined` page. It never inspects card forms.
170
+
-**Known gaps (surfaced as `test.fixme` / omitted on purpose), see [`ux-findings.md`](ux-findings.md):**
171
+
the RU site exposes no crypto gateway on `/payment/`; Site A's email validator is
172
+
permissive (accepts addresses with no `@`), so that negative case is documented as a
0 commit comments