@vijaypjavvadi/bdd2pw 2.2.5 → 2.2.7
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
CHANGED
|
@@ -9,6 +9,138 @@ 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.7] — 2026-05-11
|
|
13
|
+
|
|
14
|
+
### Changed — BUG-10 polish: stronger framework-class handling + cleaner fallback names
|
|
15
|
+
|
|
16
|
+
Two follow-up improvements to the v2.2.6 BUG-10 fix. Each one
|
|
17
|
+
addresses a residual rough edge surfaced by the
|
|
18
|
+
`pickLocator({ tag: "input", cssSelector: ".ng-untouched.search-input" })`
|
|
19
|
+
case.
|
|
20
|
+
|
|
21
|
+
#### 1. Strip framework class tokens from mixed selectors
|
|
22
|
+
|
|
23
|
+
v2.2.6 rejected framework-ONLY selectors but left mixed ones intact,
|
|
24
|
+
so `.ng-untouched.search-input` still got rendered as
|
|
25
|
+
`page.locator('.ng-untouched.search-input')`. Playwright requires
|
|
26
|
+
BOTH classes to be present simultaneously, so when the user focuses
|
|
27
|
+
the input and Angular flips `.ng-untouched` → `.ng-touched`, the
|
|
28
|
+
locator stops matching and the test fails on a should-be-stable
|
|
29
|
+
user-named element.
|
|
30
|
+
|
|
31
|
+
**Fix:** new `stripFrameworkClasses(selector)` export in
|
|
32
|
+
`src/transformers/locatorPicker.ts`. Wired into `pickLocator`
|
|
33
|
+
step 6 so the emitted selector contains only user-named class
|
|
34
|
+
tokens. `.ng-untouched.search-input` → `.search-input`.
|
|
35
|
+
`input.ng-untouched[type="search"]` → `input[type="search"]`.
|
|
36
|
+
`form.ng-untouched .search-input` → `form .search-input`. Only bare
|
|
37
|
+
`.classname` tokens are stripped; element/attribute/id parts and
|
|
38
|
+
combinators are preserved.
|
|
39
|
+
|
|
40
|
+
If stripping leaves an empty / whitespace-only selector (e.g. the
|
|
41
|
+
input had a `.ng-untouched.ng-pristine` selector that the
|
|
42
|
+
isFrameworkOnlySelector gate happened to miss for some
|
|
43
|
+
edge-case reason), the renderer falls through to xpath then to
|
|
44
|
+
tag-only — same as the v2.2.6 framework-only path.
|
|
45
|
+
|
|
46
|
+
#### 2. Skip role-suffix when the base came from the `<tag>Element` fallback
|
|
47
|
+
|
|
48
|
+
After v2.2.6, an Angular input with `cssSelector: ".ng-untouched"`
|
|
49
|
+
and no other handles produced the field name `inputElementInput` —
|
|
50
|
+
clean of the framework class but ugly. The redundant suffix was
|
|
51
|
+
because the existing `endsWith` check didn't catch `inputElement`
|
|
52
|
+
(`"inputelement".endsWith("input")` is `false`).
|
|
53
|
+
|
|
54
|
+
**Fix:** `synthFieldName` now tracks a `haveExplicitBase` flag —
|
|
55
|
+
true when ANY explicit name source (name / label / placeholder /
|
|
56
|
+
testId / text / idDerived) was available, false when we fell
|
|
57
|
+
through to the `${role ?? tag}Element` last-resort base. The
|
|
58
|
+
role-suffix step is skipped on the fallback path. So the same
|
|
59
|
+
Angular input now produces `inputElement`. All other shapes
|
|
60
|
+
unchanged.
|
|
61
|
+
|
|
62
|
+
### Tests
|
|
63
|
+
|
|
64
|
+
`tests/unit/locatorPicker.test.ts`:
|
|
65
|
+
- New `stripFrameworkClasses` group — single-class, mixed, tag /
|
|
66
|
+
attribute / descendant preservation, no-op when no framework
|
|
67
|
+
class, and a documented edge-case (`:not(.ng-foo)` strips the
|
|
68
|
+
inner token to `:not()` — acceptable, rare in practice).
|
|
69
|
+
- Updated the v2.2.6 BUG-10 tests to reflect v2.2.7 behaviour
|
|
70
|
+
(`expect(c.args).toBe('".search-input"')` not just `.toContain`,
|
|
71
|
+
field name `inputElement` not `inputElementInput`).
|
|
72
|
+
|
|
73
|
+
## [2.2.6] — 2026-05-11
|
|
74
|
+
|
|
75
|
+
### Fixed — BUG-10 (P2): locator extractor picks framework-internal CSS classes
|
|
76
|
+
|
|
77
|
+
Cloud-job report 2026-05-11 20:08 against `https://preview.owasp-juice.shop`:
|
|
78
|
+
Angular's runtime-managed form-state classes (`.ng-untouched`,
|
|
79
|
+
`.ng-pristine`, `.ng-dirty`) were being picked as both the POM
|
|
80
|
+
field-name source AND the locator CSS selector. The POM emitted
|
|
81
|
+
`ngUntouchedInput = page.locator('.ng-untouched')` and the LLM then
|
|
82
|
+
used `ngUntouchedInput` for "search field has not yet been
|
|
83
|
+
interacted with" preconditions. The assertion failed because:
|
|
84
|
+
|
|
85
|
+
1. `.ng-untouched` flips to `.ng-touched` the moment focus enters
|
|
86
|
+
the input — locators built on it are flaky by construction.
|
|
87
|
+
2. Every Angular input shares the same state classes, so the
|
|
88
|
+
selector also fails strict-mode uniqueness.
|
|
89
|
+
3. The field name itself is misleading — no test author would call
|
|
90
|
+
the search box "ng untouched input".
|
|
91
|
+
|
|
92
|
+
Same class of bug applies to anything prefixed `mat-*` (Angular
|
|
93
|
+
Material), `cdk-*` (CDK overlays / focus traps), `mdc-*` (Material
|
|
94
|
+
Design Components), and `_ngcontent-*` / `_nghost-*` (Angular view
|
|
95
|
+
encapsulation markers).
|
|
96
|
+
|
|
97
|
+
**Fix (two layers in `src/transformers/locatorPicker.ts`):**
|
|
98
|
+
|
|
99
|
+
1. **Field-name source.** `cssSelectorToName` now skips
|
|
100
|
+
framework-prefix classes when walking a multi-class selector and
|
|
101
|
+
returns the first user-named class instead. For
|
|
102
|
+
`.ng-untouched.search-input` it returns `"search input"`, not
|
|
103
|
+
`"ng untouched"`. If EVERY class on the element is framework-only,
|
|
104
|
+
it returns undefined and the caller falls through to
|
|
105
|
+
`<role|tag>Element` — for an Angular input with no other handles,
|
|
106
|
+
the synthesised field name becomes `inputElement` rather than
|
|
107
|
+
`ngUntouchedInput`.
|
|
108
|
+
|
|
109
|
+
2. **CSS-fallback gate.** `pickLocator`'s step-6 CSS fallback now
|
|
110
|
+
refuses framework-only selectors via `isFrameworkOnlySelector`.
|
|
111
|
+
When the element has no role / label / placeholder / testId /
|
|
112
|
+
text / xpath and the only CSS handle is something like
|
|
113
|
+
`.ng-untouched.ng-pristine`, we emit a tag-only locator
|
|
114
|
+
(`page.locator("input")`) and flag it `confidence: "fallback"`.
|
|
115
|
+
Tag-only is non-ideal but at least matches a real DOM element
|
|
116
|
+
instead of a transient state class.
|
|
117
|
+
|
|
118
|
+
New exports (both pure, easy to compose into future emitter rules):
|
|
119
|
+
- `isFrameworkClass(cls: string): boolean`
|
|
120
|
+
- `isFrameworkOnlySelector(selector: string | undefined): boolean`
|
|
121
|
+
|
|
122
|
+
Detection covered: `ng-*`, `mat-*`, `cdk-*`, `mdc-*`,
|
|
123
|
+
`_ngcontent-*`, `_nghost-*`. Hyphen-after-prefix is required so we
|
|
124
|
+
don't accidentally flag user-named classes like `nginx-banner` or
|
|
125
|
+
`ngo-button`.
|
|
126
|
+
|
|
127
|
+
### Tests
|
|
128
|
+
|
|
129
|
+
`tests/unit/locatorPicker.test.ts` — full coverage for BUG-10:
|
|
130
|
+
- `isFrameworkClass` matches every prefix, leaves user classes
|
|
131
|
+
alone (including the deliberate near-misses `nginx-banner`,
|
|
132
|
+
`ngo-button`, `material-card`).
|
|
133
|
+
- `isFrameworkOnlySelector` flags pure framework selectors,
|
|
134
|
+
declines mixed selectors, declines `#id`-bearing selectors, declines
|
|
135
|
+
non-class selectors.
|
|
136
|
+
- `pickLocator` drops a framework-only CSS selector and falls back
|
|
137
|
+
to tag-only.
|
|
138
|
+
- `pickLocator` preserves selectors that have at least one
|
|
139
|
+
user-named class.
|
|
140
|
+
- `synthFieldName` skips framework classes and falls through to
|
|
141
|
+
`<role|tag>Element`.
|
|
142
|
+
- `synthFieldName` picks the user-named class in a mixed selector.
|
|
143
|
+
|
|
12
144
|
## [2.2.5] — 2026-05-11
|
|
13
145
|
|
|
14
146
|
### Fixed — BUG-9 (P0): POM field name starting with a digit broke entire spec
|
|
@@ -16,6 +16,42 @@ 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;
|
|
31
|
+
/**
|
|
32
|
+
* v2.2.7 — BUG-10 follow-up. Strip framework-internal `.classname`
|
|
33
|
+
* tokens from a CSS selector string, leaving everything else intact.
|
|
34
|
+
*
|
|
35
|
+
* The v2.2.6 fix prevented framework-ONLY selectors from being picked,
|
|
36
|
+
* but a mixed selector like `.ng-untouched.search-input` still made it
|
|
37
|
+
* through with the `.ng-untouched` token in place. Playwright's
|
|
38
|
+
* `page.locator('.ng-untouched.search-input')` requires BOTH classes
|
|
39
|
+
* simultaneously — so when the user types and Angular flips
|
|
40
|
+
* `.ng-untouched` → `.ng-touched`, the locator stops matching. The
|
|
41
|
+
* test fails on what should have been a stable user-named element.
|
|
42
|
+
*
|
|
43
|
+
* After stripping, callers should treat the result as the new selector
|
|
44
|
+
* to emit. If stripping leaves nothing useful (`""` or just whitespace
|
|
45
|
+
* combinators), the caller should fall through to the next priority
|
|
46
|
+
* (xpath / tag-only).
|
|
47
|
+
*
|
|
48
|
+
* Implementation notes:
|
|
49
|
+
* - Only strips bare `.classname` tokens. `:not(.ng-foo)` and
|
|
50
|
+
* attribute selectors are left alone — they're rare and risky to
|
|
51
|
+
* rewrite.
|
|
52
|
+
* - Trims redundant whitespace introduced by the strip.
|
|
53
|
+
*/
|
|
54
|
+
export declare function stripFrameworkClasses(selector: string): string;
|
|
19
55
|
/**
|
|
20
56
|
* Dedupe a list of locator choices.
|
|
21
57
|
*
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.pickLocator = pickLocator;
|
|
15
|
+
exports.isFrameworkClass = isFrameworkClass;
|
|
16
|
+
exports.isFrameworkOnlySelector = isFrameworkOnlySelector;
|
|
17
|
+
exports.stripFrameworkClasses = stripFrameworkClasses;
|
|
15
18
|
exports.dedupeLocators = dedupeLocators;
|
|
16
19
|
const naming_1 = require("../utils/naming");
|
|
17
20
|
/**
|
|
@@ -85,15 +88,28 @@ function pickLocator(element, allElements) {
|
|
|
85
88
|
confidence: rivals.length === 0 ? "unique" : "ambiguous",
|
|
86
89
|
};
|
|
87
90
|
}
|
|
88
|
-
// 6. CSS selector
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
// 6. CSS selector — but reject framework-only class selectors
|
|
92
|
+
// (Angular's `.ng-untouched`, Material's `.mat-form-field`, etc.).
|
|
93
|
+
// These are runtime state markers that change as the user
|
|
94
|
+
// interacts with the page, so locators built on them are flaky.
|
|
95
|
+
// See BUG-10 / v2.2.6.
|
|
96
|
+
//
|
|
97
|
+
// v2.2.7 — for MIXED selectors like `.ng-untouched.search-input`,
|
|
98
|
+
// strip the framework tokens so we emit `.search-input` rather
|
|
99
|
+
// than the compound selector that requires the state class to
|
|
100
|
+
// still match. If stripping leaves an empty / whitespace-only
|
|
101
|
+
// string, fall through to xpath / tag-only.
|
|
102
|
+
if (element.cssSelector && !isFrameworkOnlySelector(element.cssSelector)) {
|
|
103
|
+
const stripped = stripFrameworkClasses(element.cssSelector);
|
|
104
|
+
if (stripped.length > 0) {
|
|
105
|
+
return {
|
|
106
|
+
api: "locator",
|
|
107
|
+
args: JSON.stringify(stripped),
|
|
108
|
+
fieldName,
|
|
109
|
+
source: element,
|
|
110
|
+
confidence: "fallback",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
97
113
|
}
|
|
98
114
|
// 7. xpath — last resort
|
|
99
115
|
if (element.xpath) {
|
|
@@ -105,7 +121,12 @@ function pickLocator(element, allElements) {
|
|
|
105
121
|
confidence: "fallback",
|
|
106
122
|
};
|
|
107
123
|
}
|
|
108
|
-
// Nothing usable — emit a tag-only locator and flag as fallback
|
|
124
|
+
// Nothing usable — emit a tag-only locator and flag as fallback.
|
|
125
|
+
// v2.2.6 — if we got here because the only CSS selector was
|
|
126
|
+
// framework-internal (and there was no role / label / placeholder /
|
|
127
|
+
// testId / text / xpath), the tag-only fallback is the least-bad
|
|
128
|
+
// option. It at least matches a visible DOM element rather than a
|
|
129
|
+
// transient state class.
|
|
109
130
|
return {
|
|
110
131
|
api: "locator",
|
|
111
132
|
args: JSON.stringify(element.tag),
|
|
@@ -127,6 +148,99 @@ function pickLocator(element, allElements) {
|
|
|
127
148
|
* `notificationBanner`) is what a test author would write by hand.
|
|
128
149
|
*/
|
|
129
150
|
const STATUS_REGION_RE = /(error|alert|message|warning|success|notification|status)/i;
|
|
151
|
+
/**
|
|
152
|
+
* v2.2.6 — BUG-10. CSS class prefixes that frameworks generate at runtime
|
|
153
|
+
* for their own bookkeeping (form-state, view-encapsulation, theming,
|
|
154
|
+
* etc.). These classes:
|
|
155
|
+
*
|
|
156
|
+
* - Change at runtime (`.ng-untouched` → `.ng-touched` the moment the
|
|
157
|
+
* user types), so selecting by them gives flaky locators.
|
|
158
|
+
* - Are not user-facing — no test author would write
|
|
159
|
+
* `await expect(page.locator('.ng-untouched')).toBeVisible()` by hand.
|
|
160
|
+
* - Mass-collide across the DOM (every Angular input has the same
|
|
161
|
+
* state classes), so locator-by-class is non-unique anyway.
|
|
162
|
+
*
|
|
163
|
+
* Covered prefixes:
|
|
164
|
+
* - `ng-*` Angular core (ng-untouched, ng-pristine, ng-dirty, …)
|
|
165
|
+
* - `mat-*` Angular Material
|
|
166
|
+
* - `cdk-*` Angular CDK (overlay, focus-trap, etc.)
|
|
167
|
+
* - `mdc-*` Material Design Components (web)
|
|
168
|
+
* - `_ngcontent-*` Angular view encapsulation attribute-like classes
|
|
169
|
+
* - `_nghost-*` Angular view encapsulation host markers
|
|
170
|
+
*
|
|
171
|
+
* Hyphen is required after the prefix so we don't accidentally exclude
|
|
172
|
+
* legitimate classes like `ngo-button` (user-named) or `cdkit` (rare but
|
|
173
|
+
* possible). The `_ngcontent-` / `_nghost-` patterns are handled with an
|
|
174
|
+
* explicit underscore prefix.
|
|
175
|
+
*/
|
|
176
|
+
const FRAMEWORK_CLASS_RE = /^(?:ng-|mat-|cdk-|mdc-|_ngcontent-|_nghost-)/i;
|
|
177
|
+
/** True when `cls` looks like a framework-internal class. */
|
|
178
|
+
function isFrameworkClass(cls) {
|
|
179
|
+
return FRAMEWORK_CLASS_RE.test(cls);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extract all bare class names from a CSS selector. Tolerant of compound
|
|
183
|
+
* selectors like `div.foo.bar#main` (returns `["foo", "bar"]`).
|
|
184
|
+
*/
|
|
185
|
+
function extractClasses(selector) {
|
|
186
|
+
const out = [];
|
|
187
|
+
const re = /\.([a-zA-Z_][\w-]*)/g;
|
|
188
|
+
let m;
|
|
189
|
+
while ((m = re.exec(selector)) !== null)
|
|
190
|
+
out.push(m[1]);
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* True when `selector` reduces to *only* framework-internal classes
|
|
195
|
+
* (e.g. `.ng-untouched.ng-pristine`). Such selectors are unstable
|
|
196
|
+
* runtime markers and must not be used as Playwright locators.
|
|
197
|
+
*
|
|
198
|
+
* Selectors with at least one non-framework class
|
|
199
|
+
* (`.ng-untouched.search-input`) are NOT flagged — the non-framework
|
|
200
|
+
* class can still drive a useful locator.
|
|
201
|
+
*/
|
|
202
|
+
function isFrameworkOnlySelector(selector) {
|
|
203
|
+
if (!selector)
|
|
204
|
+
return false;
|
|
205
|
+
// If there's an #id, we can't claim "framework only" — id is independent.
|
|
206
|
+
if (/#[a-zA-Z][\w-]*/.test(selector))
|
|
207
|
+
return false;
|
|
208
|
+
const classes = extractClasses(selector);
|
|
209
|
+
if (classes.length === 0)
|
|
210
|
+
return false; // not a class-based selector
|
|
211
|
+
return classes.every(isFrameworkClass);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* v2.2.7 — BUG-10 follow-up. Strip framework-internal `.classname`
|
|
215
|
+
* tokens from a CSS selector string, leaving everything else intact.
|
|
216
|
+
*
|
|
217
|
+
* The v2.2.6 fix prevented framework-ONLY selectors from being picked,
|
|
218
|
+
* but a mixed selector like `.ng-untouched.search-input` still made it
|
|
219
|
+
* through with the `.ng-untouched` token in place. Playwright's
|
|
220
|
+
* `page.locator('.ng-untouched.search-input')` requires BOTH classes
|
|
221
|
+
* simultaneously — so when the user types and Angular flips
|
|
222
|
+
* `.ng-untouched` → `.ng-touched`, the locator stops matching. The
|
|
223
|
+
* test fails on what should have been a stable user-named element.
|
|
224
|
+
*
|
|
225
|
+
* After stripping, callers should treat the result as the new selector
|
|
226
|
+
* to emit. If stripping leaves nothing useful (`""` or just whitespace
|
|
227
|
+
* combinators), the caller should fall through to the next priority
|
|
228
|
+
* (xpath / tag-only).
|
|
229
|
+
*
|
|
230
|
+
* Implementation notes:
|
|
231
|
+
* - Only strips bare `.classname` tokens. `:not(.ng-foo)` and
|
|
232
|
+
* attribute selectors are left alone — they're rare and risky to
|
|
233
|
+
* rewrite.
|
|
234
|
+
* - Trims redundant whitespace introduced by the strip.
|
|
235
|
+
*/
|
|
236
|
+
function stripFrameworkClasses(selector) {
|
|
237
|
+
// Replace `.ng-foo` (and the rest) with empty string. The class-token
|
|
238
|
+
// regex matches `.` followed by an identifier, terminated by anything
|
|
239
|
+
// that isn't a class-name char (so `.foo.bar` is two matches, not one).
|
|
240
|
+
const out = selector.replace(/\.([a-zA-Z_][\w-]*)/g, (full, cls) => (isFrameworkClass(cls) ? "" : full));
|
|
241
|
+
// Collapse any whitespace produced by stripping.
|
|
242
|
+
return out.replace(/\s+/g, " ").trim();
|
|
243
|
+
}
|
|
130
244
|
/**
|
|
131
245
|
* Synthesise a camelCase TS field name for an element.
|
|
132
246
|
*
|
|
@@ -153,6 +267,18 @@ function synthFieldName(element) {
|
|
|
153
267
|
const cleaned = idDerived.replace(/[^a-zA-Z0-9 ]/g, " ").trim();
|
|
154
268
|
return (0, naming_1.toJsIdentifier)((0, naming_1.camelCase)(cleaned) || "errorRegion");
|
|
155
269
|
}
|
|
270
|
+
// v2.2.7 — track whether we had to fall through to the
|
|
271
|
+
// `<role|tag>Element` last-resort base. If so, the base ALREADY
|
|
272
|
+
// carries `Element` and we don't want to also append the role-suffix
|
|
273
|
+
// ("inputElement" + "Input" = "inputElementInput" — ugly and
|
|
274
|
+
// redundant). The previous endsWith check missed this because
|
|
275
|
+
// `"inputelement".endsWith("input")` is false.
|
|
276
|
+
const haveExplicitBase = element.name !== undefined ||
|
|
277
|
+
element.label !== undefined ||
|
|
278
|
+
element.placeholder !== undefined ||
|
|
279
|
+
element.testId !== undefined ||
|
|
280
|
+
element.text !== undefined ||
|
|
281
|
+
idDerived !== undefined;
|
|
156
282
|
const baseName = element.name ??
|
|
157
283
|
element.label ??
|
|
158
284
|
element.placeholder ??
|
|
@@ -162,9 +288,11 @@ function synthFieldName(element) {
|
|
|
162
288
|
`${element.role ?? element.tag}Element`;
|
|
163
289
|
const cleaned = baseName.replace(/[^a-zA-Z0-9 ]/g, " ").trim();
|
|
164
290
|
let fieldName = (0, naming_1.camelCase)(cleaned) || "element";
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
291
|
+
if (haveExplicitBase) {
|
|
292
|
+
const suffix = roleToSuffix(element.role, element.tag);
|
|
293
|
+
if (suffix && !fieldName.toLowerCase().endsWith(suffix.toLowerCase())) {
|
|
294
|
+
fieldName += suffix;
|
|
295
|
+
}
|
|
168
296
|
}
|
|
169
297
|
// v2.2.5 — BUG-9 guard: ensure the name is a valid JS identifier.
|
|
170
298
|
// `camelCase("0 of 0")` returns "0Of0", which is not a legal
|
|
@@ -175,10 +303,23 @@ function synthFieldName(element) {
|
|
|
175
303
|
}
|
|
176
304
|
/**
|
|
177
305
|
* Best-effort name extraction from a CSS selector. Handles common shapes:
|
|
178
|
-
* #username
|
|
179
|
-
* .foo-bar
|
|
180
|
-
* div#main
|
|
181
|
-
* a.wp-block-btn
|
|
306
|
+
* #username → "username"
|
|
307
|
+
* .foo-bar → "foo bar"
|
|
308
|
+
* div#main → "main"
|
|
309
|
+
* a.wp-block-btn → "wp block btn"
|
|
310
|
+
* input.ng-untouched.search-input → "search input" (v2.2.6)
|
|
311
|
+
*
|
|
312
|
+
* v2.2.6 — BUG-10. When the selector has multiple classes, skip
|
|
313
|
+
* framework-internal classes (`ng-*`, `mat-*`, `cdk-*`, `mdc-*`,
|
|
314
|
+
* `_ngcontent-*`, `_nghost-*`) and pick the first user-named class.
|
|
315
|
+
* Without this filter, juice-shop's `.ng-untouched.search-input` gets
|
|
316
|
+
* named "ng untouched" → `ngUntouchedInput`, which is a flaky state
|
|
317
|
+
* locator and a confusing field name.
|
|
318
|
+
*
|
|
319
|
+
* If EVERY class is framework-internal, returns undefined so the caller
|
|
320
|
+
* falls through to `<role|tag>Element` rather than naming the field
|
|
321
|
+
* after a transient state class.
|
|
322
|
+
*
|
|
182
323
|
* Returns undefined for selectors with no usable id/class.
|
|
183
324
|
*/
|
|
184
325
|
function cssSelectorToName(selector) {
|
|
@@ -188,9 +329,14 @@ function cssSelectorToName(selector) {
|
|
|
188
329
|
const idMatch = selector.match(/#([a-zA-Z][\w-]*)/);
|
|
189
330
|
if (idMatch)
|
|
190
331
|
return idMatch[1].replace(/-/g, " ");
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
332
|
+
// Walk all classes, return the first non-framework one.
|
|
333
|
+
const classes = extractClasses(selector);
|
|
334
|
+
for (const cls of classes) {
|
|
335
|
+
if (!isFrameworkClass(cls))
|
|
336
|
+
return cls.replace(/-/g, " ");
|
|
337
|
+
}
|
|
338
|
+
// All classes were framework-internal — fall through to the caller's
|
|
339
|
+
// next priority (typically `<role|tag>Element`).
|
|
194
340
|
return undefined;
|
|
195
341
|
}
|
|
196
342
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vijaypjavvadi/bdd2pw",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.7",
|
|
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"
|