@vijaypjavvadi/bdd2pw 2.2.4 → 2.2.6
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 +135 -0
- package/dist/emitters/facade.d.ts +18 -0
- package/dist/emitters/facade.js +44 -4
- package/dist/emitters/facade.js.map +1 -1
- package/dist/transformers/locatorPicker.d.ts +12 -0
- package/dist/transformers/locatorPicker.js +108 -12
- package/dist/transformers/locatorPicker.js.map +1 -1
- package/dist/utils/naming.d.ts +20 -0
- package/dist/utils/naming.js +26 -0
- package/dist/utils/naming.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,141 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
_Nothing yet._
|
|
11
11
|
|
|
12
|
+
## [2.2.6] — 2026-05-11
|
|
13
|
+
|
|
14
|
+
### Fixed — BUG-10 (P2): locator extractor picks framework-internal CSS classes
|
|
15
|
+
|
|
16
|
+
Cloud-job report 2026-05-11 20:08 against `https://preview.owasp-juice.shop`:
|
|
17
|
+
Angular's runtime-managed form-state classes (`.ng-untouched`,
|
|
18
|
+
`.ng-pristine`, `.ng-dirty`) were being picked as both the POM
|
|
19
|
+
field-name source AND the locator CSS selector. The POM emitted
|
|
20
|
+
`ngUntouchedInput = page.locator('.ng-untouched')` and the LLM then
|
|
21
|
+
used `ngUntouchedInput` for "search field has not yet been
|
|
22
|
+
interacted with" preconditions. The assertion failed because:
|
|
23
|
+
|
|
24
|
+
1. `.ng-untouched` flips to `.ng-touched` the moment focus enters
|
|
25
|
+
the input — locators built on it are flaky by construction.
|
|
26
|
+
2. Every Angular input shares the same state classes, so the
|
|
27
|
+
selector also fails strict-mode uniqueness.
|
|
28
|
+
3. The field name itself is misleading — no test author would call
|
|
29
|
+
the search box "ng untouched input".
|
|
30
|
+
|
|
31
|
+
Same class of bug applies to anything prefixed `mat-*` (Angular
|
|
32
|
+
Material), `cdk-*` (CDK overlays / focus traps), `mdc-*` (Material
|
|
33
|
+
Design Components), and `_ngcontent-*` / `_nghost-*` (Angular view
|
|
34
|
+
encapsulation markers).
|
|
35
|
+
|
|
36
|
+
**Fix (two layers in `src/transformers/locatorPicker.ts`):**
|
|
37
|
+
|
|
38
|
+
1. **Field-name source.** `cssSelectorToName` now skips
|
|
39
|
+
framework-prefix classes when walking a multi-class selector and
|
|
40
|
+
returns the first user-named class instead. For
|
|
41
|
+
`.ng-untouched.search-input` it returns `"search input"`, not
|
|
42
|
+
`"ng untouched"`. If EVERY class on the element is framework-only,
|
|
43
|
+
it returns undefined and the caller falls through to
|
|
44
|
+
`<role|tag>Element` — for an Angular input with no other handles,
|
|
45
|
+
the synthesised field name becomes `inputElement` rather than
|
|
46
|
+
`ngUntouchedInput`.
|
|
47
|
+
|
|
48
|
+
2. **CSS-fallback gate.** `pickLocator`'s step-6 CSS fallback now
|
|
49
|
+
refuses framework-only selectors via `isFrameworkOnlySelector`.
|
|
50
|
+
When the element has no role / label / placeholder / testId /
|
|
51
|
+
text / xpath and the only CSS handle is something like
|
|
52
|
+
`.ng-untouched.ng-pristine`, we emit a tag-only locator
|
|
53
|
+
(`page.locator("input")`) and flag it `confidence: "fallback"`.
|
|
54
|
+
Tag-only is non-ideal but at least matches a real DOM element
|
|
55
|
+
instead of a transient state class.
|
|
56
|
+
|
|
57
|
+
New exports (both pure, easy to compose into future emitter rules):
|
|
58
|
+
- `isFrameworkClass(cls: string): boolean`
|
|
59
|
+
- `isFrameworkOnlySelector(selector: string | undefined): boolean`
|
|
60
|
+
|
|
61
|
+
Detection covered: `ng-*`, `mat-*`, `cdk-*`, `mdc-*`,
|
|
62
|
+
`_ngcontent-*`, `_nghost-*`. Hyphen-after-prefix is required so we
|
|
63
|
+
don't accidentally flag user-named classes like `nginx-banner` or
|
|
64
|
+
`ngo-button`.
|
|
65
|
+
|
|
66
|
+
### Tests
|
|
67
|
+
|
|
68
|
+
`tests/unit/locatorPicker.test.ts` — full coverage for BUG-10:
|
|
69
|
+
- `isFrameworkClass` matches every prefix, leaves user classes
|
|
70
|
+
alone (including the deliberate near-misses `nginx-banner`,
|
|
71
|
+
`ngo-button`, `material-card`).
|
|
72
|
+
- `isFrameworkOnlySelector` flags pure framework selectors,
|
|
73
|
+
declines mixed selectors, declines `#id`-bearing selectors, declines
|
|
74
|
+
non-class selectors.
|
|
75
|
+
- `pickLocator` drops a framework-only CSS selector and falls back
|
|
76
|
+
to tag-only.
|
|
77
|
+
- `pickLocator` preserves selectors that have at least one
|
|
78
|
+
user-named class.
|
|
79
|
+
- `synthFieldName` skips framework classes and falls through to
|
|
80
|
+
`<role|tag>Element`.
|
|
81
|
+
- `synthFieldName` picks the user-named class in a mixed selector.
|
|
82
|
+
|
|
83
|
+
## [2.2.5] — 2026-05-11
|
|
84
|
+
|
|
85
|
+
### Fixed — BUG-9 (P0): POM field name starting with a digit broke entire spec
|
|
86
|
+
|
|
87
|
+
Cloud-job report 2026-05-11 19:46 against `https://preview.owasp-juice.shop`:
|
|
88
|
+
every test in `tests/r-0-c934-ddf-001.spec.ts` failed with a
|
|
89
|
+
**compile-time** `SyntaxError`, zero tests executed.
|
|
90
|
+
|
|
91
|
+
Root cause: juice-shop renders pagination labels like `"0 of 0"` and
|
|
92
|
+
`"1,500 of 0"` inside status regions. The locator-picker's field-name
|
|
93
|
+
synthesiser routed those through `camelCase`, which produced
|
|
94
|
+
`0Of0`, `1500Of0`, `0,0Of0`. JavaScript identifiers must start with
|
|
95
|
+
a letter / `$` / `_`, never a digit, and cannot contain commas.
|
|
96
|
+
|
|
97
|
+
The emitted POM and spec looked like:
|
|
98
|
+
|
|
99
|
+
this.0Of0 = page.getByText("0 of 0");
|
|
100
|
+
// ^^^ SyntaxError
|
|
101
|
+
await expect(r0c934ddf001.0Of0).toBeVisible();
|
|
102
|
+
// ^^^ SyntaxError
|
|
103
|
+
|
|
104
|
+
This is worse than a runtime failure: there's no triage signal,
|
|
105
|
+
just a hard parse error, and the entire scenario set is skipped.
|
|
106
|
+
|
|
107
|
+
**Fix (defence in depth — three layers):**
|
|
108
|
+
|
|
109
|
+
1. **Identifier-safe naming helper.** New `toJsIdentifier(s)` in
|
|
110
|
+
`src/utils/naming.ts`. Strips any character not in `[A-Za-z0-9_$]`,
|
|
111
|
+
prefixes digit-leading identifiers with `_`, falls back to
|
|
112
|
+
`_field` for empty input. Pure function, fully unit-tested.
|
|
113
|
+
|
|
114
|
+
2. **POM emitter.** `synthFieldName` in
|
|
115
|
+
`src/transformers/locatorPicker.ts` runs the post-camelCase result
|
|
116
|
+
through `toJsIdentifier`. Both the main path and the status-region
|
|
117
|
+
special case are covered. So `"0 of 0"` now becomes `_0Of0`,
|
|
118
|
+
`"1,500 of 0"` becomes `_1500Of0`. The POM declares
|
|
119
|
+
`this._0Of0 = page.getByText("0 of 0")` — valid TS.
|
|
120
|
+
|
|
121
|
+
3. **Binding renderer safety net.** New `sanitizeLocatorReferences(s)`
|
|
122
|
+
in `src/emitters/facade.ts` rewrites `.<digit-leading>` member
|
|
123
|
+
access and `[<digit-leading>]` bracket access to be `_`-prefixed.
|
|
124
|
+
Applied to `assertion.locator`, `pomCall.method`, every
|
|
125
|
+
`pomCall.args` entry, and every line of `customBody`. This catches
|
|
126
|
+
cached LLM bindings from v2.2.4 or earlier that reference the old
|
|
127
|
+
unsanitized name shape, so users don't have to clear their cache
|
|
128
|
+
to benefit from the fix.
|
|
129
|
+
|
|
130
|
+
### Tests
|
|
131
|
+
|
|
132
|
+
- `tests/unit/naming.test.ts`: full coverage for `toJsIdentifier` —
|
|
133
|
+
pass-through of valid identifiers, digit-prefix path
|
|
134
|
+
(`0Of0`/`1500Of0`/`123`), comma stripping (`0,0Of0`,
|
|
135
|
+
`foo,bar`), arbitrary punctuation, empty-input fallback to
|
|
136
|
+
`_field`.
|
|
137
|
+
- `tests/unit/locatorPicker.test.ts`: end-to-end reproduction of the
|
|
138
|
+
juice-shop pagination case — `{ text: "0 of 0" }` element produces
|
|
139
|
+
fieldName `_0Of0`; `{ text: "1,500 of 0" }` produces `_1500Of0`;
|
|
140
|
+
valid inputs unchanged.
|
|
141
|
+
- `tests/unit/facade.test.ts`: helper coverage for
|
|
142
|
+
`sanitizeLocatorReferences` plus three end-to-end render tests
|
|
143
|
+
(assertion locator / pomCall args / customBody) confirming the
|
|
144
|
+
rewriter fires correctly through the public `emitTestFile` entry
|
|
145
|
+
point.
|
|
146
|
+
|
|
12
147
|
## [2.2.4] — 2026-05-11
|
|
13
148
|
|
|
14
149
|
### Fixed — BUG-7: empty `expect()` for URL assertions (regression from 2.2.3)
|
|
@@ -9,6 +9,24 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { type EmitResult } from "@vijaypjavvadi/pw-emit";
|
|
11
11
|
import type { PageObjectIR, StepBinding } from "../types";
|
|
12
|
+
/**
|
|
13
|
+
* v2.2.5 — BUG-9 safety net.
|
|
14
|
+
*
|
|
15
|
+
* If a cached LLM binding from v2.2.4 or earlier references a POM field
|
|
16
|
+
* whose name starts with a digit (e.g. `r0c934ddf001.0Of0` from
|
|
17
|
+
* juice-shop's "0 of 0" pagination label), we rewrite the
|
|
18
|
+
* digit-leading segment to be `_`-prefixed so the spec parses.
|
|
19
|
+
*
|
|
20
|
+
* The v2.2.5 field-name synthesiser already prevents new bindings from
|
|
21
|
+
* having this shape, but cached entries persist across versions until
|
|
22
|
+
* explicitly cleared.
|
|
23
|
+
*
|
|
24
|
+
* Patterns rewritten:
|
|
25
|
+
* `foo.0Of0` → `foo._0Of0` // member access onto digit
|
|
26
|
+
* `foo[0Of0]` → `foo[_0Of0]` // computed-property identifier
|
|
27
|
+
* `foo.0,0Of0` → `foo._0_0Of0` // commas → underscore
|
|
28
|
+
*/
|
|
29
|
+
export declare function sanitizeLocatorReferences(s: string): string;
|
|
12
30
|
export declare class EmitterConsistencyError extends Error {
|
|
13
31
|
constructor(message: string);
|
|
14
32
|
}
|
package/dist/emitters/facade.js
CHANGED
|
@@ -10,11 +10,43 @@
|
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.EmitterConsistencyError = void 0;
|
|
13
|
+
exports.sanitizeLocatorReferences = sanitizeLocatorReferences;
|
|
13
14
|
exports.emitPageObject = emitPageObject;
|
|
14
15
|
exports.emitTestFile = emitTestFile;
|
|
15
16
|
const pw_emit_1 = require("@vijaypjavvadi/pw-emit");
|
|
16
17
|
const naming_1 = require("../utils/naming");
|
|
17
18
|
const commentSafe_1 = require("../utils/commentSafe");
|
|
19
|
+
/**
|
|
20
|
+
* v2.2.5 — BUG-9 safety net.
|
|
21
|
+
*
|
|
22
|
+
* If a cached LLM binding from v2.2.4 or earlier references a POM field
|
|
23
|
+
* whose name starts with a digit (e.g. `r0c934ddf001.0Of0` from
|
|
24
|
+
* juice-shop's "0 of 0" pagination label), we rewrite the
|
|
25
|
+
* digit-leading segment to be `_`-prefixed so the spec parses.
|
|
26
|
+
*
|
|
27
|
+
* The v2.2.5 field-name synthesiser already prevents new bindings from
|
|
28
|
+
* having this shape, but cached entries persist across versions until
|
|
29
|
+
* explicitly cleared.
|
|
30
|
+
*
|
|
31
|
+
* Patterns rewritten:
|
|
32
|
+
* `foo.0Of0` → `foo._0Of0` // member access onto digit
|
|
33
|
+
* `foo[0Of0]` → `foo[_0Of0]` // computed-property identifier
|
|
34
|
+
* `foo.0,0Of0` → `foo._0_0Of0` // commas → underscore
|
|
35
|
+
*/
|
|
36
|
+
function sanitizeLocatorReferences(s) {
|
|
37
|
+
if (!s)
|
|
38
|
+
return s;
|
|
39
|
+
// 1. `.<digit>` → `._<digit>` for member access
|
|
40
|
+
let out = s.replace(/(\.)([0-9][A-Za-z0-9_$,]*)/g, (_, dot, ident) => {
|
|
41
|
+
return dot + "_" + ident.replace(/,/g, "_");
|
|
42
|
+
});
|
|
43
|
+
// 2. Strip stray commas from identifier-shaped runs after a dot we
|
|
44
|
+
// already prefixed (handled above) and from bracket-property names.
|
|
45
|
+
out = out.replace(/\[([0-9][A-Za-z0-9_$,]*)\]/g, (_, ident) => {
|
|
46
|
+
return "[_" + ident.replace(/,/g, "_") + "]";
|
|
47
|
+
});
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
18
50
|
class EmitterConsistencyError extends Error {
|
|
19
51
|
constructor(message) {
|
|
20
52
|
super(message);
|
|
@@ -123,12 +155,18 @@ function bindingsToBody(bindings, prelude = []) {
|
|
|
123
155
|
lines.push(`await test.step(${labelLit}, async () => {`);
|
|
124
156
|
if (b.customBody) {
|
|
125
157
|
// Compound step — body has multiple statements pre-rendered.
|
|
126
|
-
|
|
158
|
+
// v2.2.5 — sanitize digit-leading POM references that may have been
|
|
159
|
+
// cached from older bdd2pw versions.
|
|
160
|
+
const safeBody = sanitizeLocatorReferences(b.customBody);
|
|
161
|
+
for (const line of safeBody.split("\n"))
|
|
127
162
|
lines.push(` ${line}`);
|
|
128
163
|
}
|
|
129
164
|
else if (b.pomCall) {
|
|
130
|
-
const args = b.pomCall.args
|
|
131
|
-
|
|
165
|
+
const args = b.pomCall.args
|
|
166
|
+
.map((a) => sanitizeLocatorReferences(a))
|
|
167
|
+
.join(", ");
|
|
168
|
+
const method = sanitizeLocatorReferences(b.pomCall.method);
|
|
169
|
+
lines.push(` await ${b.pomCall.page}.${method}(${args});`);
|
|
132
170
|
}
|
|
133
171
|
else if (b.assertion) {
|
|
134
172
|
const expected = b.assertion.expected !== undefined ? b.assertion.expected : "";
|
|
@@ -140,7 +178,9 @@ function bindingsToBody(bindings, prelude = []) {
|
|
|
140
178
|
// async ({ page }) => ...) when the locator is empty/whitespace.
|
|
141
179
|
const rawLocator = b.assertion.locator;
|
|
142
180
|
const trimmedLocator = (rawLocator == null ? "" : String(rawLocator)).trim();
|
|
143
|
-
|
|
181
|
+
// v2.2.5 — rewrite `.0Of0` → `._0Of0` for cached bindings.
|
|
182
|
+
const safeLocator = sanitizeLocatorReferences(trimmedLocator);
|
|
183
|
+
const renderedLocator = safeLocator === "" ? "page" : safeLocator;
|
|
144
184
|
lines.push(` await expect(${renderedLocator}).${b.assertion.matcher}(${expected});`);
|
|
145
185
|
}
|
|
146
186
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facade.js","sourceRoot":"","sources":["../../src/emitters/facade.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;
|
|
1
|
+
{"version":3,"file":"facade.js","sourceRoot":"","sources":["../../src/emitters/facade.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,18 @@ import type { ElementIR, LocatorChoice } from "../types";
|
|
|
16
16
|
* candidate locator, we fall down the chain.
|
|
17
17
|
*/
|
|
18
18
|
export declare function pickLocator(element: ElementIR, allElements: ElementIR[]): LocatorChoice;
|
|
19
|
+
/** True when `cls` looks like a framework-internal class. */
|
|
20
|
+
export declare function isFrameworkClass(cls: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* True when `selector` reduces to *only* framework-internal classes
|
|
23
|
+
* (e.g. `.ng-untouched.ng-pristine`). Such selectors are unstable
|
|
24
|
+
* runtime markers and must not be used as Playwright locators.
|
|
25
|
+
*
|
|
26
|
+
* Selectors with at least one non-framework class
|
|
27
|
+
* (`.ng-untouched.search-input`) are NOT flagged — the non-framework
|
|
28
|
+
* class can still drive a useful locator.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isFrameworkOnlySelector(selector: string | undefined): boolean;
|
|
19
31
|
/**
|
|
20
32
|
* Dedupe a list of locator choices.
|
|
21
33
|
*
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.pickLocator = pickLocator;
|
|
15
|
+
exports.isFrameworkClass = isFrameworkClass;
|
|
16
|
+
exports.isFrameworkOnlySelector = isFrameworkOnlySelector;
|
|
15
17
|
exports.dedupeLocators = dedupeLocators;
|
|
16
18
|
const naming_1 = require("../utils/naming");
|
|
17
19
|
/**
|
|
@@ -85,8 +87,12 @@ function pickLocator(element, allElements) {
|
|
|
85
87
|
confidence: rivals.length === 0 ? "unique" : "ambiguous",
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
|
-
// 6. CSS selector
|
|
89
|
-
|
|
90
|
+
// 6. CSS selector — but reject framework-only class selectors
|
|
91
|
+
// (Angular's `.ng-untouched`, Material's `.mat-form-field`, etc.).
|
|
92
|
+
// These are runtime state markers that change as the user
|
|
93
|
+
// interacts with the page, so locators built on them are flaky.
|
|
94
|
+
// See BUG-10 / v2.2.6.
|
|
95
|
+
if (element.cssSelector && !isFrameworkOnlySelector(element.cssSelector)) {
|
|
90
96
|
return {
|
|
91
97
|
api: "locator",
|
|
92
98
|
args: JSON.stringify(element.cssSelector),
|
|
@@ -105,7 +111,12 @@ function pickLocator(element, allElements) {
|
|
|
105
111
|
confidence: "fallback",
|
|
106
112
|
};
|
|
107
113
|
}
|
|
108
|
-
// Nothing usable — emit a tag-only locator and flag as fallback
|
|
114
|
+
// Nothing usable — emit a tag-only locator and flag as fallback.
|
|
115
|
+
// v2.2.6 — if we got here because the only CSS selector was
|
|
116
|
+
// framework-internal (and there was no role / label / placeholder /
|
|
117
|
+
// testId / text / xpath), the tag-only fallback is the least-bad
|
|
118
|
+
// option. It at least matches a visible DOM element rather than a
|
|
119
|
+
// transient state class.
|
|
109
120
|
return {
|
|
110
121
|
api: "locator",
|
|
111
122
|
args: JSON.stringify(element.tag),
|
|
@@ -127,6 +138,68 @@ function pickLocator(element, allElements) {
|
|
|
127
138
|
* `notificationBanner`) is what a test author would write by hand.
|
|
128
139
|
*/
|
|
129
140
|
const STATUS_REGION_RE = /(error|alert|message|warning|success|notification|status)/i;
|
|
141
|
+
/**
|
|
142
|
+
* v2.2.6 — BUG-10. CSS class prefixes that frameworks generate at runtime
|
|
143
|
+
* for their own bookkeeping (form-state, view-encapsulation, theming,
|
|
144
|
+
* etc.). These classes:
|
|
145
|
+
*
|
|
146
|
+
* - Change at runtime (`.ng-untouched` → `.ng-touched` the moment the
|
|
147
|
+
* user types), so selecting by them gives flaky locators.
|
|
148
|
+
* - Are not user-facing — no test author would write
|
|
149
|
+
* `await expect(page.locator('.ng-untouched')).toBeVisible()` by hand.
|
|
150
|
+
* - Mass-collide across the DOM (every Angular input has the same
|
|
151
|
+
* state classes), so locator-by-class is non-unique anyway.
|
|
152
|
+
*
|
|
153
|
+
* Covered prefixes:
|
|
154
|
+
* - `ng-*` Angular core (ng-untouched, ng-pristine, ng-dirty, …)
|
|
155
|
+
* - `mat-*` Angular Material
|
|
156
|
+
* - `cdk-*` Angular CDK (overlay, focus-trap, etc.)
|
|
157
|
+
* - `mdc-*` Material Design Components (web)
|
|
158
|
+
* - `_ngcontent-*` Angular view encapsulation attribute-like classes
|
|
159
|
+
* - `_nghost-*` Angular view encapsulation host markers
|
|
160
|
+
*
|
|
161
|
+
* Hyphen is required after the prefix so we don't accidentally exclude
|
|
162
|
+
* legitimate classes like `ngo-button` (user-named) or `cdkit` (rare but
|
|
163
|
+
* possible). The `_ngcontent-` / `_nghost-` patterns are handled with an
|
|
164
|
+
* explicit underscore prefix.
|
|
165
|
+
*/
|
|
166
|
+
const FRAMEWORK_CLASS_RE = /^(?:ng-|mat-|cdk-|mdc-|_ngcontent-|_nghost-)/i;
|
|
167
|
+
/** True when `cls` looks like a framework-internal class. */
|
|
168
|
+
function isFrameworkClass(cls) {
|
|
169
|
+
return FRAMEWORK_CLASS_RE.test(cls);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract all bare class names from a CSS selector. Tolerant of compound
|
|
173
|
+
* selectors like `div.foo.bar#main` (returns `["foo", "bar"]`).
|
|
174
|
+
*/
|
|
175
|
+
function extractClasses(selector) {
|
|
176
|
+
const out = [];
|
|
177
|
+
const re = /\.([a-zA-Z_][\w-]*)/g;
|
|
178
|
+
let m;
|
|
179
|
+
while ((m = re.exec(selector)) !== null)
|
|
180
|
+
out.push(m[1]);
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* True when `selector` reduces to *only* framework-internal classes
|
|
185
|
+
* (e.g. `.ng-untouched.ng-pristine`). Such selectors are unstable
|
|
186
|
+
* runtime markers and must not be used as Playwright locators.
|
|
187
|
+
*
|
|
188
|
+
* Selectors with at least one non-framework class
|
|
189
|
+
* (`.ng-untouched.search-input`) are NOT flagged — the non-framework
|
|
190
|
+
* class can still drive a useful locator.
|
|
191
|
+
*/
|
|
192
|
+
function isFrameworkOnlySelector(selector) {
|
|
193
|
+
if (!selector)
|
|
194
|
+
return false;
|
|
195
|
+
// If there's an #id, we can't claim "framework only" — id is independent.
|
|
196
|
+
if (/#[a-zA-Z][\w-]*/.test(selector))
|
|
197
|
+
return false;
|
|
198
|
+
const classes = extractClasses(selector);
|
|
199
|
+
if (classes.length === 0)
|
|
200
|
+
return false; // not a class-based selector
|
|
201
|
+
return classes.every(isFrameworkClass);
|
|
202
|
+
}
|
|
130
203
|
/**
|
|
131
204
|
* Synthesise a camelCase TS field name for an element.
|
|
132
205
|
*
|
|
@@ -151,7 +224,7 @@ function synthFieldName(element) {
|
|
|
151
224
|
const idDerived = cssSelectorToName(element.cssSelector);
|
|
152
225
|
if (idDerived && STATUS_REGION_RE.test(idDerived)) {
|
|
153
226
|
const cleaned = idDerived.replace(/[^a-zA-Z0-9 ]/g, " ").trim();
|
|
154
|
-
return (0, naming_1.camelCase)(cleaned) || "errorRegion";
|
|
227
|
+
return (0, naming_1.toJsIdentifier)((0, naming_1.camelCase)(cleaned) || "errorRegion");
|
|
155
228
|
}
|
|
156
229
|
const baseName = element.name ??
|
|
157
230
|
element.label ??
|
|
@@ -166,14 +239,32 @@ function synthFieldName(element) {
|
|
|
166
239
|
if (suffix && !fieldName.toLowerCase().endsWith(suffix.toLowerCase())) {
|
|
167
240
|
fieldName += suffix;
|
|
168
241
|
}
|
|
169
|
-
|
|
242
|
+
// v2.2.5 — BUG-9 guard: ensure the name is a valid JS identifier.
|
|
243
|
+
// `camelCase("0 of 0")` returns "0Of0", which is not a legal
|
|
244
|
+
// identifier and breaks the .spec.ts parse. Prefix digit-leading
|
|
245
|
+
// names with `_` and strip any stray punctuation that survived
|
|
246
|
+
// upstream normalisation (commas, etc.).
|
|
247
|
+
return (0, naming_1.toJsIdentifier)(fieldName);
|
|
170
248
|
}
|
|
171
249
|
/**
|
|
172
250
|
* Best-effort name extraction from a CSS selector. Handles common shapes:
|
|
173
|
-
* #username
|
|
174
|
-
* .foo-bar
|
|
175
|
-
* div#main
|
|
176
|
-
* a.wp-block-btn
|
|
251
|
+
* #username → "username"
|
|
252
|
+
* .foo-bar → "foo bar"
|
|
253
|
+
* div#main → "main"
|
|
254
|
+
* a.wp-block-btn → "wp block btn"
|
|
255
|
+
* input.ng-untouched.search-input → "search input" (v2.2.6)
|
|
256
|
+
*
|
|
257
|
+
* v2.2.6 — BUG-10. When the selector has multiple classes, skip
|
|
258
|
+
* framework-internal classes (`ng-*`, `mat-*`, `cdk-*`, `mdc-*`,
|
|
259
|
+
* `_ngcontent-*`, `_nghost-*`) and pick the first user-named class.
|
|
260
|
+
* Without this filter, juice-shop's `.ng-untouched.search-input` gets
|
|
261
|
+
* named "ng untouched" → `ngUntouchedInput`, which is a flaky state
|
|
262
|
+
* locator and a confusing field name.
|
|
263
|
+
*
|
|
264
|
+
* If EVERY class is framework-internal, returns undefined so the caller
|
|
265
|
+
* falls through to `<role|tag>Element` rather than naming the field
|
|
266
|
+
* after a transient state class.
|
|
267
|
+
*
|
|
177
268
|
* Returns undefined for selectors with no usable id/class.
|
|
178
269
|
*/
|
|
179
270
|
function cssSelectorToName(selector) {
|
|
@@ -183,9 +274,14 @@ function cssSelectorToName(selector) {
|
|
|
183
274
|
const idMatch = selector.match(/#([a-zA-Z][\w-]*)/);
|
|
184
275
|
if (idMatch)
|
|
185
276
|
return idMatch[1].replace(/-/g, " ");
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
277
|
+
// Walk all classes, return the first non-framework one.
|
|
278
|
+
const classes = extractClasses(selector);
|
|
279
|
+
for (const cls of classes) {
|
|
280
|
+
if (!isFrameworkClass(cls))
|
|
281
|
+
return cls.replace(/-/g, " ");
|
|
282
|
+
}
|
|
283
|
+
// All classes were framework-internal — fall through to the caller's
|
|
284
|
+
// next priority (typically `<role|tag>Element`).
|
|
189
285
|
return undefined;
|
|
190
286
|
}
|
|
191
287
|
function roleToSuffix(role, tag) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locatorPicker.js","sourceRoot":"","sources":["../../src/transformers/locatorPicker.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAUH,
|
|
1
|
+
{"version":3,"file":"locatorPicker.js","sourceRoot":"","sources":["../../src/transformers/locatorPicker.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/naming.d.ts
CHANGED
|
@@ -7,6 +7,26 @@
|
|
|
7
7
|
export declare function pascalCase(input: string): string;
|
|
8
8
|
/** Convert any case to camelCase. */
|
|
9
9
|
export declare function camelCase(input: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* v2.2.5 — coerce an arbitrary string into a valid TypeScript / JavaScript
|
|
12
|
+
* identifier. Used by the POM field-name synthesiser so we never emit
|
|
13
|
+
* code like `this.0Of0 = ...` (juice-shop pagination labels: "0 of 0",
|
|
14
|
+
* "1 - 50 of 0") which makes the .spec.ts file refuse to parse.
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* 1. Strip any character that isn't `[A-Za-z0-9_$]`. Commas, hyphens,
|
|
18
|
+
* backticks, quotes, whitespace etc. all go.
|
|
19
|
+
* 2. If what remains starts with a digit, prefix with a single `_`.
|
|
20
|
+
* JS identifier production: `IdentifierStart` = letter | `$` | `_`.
|
|
21
|
+
* 3. If the result is empty, return a stable fallback "_field" so the
|
|
22
|
+
* caller never has to handle empty-string corner cases.
|
|
23
|
+
*
|
|
24
|
+
* Reserved-word avoidance is intentionally NOT handled here: at the
|
|
25
|
+
* field-access site `obj.class` is fine (only top-level identifiers are
|
|
26
|
+
* reserved). If we later add free-standing identifier emission (e.g. let
|
|
27
|
+
* <name> = ...), revisit.
|
|
28
|
+
*/
|
|
29
|
+
export declare function toJsIdentifier(input: string): string;
|
|
10
30
|
/** Convert any case to kebab-case. */
|
|
11
31
|
export declare function kebabCase(input: string): string;
|
|
12
32
|
/** Convert any case to snake_case. */
|
package/dist/utils/naming.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.pascalCase = pascalCase;
|
|
9
9
|
exports.camelCase = camelCase;
|
|
10
|
+
exports.toJsIdentifier = toJsIdentifier;
|
|
10
11
|
exports.kebabCase = kebabCase;
|
|
11
12
|
exports.snakeCase = snakeCase;
|
|
12
13
|
exports.pageObjectFileStem = pageObjectFileStem;
|
|
@@ -22,6 +23,31 @@ function camelCase(input) {
|
|
|
22
23
|
const pascal = pascalCase(input);
|
|
23
24
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* v2.2.5 — coerce an arbitrary string into a valid TypeScript / JavaScript
|
|
28
|
+
* identifier. Used by the POM field-name synthesiser so we never emit
|
|
29
|
+
* code like `this.0Of0 = ...` (juice-shop pagination labels: "0 of 0",
|
|
30
|
+
* "1 - 50 of 0") which makes the .spec.ts file refuse to parse.
|
|
31
|
+
*
|
|
32
|
+
* Rules:
|
|
33
|
+
* 1. Strip any character that isn't `[A-Za-z0-9_$]`. Commas, hyphens,
|
|
34
|
+
* backticks, quotes, whitespace etc. all go.
|
|
35
|
+
* 2. If what remains starts with a digit, prefix with a single `_`.
|
|
36
|
+
* JS identifier production: `IdentifierStart` = letter | `$` | `_`.
|
|
37
|
+
* 3. If the result is empty, return a stable fallback "_field" so the
|
|
38
|
+
* caller never has to handle empty-string corner cases.
|
|
39
|
+
*
|
|
40
|
+
* Reserved-word avoidance is intentionally NOT handled here: at the
|
|
41
|
+
* field-access site `obj.class` is fine (only top-level identifiers are
|
|
42
|
+
* reserved). If we later add free-standing identifier emission (e.g. let
|
|
43
|
+
* <name> = ...), revisit.
|
|
44
|
+
*/
|
|
45
|
+
function toJsIdentifier(input) {
|
|
46
|
+
let s = input.replace(/[^A-Za-z0-9_$]/g, "");
|
|
47
|
+
if (s.length > 0 && /^[0-9]/.test(s))
|
|
48
|
+
s = "_" + s;
|
|
49
|
+
return s || "_field";
|
|
50
|
+
}
|
|
25
51
|
/** Convert any case to kebab-case. */
|
|
26
52
|
function kebabCase(input) {
|
|
27
53
|
return splitWords(input).map((w) => w.toLowerCase()).join("-");
|
package/dist/utils/naming.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../src/utils/naming.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAGH,gCAIC;AAGD,8BAGC;AAGD,8BAEC;AAGD,8BAEC;AAMD,gDAGC;AAMD,oCAEC;
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../src/utils/naming.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAGH,gCAIC;AAGD,8BAGC;AAqBD,wCAIC;AAGD,8BAEC;AAGD,8BAEC;AAMD,gDAGC;AAMD,oCAEC;AA/DD,sCAAsC;AACtC,SAAgB,UAAU,CAAC,KAAa;IACtC,OAAO,UAAU,CAAC,KAAK,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAChE,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,qCAAqC;AACrC,SAAgB,SAAS,CAAC,KAAa;IACrC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,QAAQ,CAAC;AACvB,CAAC;AAED,sCAAsC;AACtC,SAAgB,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjE,CAAC;AAED,sCAAsC;AACtC,SAAgB,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,SAAiB;IAClD,MAAM,QAAQ,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACjD,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY,CAAC,WAAmB;IAC9C,OAAO,GAAG,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC;AAC1C,CAAC;AAED,sEAAsE;AACtE,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC;SACnC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vijaypjavvadi/bdd2pw",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
4
4
|
"description": "Scaffold runnable Playwright TypeScript tests from Gherkin .feature files. Detects existing Page Objects, scans live pages via Microsoft Playwright MCP, emits POMs + specs, ready for execution. CLI + HTTP service. Optional governed LLM fallback (Anthropic / OpenAI / Gemini).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bdd2pw": "dist/cli.js"
|