@vijaypjavvadi/bdd2pw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +308 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +176 -0
- package/dist/cli.js.map +1 -0
- package/dist/discovery/mcpClient.d.ts +53 -0
- package/dist/discovery/mcpClient.js +326 -0
- package/dist/discovery/mcpClient.js.map +1 -0
- package/dist/discovery/snapshotParser.d.ts +13 -0
- package/dist/discovery/snapshotParser.js +51 -0
- package/dist/discovery/snapshotParser.js.map +1 -0
- package/dist/emitters/facade.d.ts +52 -0
- package/dist/emitters/facade.js +125 -0
- package/dist/emitters/facade.js.map +1 -0
- package/dist/http/artifacts.d.ts +14 -0
- package/dist/http/artifacts.js +36 -0
- package/dist/http/artifacts.js.map +1 -0
- package/dist/http/jobs.d.ts +26 -0
- package/dist/http/jobs.js +59 -0
- package/dist/http/jobs.js.map +1 -0
- package/dist/http/routes.d.ts +11 -0
- package/dist/http/routes.js +267 -0
- package/dist/http/routes.js.map +1 -0
- package/dist/http/schemas.d.ts +167 -0
- package/dist/http/schemas.js +57 -0
- package/dist/http/schemas.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +507 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/governanceClient.d.ts +24 -0
- package/dist/llm/governanceClient.js +22 -0
- package/dist/llm/governanceClient.js.map +1 -0
- package/dist/llm/providers/anthropic.d.ts +8 -0
- package/dist/llm/providers/anthropic.js +12 -0
- package/dist/llm/providers/anthropic.js.map +1 -0
- package/dist/llm/providers/gemini.d.ts +5 -0
- package/dist/llm/providers/gemini.js +9 -0
- package/dist/llm/providers/gemini.js.map +1 -0
- package/dist/llm/providers/openai.d.ts +5 -0
- package/dist/llm/providers/openai.js +9 -0
- package/dist/llm/providers/openai.js.map +1 -0
- package/dist/parser/gherkinParser.d.ts +17 -0
- package/dist/parser/gherkinParser.js +143 -0
- package/dist/parser/gherkinParser.js.map +1 -0
- package/dist/repo/projectScaffolder.d.ts +24 -0
- package/dist/repo/projectScaffolder.js +64 -0
- package/dist/repo/projectScaffolder.js.map +1 -0
- package/dist/repo/repoScanner.d.ts +21 -0
- package/dist/repo/repoScanner.js +132 -0
- package/dist/repo/repoScanner.js.map +1 -0
- package/dist/reports/reviewReport.d.ts +19 -0
- package/dist/reports/reviewReport.js +96 -0
- package/dist/reports/reviewReport.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +40 -0
- package/dist/server.js.map +1 -0
- package/dist/transformers/locatorPicker.d.ts +40 -0
- package/dist/transformers/locatorPicker.js +283 -0
- package/dist/transformers/locatorPicker.js.map +1 -0
- package/dist/transformers/pomResolver.d.ts +28 -0
- package/dist/transformers/pomResolver.js +35 -0
- package/dist/transformers/pomResolver.js.map +1 -0
- package/dist/transformers/stepMatcher.d.ts +36 -0
- package/dist/transformers/stepMatcher.js +461 -0
- package/dist/transformers/stepMatcher.js.map +1 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +25 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/naming.d.ts +23 -0
- package/dist/utils/naming.js +58 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/validate/tscRunner.d.ts +17 -0
- package/dist/validate/tscRunner.js +98 -0
- package/dist/validate/tscRunner.js.map +1 -0
- package/package.json +69 -0
- package/templates/gitignore.tmpl +10 -0
- package/templates/package.json.tmpl +16 -0
- package/templates/playwright.config.ts.tmpl +24 -0
- package/templates/tsconfig.json.tmpl +15 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# bdd2pw — CHANGELOG
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
_Nothing yet._
|
|
11
|
+
|
|
12
|
+
## [1.0.0] — 2026-05-03
|
|
13
|
+
|
|
14
|
+
First public npm release. Six phases shipped, two real-world fixtures locked,
|
|
15
|
+
101 / 101 tests green. See [`docs/STATUS.md`](./docs/STATUS.md) for the runtime
|
|
16
|
+
snapshot and [`docs/SCOPE.md`](./docs/SCOPE.md) §17 for the full
|
|
17
|
+
implementation-time decisions log.
|
|
18
|
+
|
|
19
|
+
### Headline
|
|
20
|
+
|
|
21
|
+
`feature in → ready-to-run Playwright tests out`. Rule-based
|
|
22
|
+
deterministic generation (no LLM in v1.0). CLI + HTTP service. Same code path
|
|
23
|
+
through both surfaces.
|
|
24
|
+
|
|
25
|
+
### Shipped in v1.0
|
|
26
|
+
|
|
27
|
+
- **Phase 0** — `@vijaypjavvadi/pw-emit@1.0.0` extracted; bdd2pw consumes it as
|
|
28
|
+
a regular dep (`^1.0.0`).
|
|
29
|
+
- **Phase 1a** — Gherkin parser (`@cucumber/gherkin`), repo scanner (ts-morph),
|
|
30
|
+
POM resolver (reuse / augment / create), file-snapshot discovery, locator
|
|
31
|
+
picker (priority chain + status-region special case), 14-rule step matcher,
|
|
32
|
+
emitter facade, end-to-end `scaffold()`. Locked against `practice-test-login`
|
|
33
|
+
(7/7 mapped, 0 warnings).
|
|
34
|
+
- **Phase 1b** — Live browser discovery via direct `playwright.chromium.launch()`
|
|
35
|
+
(chose direct Playwright over `@playwright/mcp` for simpler in-process control).
|
|
36
|
+
Status-region visibility-filter bypass.
|
|
37
|
+
- **Phase 1c** — Second real fixture `cms-login` (third-person dialect),
|
|
38
|
+
0 warnings first try. Subject-agnostic prefix locked.
|
|
39
|
+
- **Phase 2** — `analyze` (dry-run) + `update-pom` (append-only AST surgery via
|
|
40
|
+
ts-morph; hard-fails on missing POM). `BDD_REVIEW.md` writer. `tsc --noEmit`
|
|
41
|
+
validator (soft-fail).
|
|
42
|
+
- **Phase 3** — Express HTTP service `:4300`. Three async workers, ULID job
|
|
43
|
+
IDs, Zod request validation, Pino structured logs, archiver-streamed zip
|
|
44
|
+
artefacts. Per-job tmpdir contains blast radius for HTTP scaffold runs.
|
|
45
|
+
|
|
46
|
+
### CLI
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
bdd2pw scaffold <feature> --url <url> --page <Name> --repo <dir> [opts]
|
|
50
|
+
bdd2pw analyze <feature> --url <url> [opts]
|
|
51
|
+
bdd2pw update-pom --page <Name> --url <url> --repo <dir>
|
|
52
|
+
bdd2pw serve [--port 4300]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### HTTP
|
|
56
|
+
|
|
57
|
+
`POST /scaffold | /analyze | /update-pom` → `202 + { jobId, links }`.
|
|
58
|
+
`GET /jobs/:id`, `GET /jobs/:id/artifact` (zip), `GET /jobs/:id/log`,
|
|
59
|
+
`GET /healthz`, `GET /readyz`, `GET /version`.
|
|
60
|
+
|
|
61
|
+
### Tests
|
|
62
|
+
|
|
63
|
+
101 / 101 across 10 Vitest files. Includes a full HTTP regression that asserts
|
|
64
|
+
the artefact response body starts with the ZIP magic bytes `PK\x03\x04`.
|
|
65
|
+
|
|
66
|
+
### Distribution
|
|
67
|
+
|
|
68
|
+
- npm: `@vijaypjavvadi/bdd2pw@1.0.0` (public, scoped, with provenance).
|
|
69
|
+
- GitHub: tag-triggered `release.yml` workflow auto-publishes on `vX.Y.Z` push.
|
|
70
|
+
- CI matrix: Ubuntu / macOS / Windows × Node 18 / 20 / 22.
|
|
71
|
+
|
|
72
|
+
### Deferred to v1.1+
|
|
73
|
+
|
|
74
|
+
- LLM fallback for vague steps (governance-routed). Real fixtures hit 100%
|
|
75
|
+
rule coverage so this was off the critical path.
|
|
76
|
+
- SQLite-backed job persistence (currently in-memory `Map`).
|
|
77
|
+
- Built-in `auth.setup.ts` generator. Today: bring your own `--storage-state`.
|
|
78
|
+
- Multi-page navigation discovery, `playwright-bdd` runtime mode, API mocking
|
|
79
|
+
scaffolds, visual regression — all post-v1.1.
|
|
80
|
+
|
|
81
|
+
### Earlier-phase notes (kept for history)
|
|
82
|
+
|
|
83
|
+
#### Phase 3 — HTTP service is real
|
|
84
|
+
|
|
85
|
+
The `POST /scaffold`, `POST /analyze`, `POST /update-pom` routes now actually do work. Same code path as the CLI; HTTP is a thin shell.
|
|
86
|
+
|
|
87
|
+
#### What landed
|
|
88
|
+
|
|
89
|
+
- **Workers in `src/http/routes.ts`** — three async `Promise` workers, one per `POST` endpoint. Each accepts the parsed Zod-validated body, calls the matching `scaffold()` / `analyze()` / `updatePom()` function from `src/index.ts`, and updates the in-memory job state through `running` → `completed` / `failed`.
|
|
90
|
+
- **Per-job artifact dir.** `POST /scaffold` overrides the caller-provided `repo` with `<os.tmpdir()>/bdd2pw-jobs/<jobId>/`. This contains the blast radius — the HTTP user can't write outside that sandbox even if they pass an unsafe path.
|
|
91
|
+
- **`streamArtifactZip()` in `src/http/artifacts.ts`** — uses `archiver` to zip the artifact dir on the fly, streaming directly to the response. Excludes `node_modules`, `.git`, `test-results`, `playwright-report`. Headers: `application/zip` + `Content-Disposition: attachment; filename="bdd2pw-<jobId>.zip"`.
|
|
92
|
+
- **`GET /jobs/:id/log`** — returns a plain-text dump of the job's warnings + errors. Real per-job pino log file tailing deferred to v1.1.
|
|
93
|
+
- **`GET /jobs/:id/artifact`** failure modes covered: `404 JobNotFound`, `409 ArtifactNotReady` (job still running or failed), `410 ArtifactExpired` (artifact dir already swept).
|
|
94
|
+
|
|
95
|
+
#### Tests
|
|
96
|
+
|
|
97
|
+
- `tests/e2e/httpWorker.test.ts` — spins up the real Express app on a random port, drives it via `fetch()`, polls for completion. 11 assertions across 4 describe blocks:
|
|
98
|
+
- `/healthz`, `/version`, `/readyz` respond correctly.
|
|
99
|
+
- `POST /scaffold` validates the body and returns 400 for malformed input (missing fields, bad URL).
|
|
100
|
+
- End-to-end: POST `/scaffold` with the practice-test-login fixture → poll `/jobs/:id` → reaches `completed` with no warnings → `GET /jobs/:id/artifact` returns a real zip (verified by the ZIP magic bytes `PK\x03\x04` at the start of the response body).
|
|
101
|
+
- `GET /jobs/:id/log` returns a text/plain dump containing the job ID and status.
|
|
102
|
+
|
|
103
|
+
#### Out of scope for v1.0 (deferred to v1.1+)
|
|
104
|
+
|
|
105
|
+
- **Multi-tenancy / auth** — handled at the gateway (`modern-automation-platform:3000`), bdd2pw HTTP is single-tenant per process. Same pattern as `sel2pw`.
|
|
106
|
+
- **Job queue + worker pool** — current model is one Promise per job, bounded by Node's event loop. SQLite-backed queue with `p-limit` on MCP browsers lands in v1.1.
|
|
107
|
+
- **Granular per-stage progress events** — workers emit `parsing` / `emitting` / `completed`, but not the full 9-stage progress chain from the architecture doc. Sufficient for the UI to show "running" → "done"; finer-grained progress is a polish task.
|
|
108
|
+
- **Real per-job log file** — `GET /jobs/:id/log` currently returns warnings/errors from the in-memory record. Pino multi-stream routing per job is v1.1.
|
|
109
|
+
|
|
110
|
+
### Phase 2 — `updatePom` ships, append-only by construction
|
|
111
|
+
|
|
112
|
+
Replaces the `NotImplementedError` stub. **The third CLI command is now real.** Re-scanning a page that already has a Page Object merges newly-discovered locators in — without ever touching what's already there.
|
|
113
|
+
|
|
114
|
+
#### Design choice: append-only over lock-blocks
|
|
115
|
+
|
|
116
|
+
The original SCOPE proposed `// __BDD2PW_LOCK__` markers around generated content. Switched to **append-only via AST surgery** because:
|
|
117
|
+
|
|
118
|
+
- No visible markers in user code (lock comments are noise in otherwise hand-readable files).
|
|
119
|
+
- Stronger guarantee: enforced by construction, not by comments the user could strip.
|
|
120
|
+
- No special-casing for hand-edits — if it's already there, it's preserved.
|
|
121
|
+
- Smaller blast radius: bugs in updatePom can only ADD wrong things, never destroy existing code.
|
|
122
|
+
|
|
123
|
+
Trade-off: stale fields (when the live page removes an element) are not auto-deleted. Intentional — staleness is recoverable, deletion is not.
|
|
124
|
+
|
|
125
|
+
#### Hard rules `updatePom` enforces
|
|
126
|
+
|
|
127
|
+
1. Never deletes any property, method, or import.
|
|
128
|
+
2. Never renames anything.
|
|
129
|
+
3. Never modifies any existing method body — hand-edits are preserved byte-identical.
|
|
130
|
+
4. Skips field-name collisions (existing field wins).
|
|
131
|
+
5. Method synthesis is opt-in and deferred to v1.1.
|
|
132
|
+
6. Hard-fails if the POM file doesn't exist (suggests `scaffold` first).
|
|
133
|
+
7. Hard-fails if the class name doesn't match `--page`.
|
|
134
|
+
|
|
135
|
+
#### Implementation
|
|
136
|
+
|
|
137
|
+
- **`updatePom()` in `src/index.ts`** — replaces the stub. Uses `ts-morph` to read the existing POM AST, computes the new-fields diff against the live scan, and uses `addProperty` + `addStatements` to splice new content into the existing class. Then `save()` writes back. No re-emit, no overwrite.
|
|
138
|
+
- **`renderLocatorExpr` imported from `@vijaypjavvadi/pw-emit`** so the appended constructor lines use the same locator format as `scaffold` would.
|
|
139
|
+
- Honours `--no-discovery` and `--snapshot-file` flags for offline/CI use, mirroring `scaffold`.
|
|
140
|
+
- Surfaces a `BDD_REVIEW.md` with diff stats: existing/discovered/new/skipped-collisions counts.
|
|
141
|
+
|
|
142
|
+
#### Tests
|
|
143
|
+
|
|
144
|
+
- `tests/e2e/updatePom.test.ts` — 11 assertions across 3 describe blocks. The critical cases:
|
|
145
|
+
- **Hand-edits survive byte-identical** (the must-not-regress invariant): hand-written comments, `console.log` lines, and entire method bodies stay untouched.
|
|
146
|
+
- **Custom helper methods survive** — anything outside the discovered IR is preserved.
|
|
147
|
+
- **Existing field collisions are skipped** — if the user already declared `usernameInput` with a `#username` selector, updatePom does NOT overwrite with whatever the live snapshot would pick.
|
|
148
|
+
- **Idempotent** — second invocation in a row adds zero fields.
|
|
149
|
+
- **Hard-fail on missing class** — points at `scaffold`.
|
|
150
|
+
|
|
151
|
+
### Phase 1c — second real-world fixture clean (selenium14/LoginCMS)
|
|
152
|
+
|
|
153
|
+
**Validated against `selenium14/AutomationFrameworkCucumberTestNG/src/test/resources/features/LoginCMS.feature` — third-party Cucumber dialect from a real OSS Selenium codebase, against `https://cms.anhtester.com/login`.** 0 warnings, 0 errors, every step mapped.
|
|
154
|
+
|
|
155
|
+
This fixture exercises:
|
|
156
|
+
- `User navigate to Login Page for Admin "URL"` — third-person subject + bare-text + quoted URL → `goto()`
|
|
157
|
+
- `user enter email "admin@example.com" password "123456"` — **compound input step** (one Gherkin step, two field/value pairs) → two `.fill()` lines emitted
|
|
158
|
+
- `click Login button` — no subject prefix → `loginButton.click()`
|
|
159
|
+
- `user is redirected to the Dashboard page` — third-person `is redirected to` → `toHaveURL(/Dashboard/)`
|
|
160
|
+
|
|
161
|
+
#### Bugs fixed this round
|
|
162
|
+
|
|
163
|
+
- **Step matcher: subject-agnostic rules.** All 13 rules now accept `I | user | User | the user | the User` as the subject prefix (was hard-coded `I`). Verb forms also accept singular AND plural conjugation (`click | clicks`, `enter | enters`, etc.). For rule 3 (click) the subject is now optional entirely (`click Login button` works without any subject).
|
|
164
|
+
- **Rule 1 (navigate): handles `navigate to <text> "<URL>"`.** Previous pattern only accepted `navigate to <target>`. New pattern: `(?:.+?\s+)?["']?<target>["']?` — the optional `<text> ` prefix is consumed, the quoted URL becomes the target.
|
|
165
|
+
- **Rule 11 (redirect): accepts `is redirected`.** Was `(should be |am )?redirected`; now `(should be |am |is )?redirected`.
|
|
166
|
+
- **Compound input rule (2c).** New rule for `SUBJ verb FIELD1 "V1" FIELD2 "V2" [...]` — extracts every (field, value) pair via a global regex pass and emits one `.fill()` per pair. Falls back to `null` (TODO) if any field doesn't resolve, so partial matches don't produce wrong code.
|
|
167
|
+
|
|
168
|
+
#### Type addition (small)
|
|
169
|
+
|
|
170
|
+
- **`StepBinding.customBody?: string`** — for compound steps that produce multiple TS statements per Gherkin step. The emitter writes `customBody` verbatim instead of synthesising a single line from `pomCall`/`assertion`. Existing rules unchanged; only the new compound rule uses this path.
|
|
171
|
+
|
|
172
|
+
#### Orchestrator bug fix
|
|
173
|
+
|
|
174
|
+
- **Background warnings now flow into `BDD_REVIEW.md`.** Previously only scenario-step warnings were collected; Background-step warnings were silently dropped. Now surfaced under `[Background]`.
|
|
175
|
+
|
|
176
|
+
### Phase 1b milestone — live discovery 7/7 against the real site
|
|
177
|
+
|
|
178
|
+
**Same `practice-test-login` feature that needed a hand-authored snapshot in Phase 1a now passes 7/7 with `--url` only — bdd2pw scans the live page itself.** No snapshot file, no hand-tuning, real Chromium against `https://practicetestautomation.com/practice-test-login/`.
|
|
179
|
+
|
|
180
|
+
#### Iteration arc (live-scan rounds)
|
|
181
|
+
|
|
182
|
+
| Round | Pass / Fail | Root cause + fix |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| 1 | 4 / 3 | Scanner missed `<div id="error">` (no role/testid/aria-label) → broaden selector list to include `[id*=error i]`, `[class*=alert i]`, etc. |
|
|
185
|
+
| 2 | 3 / 4 | Scanner picked up error div but field name was text-derived (`yourUsernameIsInvalid`) → step matcher's `findField("error", [...])` couldn't match it. Fix: in `synthFieldName`, when CSS id matches `STATUS_REGION_RE`, prefer id-derived name → field becomes `error`. Plus `isLikelyStatusRegion` bypasses the visibility filter (status banners are intentionally hidden until triggered). Plus implicit roles for `<button>`/`<a>`/`<input type=submit>` so picker emits `getByRole`. Plus dedup-by-locator-value (collapse `username` + `usernameInput` into one). |
|
|
186
|
+
| 3 | 3 / 4 | Field name was right (`error`) but locator was wrong: picker emitted `getByText("Your username is invalid!")` not `locator("#error")`. Volatile text content was winning over the stable id. Fix: locator picker skips the `getByText` branch when element matches `STATUS_REGION_RE` — text changes per scenario, id is the stable handle. |
|
|
187
|
+
| 4 | **7 / 0** | — |
|
|
188
|
+
|
|
189
|
+
#### Bugs fixed this round
|
|
190
|
+
|
|
191
|
+
- **`src/discovery/mcpClient.ts`:** broader selector list (`[id*=error]`, `[class*=alert]`, …); `implicitRole()` for native HTML form controls; `isLikelyStatusRegion()` visibility-filter bypass; `isVisible()` zero-size + display:none filter; tsconfig `/// <reference lib="dom" />` so the `page.evaluate()` callback type-checks.
|
|
192
|
+
- **`src/transformers/locatorPicker.ts`:** new `STATUS_REGION_RE`; `synthFieldName` priority 0 = id-derived for status regions (so `<div id="error">` → `error`, not `yourUsernameIsInvalid`); `getByText` branch skipped for status regions; `dedupeLocators` now collapses by `(api, args)` identity, keeping the longer field name.
|
|
193
|
+
|
|
194
|
+
#### What this proves
|
|
195
|
+
|
|
196
|
+
Three of the original Phase 1a iteration rounds were **snapshot accuracy** bugs (fictional ARIA roles, fictional placeholders). With Phase 1b they're gone — the scanner sees what's actually there. The four Phase 1b rounds were rule/picker bugs surfaced by real DOM data, all fixed. Net result: a **single CLI invocation** with `--url` produces a runnable spec from a Cucumber `.feature` file with zero hand-edits, zero snapshot authoring.
|
|
197
|
+
|
|
198
|
+
### Phase 1b — real browser-based page discovery (initial implementation, earlier this round)
|
|
199
|
+
|
|
200
|
+
Replaces the file-snapshot-only discovery with a real Chromium-launching scanner. Hand-authoring `snapshot.json` is no longer required for the common case; pass `--url` and bdd2pw scans the live page itself.
|
|
201
|
+
|
|
202
|
+
#### What landed
|
|
203
|
+
|
|
204
|
+
- **`scanPageWithBrowser()`** in `src/discovery/mcpClient.ts` — uses `playwright.chromium.launch()` directly. Walks the DOM with a curated selector list (inputs, buttons, links, selects, headings, anything with `[role]` / `[data-testid]` / `[aria-label]`), extracts per-element role / accessible name / label / placeholder / testId / text / best CSS selector, and returns a flat `ElementIR[]`-shaped list. Snapshot parser handles it without changes.
|
|
205
|
+
- **`playwright`** added as an `optionalDependencies` entry. Dynamic-imported in the scanner with a clear install-hint error if missing (`"npm install -D playwright && npx playwright install chromium"`).
|
|
206
|
+
- **Storage state support** — pass `--storage-state <path.json>` to scan an authenticated page; passed through to `browser.newContext({ storageState })`.
|
|
207
|
+
- **Headed mode** — `--headed` shows the browser window, useful for debugging snapshot accuracy.
|
|
208
|
+
- **Network-idle wait with fallback** — first attempt waits 5s for network idle (catches SPAs that finish loading lazily); falls back to `domcontentloaded` if idle never settles.
|
|
209
|
+
|
|
210
|
+
#### Removed framing
|
|
211
|
+
|
|
212
|
+
- Dropped `@playwright/mcp` from optional dependencies. **AQ-2 resolved:** MCP is an LLM-control protocol, not a programmatic browser API. We just need one URL → one snapshot, so direct `playwright` is one fewer process and one fewer protocol layer. The file `src/discovery/mcpClient.ts` keeps its name for now (rename is cosmetic, deferred).
|
|
213
|
+
|
|
214
|
+
#### Backward compatibility
|
|
215
|
+
|
|
216
|
+
- `--snapshot-file <path>` still works exactly as before. The orchestrator dispatches: snapshot file present → file path; otherwise → real browser. CI runs that don't want network access keep using pinned snapshot files (e.g. `examples/practice-test-login/snapshot.json`).
|
|
217
|
+
- `practiceTestLogin.test.ts` still uses the file-snapshot path so the regression test runs without network.
|
|
218
|
+
|
|
219
|
+
#### What this fixes
|
|
220
|
+
|
|
221
|
+
The 3-of-6 iteration rounds in Phase 1a milestone that were "snapshot accuracy" bugs (fictional ARIA roles, fictional placeholders, etc.) — those scenarios should now Just Work because the scanner sees the actual DOM.
|
|
222
|
+
|
|
223
|
+
### Phase 1a milestone — first end-to-end green run (earlier this round)
|
|
224
|
+
|
|
225
|
+
**The full rule-based pipeline produced a runnable Playwright spec from a real-world Cucumber `.feature` file, with zero hand-edits.** Validated against `https://practicetestautomation.com/practice-test-login/` — `npx playwright test --project=chromium` reports **7 passed (4.2s)**.
|
|
226
|
+
|
|
227
|
+
#### Scope
|
|
228
|
+
|
|
229
|
+
- Source feature: `examples/practice-test-login/login.feature` — 5 scenarios + 1 Scenario Outline (2 example rows) + Background = 7 generated `test()` blocks
|
|
230
|
+
- Discovery source: hand-authored `examples/practice-test-login/snapshot.json` (Phase 1a fallback; real `@playwright/mcp` lands in Phase 1b)
|
|
231
|
+
- Output target: `pages/login.page.ts`, `tests/login.spec.ts`, `playwright.config.ts`, `package.json`, `tsconfig.json`, `.gitignore`, `BDD_REVIEW.md`
|
|
232
|
+
|
|
233
|
+
#### Iteration arc that got us there
|
|
234
|
+
|
|
235
|
+
| Round | Pass / Fail | Root cause |
|
|
236
|
+
|---|---|---|
|
|
237
|
+
| 1 | 0 / 7 | Undeclared `loginPage` (facade emitted POM calls without instantiation) |
|
|
238
|
+
| 2 | 4 / 3 | Rule 3 lost role hint; rule 8 required "the" not "an"; word-order mismatch in rule 2 |
|
|
239
|
+
| 3 | 2 / 5 | Snapshot fictional ARIA roles (`role: "alert"` not on page) |
|
|
240
|
+
| 4 | 1 / 6 | Snapshot fictional placeholders (`placeholder: "Username"` not on page) |
|
|
241
|
+
| 5 | 6 / 1 | Rule 9 emitted `toHaveText` (exact); step said "containing" |
|
|
242
|
+
| 6 | **7 / 0** | (none) |
|
|
243
|
+
|
|
244
|
+
3 of 6 rounds were rule/code bugs. 3 were snapshot accuracy — exactly what real `@playwright/mcp` integration will eliminate (Phase 1b).
|
|
245
|
+
|
|
246
|
+
#### Bugs fixed this round (rule + code)
|
|
247
|
+
|
|
248
|
+
- **`emitTestFile` facade (`src/emitters/facade.ts`):** prepend `const <pageVar> = new <Class>(page);` to every test body and every hook body. Previously the spec emitted `await loginPage.goto()` without ever declaring `loginPage`.
|
|
249
|
+
- **`scaffold()` orchestrator (`src/index.ts`):** synthesise a `goto(): Promise<void>` POM method that calls `await this.page.goto(<url>)` when no goto exists. Without it, rule 1 emitted `await loginPage.goto()` against a method with no body.
|
|
250
|
+
- **POM file naming (`src/index.ts`):** use `pageObjectFileName` from `@vijaypjavvadi/pw-emit` instead of `${camelCase(opts.page)}.page.ts`. Was emitting `loginPage.page.ts`; now emits `login.page.ts`.
|
|
251
|
+
- **Reserved field-name filter (`src/index.ts`):** `page`, `context`, `browser` are now excluded from the "missing fields" set. Previously `loginPage.page.toHaveURL(...)` made `page` look like a missing field.
|
|
252
|
+
- **Step matcher rule 2b (`src/transformers/stepMatcher.ts`):** new — handles reversed word order `I enter <field> "<value>"`. Original rule expected `I enter "<value>" into <field>`.
|
|
253
|
+
- **Step matcher rule 3:** captures the explicit role suffix (`button` / `link` / `icon` / `tab`) and passes only that to `findField` so "click the login button" doesn't match `testLoginHeading`.
|
|
254
|
+
- **Step matcher rule 8:** accepts `the | an | a error message`, prefers `Alert > Error > Message`-suffixed fields.
|
|
255
|
+
- **Step matcher rule 9a:** new — `toContainText` semantics for `containing | that contains | with text`. Must run before rule 9b (exact text equality) which now also accepts `the | an | a`.
|
|
256
|
+
- **Step matcher rules 11–13:** new — redirect (`toHaveURL` regex), attribute check (`toHaveAttribute`), URL prefix/contains (`toHaveURL` anchored regex).
|
|
257
|
+
- **`findField` resolution order (`src/transformers/stepMatcher.ts`):** added a suffix-constrained pass — when preferredSuffixes are provided, only consider fields with those suffixes; if exactly one matches, use it. Stops "login" from matching `testLoginHeading` when there's a `submitButton` on the same page.
|
|
258
|
+
- **`synthFieldName` (`src/transformers/locatorPicker.ts`):** added `cssSelectorToName()` fallback. When no name/label/placeholder/text is available, derive the field name from the CSS id (`#username` → `usernameInput`) instead of the ugly `inputElement` placeholder.
|
|
259
|
+
|
|
260
|
+
#### Added
|
|
261
|
+
|
|
262
|
+
- `examples/practice-test-login/` — pinned regression fixture (login.feature + snapshot.json + README)
|
|
263
|
+
- `tests/e2e/practiceTestLogin.test.ts` — 22-assertion regression test for the full pipeline. Calls `scaffold()`, reads back POM + spec, asserts every binding pattern that should have landed.
|
|
264
|
+
- `vitest.config.ts` — `hookTimeout: 30_000` for E2E test cold-start reads.
|
|
265
|
+
|
|
266
|
+
### Added — Phase 1a (initial implementation, earlier this round)
|
|
267
|
+
- **Real Gherkin parser** — `src/parser/gherkinParser.ts`. Uses `@cucumber/gherkin`. Handles Background, Scenario, Scenario Outline + Examples, doc strings, data tables.
|
|
268
|
+
- **Step matcher rules** — `src/transformers/stepMatcher.ts`. 10 rules covering navigation, input, click, select, check/uncheck, visibility (positive + negative), text equality, URL, error message. Falls through to a warning when no rule matches.
|
|
269
|
+
- **Locator picker** — `src/transformers/locatorPicker.ts`. Priority chain `getByRole > getByLabel > getByPlaceholder > getByTestId > getByText > css > xpath`. Ambiguity flagged when rivals exist. Field-name synthesis with role-based suffixes (`usernameInput`, `signInButton`).
|
|
270
|
+
- **POM resolver** — `src/transformers/pomResolver.ts`. Pure decision: REUSE / AUGMENT / CREATE based on existing fields vs referenced fields.
|
|
271
|
+
- **Repo scanner** — `src/repo/repoScanner.ts`. `ts-morph`-based AST scan of `pages/*.page.ts`. Extracts class name, locator fields, methods (preserves bodies for AUGMENT mode).
|
|
272
|
+
- **Project scaffolder** — `src/repo/projectScaffolder.ts`. Delegates to `@vijaypjavvadi/pw-emit`'s `emitProject()`. Idempotent.
|
|
273
|
+
- **Page discovery (file-snapshot fallback)** — `src/discovery/mcpClient.ts`. Phase 1a reads a pre-captured JSON snapshot via `--snapshot-file`. Real `@playwright/mcp` integration scheduled for Phase 1b.
|
|
274
|
+
- **Snapshot parser** — `src/discovery/snapshotParser.ts`. Accepts both flat-array and tree-shaped snapshots; flattens to `ElementIR[]`.
|
|
275
|
+
- **Review report** — `src/reports/reviewReport.ts`. Writes `BDD_REVIEW.md` with severity sections (Errors / Warnings / Info).
|
|
276
|
+
- **TSC validator** — `src/validate/tscRunner.ts`. Runs `tsc --noEmit` in the target repo, parses diagnostics into `ReviewItem`s. Soft-fails with a warning if `tsc` isn't installed.
|
|
277
|
+
- **`scaffold()` end-to-end wired** — `src/index.ts`. Orchestrates: parse → scan repo → scaffold → discover → pick locators → match steps (provisional) → resolve POM → re-match against final POM → expand Scenario Outlines → emit POM + spec → tsc validate → review report.
|
|
278
|
+
- **`analyze()` working** — same pipeline minus emit/validate. Returns the plan.
|
|
279
|
+
- **CLI `--snapshot-file` flag** for the file-based discovery path.
|
|
280
|
+
- **Example fixture** `examples/login-feature/snapshot.json` — hand-authored a11y tree for end-to-end testing.
|
|
281
|
+
- **3 new unit test files**: `stepMatcher.test.ts`, `locatorPicker.test.ts`, `pomResolver.test.ts`.
|
|
282
|
+
|
|
283
|
+
### Still stubbed (next phases)
|
|
284
|
+
- `updatePom()` — needs lock-block enforcement (Phase 2)
|
|
285
|
+
- Real Playwright MCP integration (Phase 1b)
|
|
286
|
+
- HTTP worker that calls `scaffold()` (Phase 3)
|
|
287
|
+
- LLM fallback in stepMatcher (Phase 4)
|
|
288
|
+
|
|
289
|
+
### Added — Phase 0 (previous round)
|
|
290
|
+
- Repository scaffold: package.json, tsconfig, ESLint, Prettier, Vitest, TypeDoc, MIT LICENSE
|
|
291
|
+
- CI workflow (Node 18/20/22 × Ubuntu/macOS/Windows), Release workflow (tag-triggered npm publish with provenance)
|
|
292
|
+
- `.changeset` config for versioning
|
|
293
|
+
- `src/` skeleton: `cli.ts`, `server.ts`, `index.ts`, `types.ts`, `utils/{naming,logger}.ts`
|
|
294
|
+
- Module stubs (now most replaced by Phase 1a implementations)
|
|
295
|
+
- Project templates: `package.json.tmpl`, `playwright.config.ts.tmpl`, `tsconfig.json.tmpl`, `gitignore.tmpl`
|
|
296
|
+
- Example fixture: `examples/login-feature/login.feature`
|
|
297
|
+
- First passing unit test: `tests/unit/naming.test.ts`
|
|
298
|
+
- `@vijaypjavvadi/pw-emit` consumed via `file:../pw-emit`
|
|
299
|
+
- `src/emitters/facade.ts` rewritten to delegate to `pw-emit`
|
|
300
|
+
|
|
301
|
+
### Documents
|
|
302
|
+
- `README.md` — project orientation, decisions log, phasing
|
|
303
|
+
- `docs/SCOPE.md` — v1.0 scope, FR/NFR, CLI + HTTP surface
|
|
304
|
+
- `docs/ARCHITECTURE.md` — component diagram, sequence flows, contracts, error model
|
|
305
|
+
|
|
306
|
+
## [0.1.0] — TBD
|
|
307
|
+
|
|
308
|
+
Initial published placeholder. **No runtime functionality yet.** Phase 0 (extracting `@vijaypjavvadi/pw-emit` from `sel2pw`) blocks Phase 1 of this package.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vijay P. Javvadi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# bdd2pw — Gherkin `.feature` → Playwright TypeScript scaffold
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
[](./tests)
|
|
6
|
+
[](./examples)
|
|
7
|
+
|
|
8
|
+
Most teams writing BDD test cases (via Cucumber `.feature` files, possibly authored by an LLM) face the same gap: the `.feature` describes the test, but **someone still has to write the Page Objects and the Playwright spec by hand**. That's the work `bdd2pw` does for you.
|
|
9
|
+
|
|
10
|
+
Point it at a `.feature` file and a URL. It parses the Gherkin, scans the live page with Chromium, picks the most stable locator for every interactive element, matches each step to a POM method call, and emits a runnable Playwright TypeScript repo — including `playwright.config.ts`, `pages/login.page.ts`, `tests/login.spec.ts`, and a `BDD_REVIEW.md` listing anything a human still needs to look at.
|
|
11
|
+
|
|
12
|
+
## What `bdd2pw` is and isn't
|
|
13
|
+
|
|
14
|
+
`bdd2pw` is a **scaffolder**, not an oracle. The output is honest about its limits:
|
|
15
|
+
|
|
16
|
+
- **Specific `.feature` files** — quoted credentials, concrete assertion targets, standard verb forms — convert to **runnable specs with zero hand-edits**. Validated end-to-end against a real public site (`https://practicetestautomation.com/practice-test-login/`): 7/7 scenarios green.
|
|
17
|
+
- **Vague `.feature` files** — "Enter valid username and password and click login button" with no quoted values — get partial coverage. Steps that can't be unambiguously mapped land as `// TODO` comments in the spec, and warnings in `BDD_REVIEW.md`. The user finishes the 1-3 ambiguous steps; the locators, POM scaffold, project config, and Background `goto()` are already done.
|
|
18
|
+
- **No LLM in the v1.0 hot path.** The 14-rule matcher covers three Cucumber dialects (first-person, third-person, no-subject) plus compound input steps. Genuinely vague steps are inherently ambiguous and get `// TODO` — better than an LLM guessing.
|
|
19
|
+
- **`updatePom` is append-only by construction.** Re-scanning a page that already has a Page Object adds new locators only. Hand-edited method bodies, custom helper methods, custom imports are all preserved byte-identical. Never deletes, never renames, never modifies.
|
|
20
|
+
|
|
21
|
+
> **The pitch.** Run `bdd2pw scaffold` and you get a Playwright TS repo where `npx playwright test` runs against the real site. For specific fixtures, all green. For vague ones, the 60-90% you'd otherwise hand-write is done; you finish the rest.
|
|
22
|
+
|
|
23
|
+
## Where this fits in the platform
|
|
24
|
+
|
|
25
|
+
| Stage | Service | Role |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| 1. Generate | `test-case-generation-service` (FastAPI :4100) | Author `.feature` files from requirements |
|
|
28
|
+
| **1.5. Scaffold** | **`bdd2pw` (this repo) — Express :4300 + CLI** | `.feature` + URL → POM + spec, ready to run |
|
|
29
|
+
| 2. Migrate | [`@vijaypjavvadi/sel2pw`](https://github.com/javvadivijayprasad/sel2pw) (Express :4200) | Legacy Selenium → Playwright |
|
|
30
|
+
| 3. Stabilise | `self-healing-stage-services` (FastAPI :8003) | Heal locators at runtime |
|
|
31
|
+
| 4. Govern | `ai-governance` (Python lib + sidecar :8004) | Sanitise every LLM payload (when v1.1 LLM fallback ships) |
|
|
32
|
+
| Orchestrate | `modern-automation-platform` (Express :3000) | Auth, jobs, UI, artifacts |
|
|
33
|
+
|
|
34
|
+
`bdd2pw` and `sel2pw` are siblings. Both consume **`@vijaypjavvadi/pw-emit`** (the shared emitter library), so the Page Objects produced by one are byte-identical in shape to those produced by the other.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -D @vijaypjavvadi/bdd2pw
|
|
40
|
+
npx playwright install chromium # browser for live page discovery
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Node ≥ 18. Cross-platform (Linux / macOS / Windows). `playwright` is an optional dep — if you don't install browsers, pass `--snapshot-file <path.json>` instead.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Scaffold a runnable Playwright TS project from a .feature file
|
|
49
|
+
npx bdd2pw scaffold path/to/login.feature \
|
|
50
|
+
--url https://your-app.example.com/login \
|
|
51
|
+
--page LoginPage \
|
|
52
|
+
--repo ./my-tests
|
|
53
|
+
|
|
54
|
+
cd ./my-tests
|
|
55
|
+
npm install
|
|
56
|
+
npx playwright test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That's it. `./my-tests` now has `pages/login.page.ts`, `tests/login.spec.ts`, `playwright.config.ts`, `package.json`, `tsconfig.json`, `.gitignore`, and `BDD_REVIEW.md`.
|
|
60
|
+
|
|
61
|
+
## CLI surface — three commands
|
|
62
|
+
|
|
63
|
+
### `scaffold` — generate a fresh repo
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bdd2pw scaffold <feature> --url <url> --page <Name> --repo <dir> [options]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Reads the `.feature`, scans the URL with Chromium, picks locators, matches steps, emits the project. Flags:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
--snapshot-file <path> Use a captured JSON snapshot instead of launching a browser
|
|
73
|
+
--no-discovery Skip page discovery entirely (rule-only probing)
|
|
74
|
+
--storage-state <path> Pre-authenticated storage state JSON
|
|
75
|
+
--headed Show browser during scan
|
|
76
|
+
--templates <dir> Override default project templates
|
|
77
|
+
--dry-run Print plan, write nothing
|
|
78
|
+
--no-validate Skip tsc --noEmit step
|
|
79
|
+
--force Overwrite existing spec files
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `analyze` — dry-run the matcher
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bdd2pw analyze <feature> --url <url> [options]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Same pipeline but writes nothing — prints the parsed feature, the discovered locators, and the planned step bindings as JSON. Useful for "what would scaffold do?" probing.
|
|
89
|
+
|
|
90
|
+
### `update-pom` — merge new locators into an existing POM
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bdd2pw update-pom --page <Name> --url <url> --repo <dir> [options]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Re-scan the page and **append** any newly-discovered locators to the existing Page Object. Never touches existing fields, methods, or specs. Hand-edited method bodies survive byte-identical.
|
|
97
|
+
|
|
98
|
+
## HTTP service
|
|
99
|
+
|
|
100
|
+
`bdd2pw serve` starts an Express service on `:4300` with the same three commands as REST endpoints. Mirror of the CLI; HTTP is a thin shell.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
bdd2pw serve --port 4300
|
|
104
|
+
|
|
105
|
+
# In another terminal:
|
|
106
|
+
curl -X POST http://localhost:4300/scaffold \
|
|
107
|
+
-H 'content-type: application/json' \
|
|
108
|
+
-d '{"feature":"/abs/path/to/login.feature","url":"https://...","page":"LoginPage","repo":"unused","options":{"snapshotFile":"/abs/snapshot.json","noValidate":true}}'
|
|
109
|
+
# → 202 Accepted: {"jobId":"01HX...","links":{"self":"/jobs/01HX...","artifact":"/jobs/01HX.../artifact"}}
|
|
110
|
+
|
|
111
|
+
curl http://localhost:4300/jobs/01HX... # poll for completion
|
|
112
|
+
curl -O http://localhost:4300/jobs/01HX.../artifact # download zip
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Endpoint | Purpose |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `GET /healthz`, `/version`, `/readyz` | Liveness probes |
|
|
118
|
+
| `POST /scaffold`, `/analyze`, `/update-pom` | Async job — returns `202` + `jobId` |
|
|
119
|
+
| `GET /jobs/:id` | Job status / progress / result |
|
|
120
|
+
| `GET /jobs/:id/artifact` | Stream zip of the scaffolded repo |
|
|
121
|
+
| `GET /jobs/:id/log` | Plain-text dump of warnings + errors |
|
|
122
|
+
|
|
123
|
+
In production, the platform gateway proxies `/api/v1/scaffold/*` to `:4300`. Direct port exposure is local-dev only.
|
|
124
|
+
|
|
125
|
+
## Pipeline
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
.feature file
|
|
129
|
+
│
|
|
130
|
+
▼
|
|
131
|
+
┌──────────────┐
|
|
132
|
+
│ gherkinParser│ @cucumber/gherkin → FeatureIR (scenarios, background, examples)
|
|
133
|
+
└──────┬───────┘
|
|
134
|
+
▼
|
|
135
|
+
┌──────────────┐
|
|
136
|
+
│ repoScanner │ ts-morph → existing pages/*.page.ts → Map<class, IR>
|
|
137
|
+
└──────┬───────┘
|
|
138
|
+
▼
|
|
139
|
+
┌──────────────┐
|
|
140
|
+
│ pomResolver │ CREATE / AUGMENT / REUSE decision
|
|
141
|
+
└──────┬───────┘
|
|
142
|
+
▼
|
|
143
|
+
┌──────────────┐
|
|
144
|
+
│ scanPage │ playwright chromium → DOM walk → ElementIR[]
|
|
145
|
+
│ │ (or --snapshot-file / --no-discovery fallbacks)
|
|
146
|
+
└──────┬───────┘
|
|
147
|
+
▼
|
|
148
|
+
┌──────────────┐
|
|
149
|
+
│ locatorPicker│ getByRole > getByLabel > getByPlaceholder > getByTestId
|
|
150
|
+
│ │ > getByText > css > xpath
|
|
151
|
+
│ │ Status-region special case + dedup by (api, args)
|
|
152
|
+
└──────┬───────┘
|
|
153
|
+
▼
|
|
154
|
+
┌──────────────┐
|
|
155
|
+
│ stepMatcher │ 14 rules — subject-agnostic, dialect-tolerant,
|
|
156
|
+
│ │ compound-input via customBody
|
|
157
|
+
└──────┬───────┘
|
|
158
|
+
▼
|
|
159
|
+
┌──────────────┐
|
|
160
|
+
│ pw-emit │ @vijaypjavvadi/pw-emit → POM TS + spec TS + project scaffold
|
|
161
|
+
└──────┬───────┘
|
|
162
|
+
▼
|
|
163
|
+
┌──────────────┐
|
|
164
|
+
│ tscRunner │ Optional tsc --noEmit → diagnostics into BDD_REVIEW.md
|
|
165
|
+
└──────┬───────┘
|
|
166
|
+
▼
|
|
167
|
+
output/
|
|
168
|
+
pages/<name>.page.ts
|
|
169
|
+
tests/<name>.spec.ts
|
|
170
|
+
playwright.config.ts
|
|
171
|
+
package.json, tsconfig.json, .gitignore
|
|
172
|
+
BDD_REVIEW.md ← warnings + manual TODOs
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## What `bdd2pw` covers
|
|
176
|
+
|
|
177
|
+
### Step matcher rules (14)
|
|
178
|
+
|
|
179
|
+
| # | Pattern | Emits |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| 1 | `(I/user) am on/navigate to/visit/open <X>` (with or without quoted URL) | `goto()` |
|
|
182
|
+
| 2a | `(I/user) enter "<value>" into <field>` | `<field>.fill("<value>")` |
|
|
183
|
+
| 2b | `(I/user) enter <field> "<value>"` | `<field>.fill("<value>")` |
|
|
184
|
+
| 2c | **Compound:** `(I/user) enter <f1> "<v1>" <f2> "<v2>" ...` | N `.fill()` lines |
|
|
185
|
+
| 3 | `(I/user/-) click/press/tap <X> button/link/icon/tab` | `<field>.click()` (or POM method if defined) |
|
|
186
|
+
| 4 | `(I/user) select "<opt>" from <dropdown>` | `<field>.selectOption(...)` |
|
|
187
|
+
| 5 | `(I/user) check/uncheck <checkbox>` | `<field>.check()` / `.uncheck()` |
|
|
188
|
+
| 6 | `(I/user) (should) see <X>` | `expect(<X>).toBeVisible()` |
|
|
189
|
+
| 7 | `(I/user) should not see <X>` | `expect(<X>).toBeHidden()` |
|
|
190
|
+
| 8 | `(I/user) should see (the/an/a) error message "<X>"` | `expect(<errorField>).toContainText("<X>")` |
|
|
191
|
+
| 9a | `(I/user) should see <X> containing/with text "<Y>"` | `expect(<X>).toContainText("<Y>")` |
|
|
192
|
+
| 9b | `(I/user) should see <X> "<Y>"` (exact equality) | `expect(<X>).toHaveText("<Y>")` |
|
|
193
|
+
| 10 | `(I/user) should remain on <page>` | `expect(page).toHaveURL(/<page>/)` |
|
|
194
|
+
| 11 | `(I/user) (should be/is) redirected to <page>` | `expect(page).toHaveURL(/<page>/)` |
|
|
195
|
+
| 12 | `the <field> field should be of type "<type>"` | `expect(<field>).toHaveAttribute("type", "<type>")` |
|
|
196
|
+
| 13 | `the (current) URL should start with/contain "<X>"` | `expect(page).toHaveURL(/^<X>/)` |
|
|
197
|
+
|
|
198
|
+
### Cucumber features
|
|
199
|
+
|
|
200
|
+
- `Background:` → `test.beforeEach`
|
|
201
|
+
- `Scenario Outline:` + `Examples:` → one `test()` per row, with placeholder substitution
|
|
202
|
+
- `Tags` → emitted as `// @tag` comments above each test
|
|
203
|
+
- Doc strings + data tables — preserved on `StepIR.argument`
|
|
204
|
+
|
|
205
|
+
### Locator priority
|
|
206
|
+
|
|
207
|
+
`getByRole` (with accessible name) > `getByLabel` > `getByPlaceholder` > `getByTestId` > `getByText` > CSS > xpath. **Status regions** (`<div id="error">`, `[role=alert]`, `[class*=notification]`) bypass the visibility filter (they're hidden until triggered) and use their `id` for both field name and locator (text content is volatile, id is stable).
|
|
208
|
+
|
|
209
|
+
## Verification
|
|
210
|
+
|
|
211
|
+
| Check | Status |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `npm run lint` | ✅ clean |
|
|
214
|
+
| `tsc --noEmit` | ✅ clean |
|
|
215
|
+
| `npm test` (vitest) | ✅ **101/101 green** across 10 test files |
|
|
216
|
+
| `practice-test-login` end-to-end vs the live site | ✅ **7/7** scenarios green via `npx playwright test` |
|
|
217
|
+
| `cms-login` (selenium14 dialect) regression | ✅ 12 assertions green, third-person + compound input + `is redirected` |
|
|
218
|
+
| HTTP worker zip download | ✅ end-to-end test verifies real ZIP magic bytes |
|
|
219
|
+
| `update-pom` preserves hand-edited method bodies | ✅ 5 byte-identical-survival assertions |
|
|
220
|
+
|
|
221
|
+
## Roadmap
|
|
222
|
+
|
|
223
|
+
| Phase | Status | Headline |
|
|
224
|
+
|---|---|---|
|
|
225
|
+
| Phase 0 | ✅ Shipped | `@vijaypjavvadi/pw-emit` extracted; sibling package consumed by both `bdd2pw` and (planned) `sel2pw v1.2` |
|
|
226
|
+
| Phase 1a | ✅ Shipped | Gherkin parser, step matcher rules, locator picker, POM resolver, repo scanner, file-snapshot discovery, scaffold orchestration |
|
|
227
|
+
| Phase 1b | ✅ Shipped | Real Chromium-based page discovery via `playwright`, dialect-agnostic rules |
|
|
228
|
+
| Phase 1c | ✅ Shipped | Third-person + compound-input dialect coverage; second real-world fixture pinned |
|
|
229
|
+
| Phase 2 | ✅ Shipped | `update-pom` with append-only AST surgery — preserves hand-edits byte-identical |
|
|
230
|
+
| Phase 3 | ✅ Shipped | HTTP service `:4300` with three real workers + zip artifact download |
|
|
231
|
+
| **v1.0 (this release)** | **Ready to tag** | All of the above |
|
|
232
|
+
| v1.1 | Roadmap | LLM fallback for genuinely-vague steps, governance-routed |
|
|
233
|
+
| v1.2 | Roadmap | sel2pw migration onto `pw-emit`; multi-page scenario discovery |
|
|
234
|
+
| v1.3 | Roadmap | Optional `playwright-bdd` runtime mode |
|
|
235
|
+
|
|
236
|
+
## Documents
|
|
237
|
+
|
|
238
|
+
| Doc | Purpose |
|
|
239
|
+
|---|---|
|
|
240
|
+
| [`docs/SCOPE.md`](./docs/SCOPE.md) | What's in / out of v1.0, FR/NFR, CLI + HTTP surface, decisions log |
|
|
241
|
+
| [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) | Component diagram, sequence diagrams, `pw-emit` contract, HTTP API contract, error model |
|
|
242
|
+
| [`docs/PHASE_0_PLAN.md`](./docs/PHASE_0_PLAN.md) | The pw-emit extraction plan (mostly historical now — it shipped) |
|
|
243
|
+
| [`docs/STATUS.md`](./docs/STATUS.md) | Verified state right now — what works, what's pending, what's deferred |
|
|
244
|
+
| [`CHANGELOG.md`](./CHANGELOG.md) | Full version history |
|
|
245
|
+
| [`CONTRIBUTING.md`](./CONTRIBUTING.md) | Local dev, branching, PR conventions |
|
|
246
|
+
|
|
247
|
+
## Examples
|
|
248
|
+
|
|
249
|
+
- [`examples/practice-test-login/`](./examples/practice-test-login/) — first-person dialect, `Scenario Outline + Examples`, security scenarios. Validated 7/7 green against a live public site.
|
|
250
|
+
- [`examples/cms-login/`](./examples/cms-login/) — third-person dialect, compound input, `is redirected`. From a real OSS Selenium codebase.
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
MIT
|