@immense/vue-pom-generator 1.0.43 → 1.0.44
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/RELEASE_NOTES.md +14 -18
- package/class-generation/Pointer.ts +43 -2
- package/class-generation/index.ts +27 -4
- package/dist/class-generation/Pointer.d.ts +2 -0
- package/dist/class-generation/Pointer.d.ts.map +1 -1
- package/dist/index.cjs +26 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +26 -3
- package/dist/index.mjs.map +1 -1
- package/dist/plugin/types.d.ts +1 -1
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/tests/pointer.test.d.ts +2 -0
- package/dist/tests/pointer.test.d.ts.map +1 -0
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
● ## Highlights
|
|
2
2
|
|
|
3
|
-
- Fixed
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
|
|
3
|
+
- Fixed handling of wrapped editable targets in class generation
|
|
4
|
+
- Enhanced Pointer module to correctly resolve targets through wrapper elements
|
|
5
|
+
- Added comprehensive test suite for pointer functionality (152 new test lines)
|
|
6
|
+
- Improved test coverage for class generation edge cases
|
|
7
7
|
|
|
8
8
|
## Changes
|
|
9
9
|
|
|
10
10
|
**Bug Fixes**
|
|
11
|
-
-
|
|
11
|
+
- Handle wrapped editable targets correctly in Pointer resolution logic
|
|
12
|
+
- Updated class generation index to support wrapped target elements
|
|
12
13
|
|
|
13
|
-
**Testing
|
|
14
|
-
- Added
|
|
15
|
-
-
|
|
14
|
+
**Testing**
|
|
15
|
+
- Added new `tests/pointer.test.ts` with comprehensive pointer test coverage
|
|
16
|
+
- Expanded `tests/class-generation-coverage.test.ts` with 43 additional test lines
|
|
16
17
|
|
|
17
|
-
**
|
|
18
|
-
-
|
|
19
|
-
- Enhanced dev plugin to match build-mode behavior
|
|
20
|
-
- Updated support plugins for better parity handling
|
|
18
|
+
**Type Updates**
|
|
19
|
+
- Minor update to `plugin/types.ts`
|
|
21
20
|
|
|
22
21
|
## Breaking Changes
|
|
23
22
|
|
|
@@ -25,13 +24,10 @@
|
|
|
25
24
|
|
|
26
25
|
## Pull Requests Included
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
regression tests (by @mayfieldiv)
|
|
30
|
-
- [#5](https://github.com/immense/vue-pom-generator/pull/5) fix: dev-mode POM generation parity
|
|
31
|
-
with build mode (by @mayfieldiv)
|
|
27
|
+
None — this release contains direct commits only.
|
|
32
28
|
|
|
33
29
|
## Testing
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
Significant test coverage added: new dedicated pointer test suite (152 lines) and expanded class
|
|
32
|
+
generation coverage tests (43 lines).
|
|
37
33
|
|
|
@@ -5,6 +5,8 @@ import type { PwLocator, PwPage } from "./playwright-types";
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
7
|
const __PW_CURSOR_ID__ = "__pw_cursor__";
|
|
8
|
+
const __PW_EDITABLE_DESCENDANT_SELECTOR__
|
|
9
|
+
= "input, textarea, select, [contenteditable=''], [contenteditable='true'], [contenteditable]:not([contenteditable='false'])";
|
|
8
10
|
|
|
9
11
|
// A minimal 16×24 arrow cursor encoded as a base64 PNG.
|
|
10
12
|
const __PW_CURSOR_PNG__ =
|
|
@@ -165,6 +167,44 @@ export class Pointer {
|
|
|
165
167
|
return trimmed || undefined;
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
private async isEditableElement(locator: PwLocator): Promise<boolean> {
|
|
171
|
+
try {
|
|
172
|
+
return await locator.first().evaluate((element) => {
|
|
173
|
+
if (!(element instanceof HTMLElement)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const tagName = element.tagName.toLowerCase();
|
|
178
|
+
return tagName === "input"
|
|
179
|
+
|| tagName === "textarea"
|
|
180
|
+
|| tagName === "select"
|
|
181
|
+
|| element.isContentEditable;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async resolveEditableLocator(locator: PwLocator): Promise<PwLocator> {
|
|
190
|
+
const first = locator.first();
|
|
191
|
+
if (await this.isEditableElement(first)) {
|
|
192
|
+
return first;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const descendant = first.locator(__PW_EDITABLE_DESCENDANT_SELECTOR__).first();
|
|
196
|
+
try {
|
|
197
|
+
if (await descendant.count() > 0) {
|
|
198
|
+
return descendant;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Fall back to the original target if descendant lookup fails.
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return first;
|
|
206
|
+
}
|
|
207
|
+
|
|
168
208
|
public async animateCursorToElement(
|
|
169
209
|
target: ElementTarget,
|
|
170
210
|
executeClick: boolean = true,
|
|
@@ -305,15 +345,16 @@ export class Pointer {
|
|
|
305
345
|
await this.animateCursorToElement(target, executeClick, delayMs, annotationText, options);
|
|
306
346
|
|
|
307
347
|
const locator = this.toLocator(target);
|
|
348
|
+
const editableLocator = await this.resolveEditableLocator(locator);
|
|
308
349
|
const typeDelayMs = animationOptions.keyboard?.typeDelayMilliseconds ?? 100;
|
|
309
350
|
|
|
310
351
|
if (animationOptions.enabled !== false && typeDelayMs > 0) {
|
|
311
352
|
// Clear existing content, then type character-by-character so keystrokes are visible.
|
|
312
|
-
await
|
|
353
|
+
await editableLocator.clear();
|
|
313
354
|
await this.page.keyboard.type(text, { delay: typeDelayMs });
|
|
314
355
|
}
|
|
315
356
|
else {
|
|
316
|
-
await
|
|
357
|
+
await editableLocator.fill(text);
|
|
317
358
|
}
|
|
318
359
|
}
|
|
319
360
|
}
|
|
@@ -758,6 +758,26 @@ function generateAggregatedCSharpFiles(
|
|
|
758
758
|
" protected IPage Page { get; }",
|
|
759
759
|
` protected ILocator LocatorByTestId(string testId) => Page.Locator($"[${testIdAttribute}=\\"{testId}\\"]");`,
|
|
760
760
|
" protected ILocator LocatorWithinTestIdByLabel(string rootTestId, string label, bool exact = true) => LocatorByTestId(rootTestId).GetByLabel(label, new() { Exact = exact });",
|
|
761
|
+
" protected async Task<ILocator> ResolveEditableLocatorAsync(ILocator locator)",
|
|
762
|
+
" {",
|
|
763
|
+
" var isEditable = await locator.EvaluateAsync<bool>(@\"el => {",
|
|
764
|
+
" if (!el || !(el instanceof HTMLElement)) return false;",
|
|
765
|
+
" const tagName = el.tagName.toLowerCase();",
|
|
766
|
+
" return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || el.isContentEditable;",
|
|
767
|
+
" }\");",
|
|
768
|
+
" if (isEditable)",
|
|
769
|
+
" {",
|
|
770
|
+
" return locator;",
|
|
771
|
+
" }",
|
|
772
|
+
"",
|
|
773
|
+
" var descendant = locator.Locator(\"input, textarea, select, [contenteditable=''], [contenteditable='true'], [contenteditable]:not([contenteditable='false'])\").First;",
|
|
774
|
+
" if (await descendant.CountAsync() > 0)",
|
|
775
|
+
" {",
|
|
776
|
+
" return descendant;",
|
|
777
|
+
" }",
|
|
778
|
+
"",
|
|
779
|
+
" return locator;",
|
|
780
|
+
" }",
|
|
761
781
|
" protected async Task ClickWithinTestIdByLabelAsync(string rootTestId, string label, bool exact = true)",
|
|
762
782
|
" {",
|
|
763
783
|
" await LocatorWithinTestIdByLabel(rootTestId, label, exact).ClickAsync();",
|
|
@@ -889,7 +909,8 @@ function generateAggregatedCSharpFiles(
|
|
|
889
909
|
|
|
890
910
|
const emitActionCall = (locatorAccess: string) => {
|
|
891
911
|
if (pom.nativeRole === "input") {
|
|
892
|
-
chunks.push(` await ${locatorAccess}
|
|
912
|
+
chunks.push(` var editableLocator = await ResolveEditableLocatorAsync(${locatorAccess});`);
|
|
913
|
+
chunks.push(" await editableLocator.FillAsync(text);");
|
|
893
914
|
}
|
|
894
915
|
else if (pom.nativeRole === "select") {
|
|
895
916
|
chunks.push(` await ${locatorAccess}.SelectOptionAsync(value);`);
|
|
@@ -923,7 +944,8 @@ function generateAggregatedCSharpFiles(
|
|
|
923
944
|
chunks.push(" if (await locator.CountAsync() > 0)");
|
|
924
945
|
chunks.push(" {");
|
|
925
946
|
if (pom.nativeRole === "input") {
|
|
926
|
-
chunks.push(" await locator
|
|
947
|
+
chunks.push(" var editableLocator = await ResolveEditableLocatorAsync(locator);");
|
|
948
|
+
chunks.push(" await editableLocator.FillAsync(text);");
|
|
927
949
|
}
|
|
928
950
|
else if (pom.nativeRole === "select") {
|
|
929
951
|
chunks.push(" await locator.SelectOptionAsync(value);");
|
|
@@ -1229,9 +1251,10 @@ function generateViewObjectModelContent(
|
|
|
1229
1251
|
return false;
|
|
1230
1252
|
|
|
1231
1253
|
const scope = a.attachTo ?? "views";
|
|
1254
|
+
const scopeMatchesBoth = scope === "both" || scope === "pagesAndComponents";
|
|
1232
1255
|
const scopeOk = isView
|
|
1233
|
-
? (scope === "views" ||
|
|
1234
|
-
: (scope === "components" ||
|
|
1256
|
+
? (scope === "views" || scopeMatchesBoth)
|
|
1257
|
+
: (scope === "components" || scopeMatchesBoth);
|
|
1235
1258
|
if (!scopeOk)
|
|
1236
1259
|
return false;
|
|
1237
1260
|
return a.attachWhenUsesComponents.some(c => hasChildComponent(c));
|
|
@@ -56,6 +56,8 @@ export declare class Pointer {
|
|
|
56
56
|
constructor(page: PwPage, testIdAttribute: string);
|
|
57
57
|
private toLocator;
|
|
58
58
|
private getTestId;
|
|
59
|
+
private isEditableElement;
|
|
60
|
+
private resolveEditableLocator;
|
|
59
61
|
animateCursorToElement(target: ElementTarget, executeClick?: boolean, delayMs?: number, _annotationText?: string, options?: {
|
|
60
62
|
afterClick?: AfterPointerClick;
|
|
61
63
|
}): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Pointer.d.ts","sourceRoot":"","sources":["../../class-generation/Pointer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"Pointer.d.ts","sourceRoot":"","sources":["../../class-generation/Pointer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgE5D,MAAM,WAAW,0BAA0B;IAC1C;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,sDAAsD;IACtD,OAAO,CAAC,EAAE;QACT;;;;WAIG;QACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAE9B;;;WAGG;QACH,eAAe,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;QAE7E;;;WAGG;QACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;IAEF,uCAAuC;IACvC,QAAQ,CAAC,EAAE;QACV;;;WAGG;QACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;CACF;AASD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,0BAA0B,GAAG,IAAI,CAavF;AAED,MAAM,WAAW,qBAAqB;IACrC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEtF,KAAK,aAAa,GAAG,MAAM,GAAG,SAAS,CAAC;AAMxC,qBAAa,OAAO;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAEtB,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM;IAKxD,OAAO,CAAC,SAAS;YAIH,SAAS;YAMT,iBAAiB;YAmBjB,sBAAsB;IAmBvB,sBAAsB,CAClC,MAAM,EAAE,aAAa,EACrB,YAAY,GAAE,OAAc,EAC5B,OAAO,GAAE,MAAY,EACrB,eAAe,GAAE,MAAW,EAC5B,OAAO,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,iBAAiB,CAAC;KAC/B,GACC,OAAO,CAAC,IAAI,CAAC;IAsHH,qCAAqC,CACjD,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,OAAc,EAC5B,OAAO,GAAE,MAAY,EACrB,cAAc,GAAE,MAAW,EAC3B,OAAO,CAAC,EAAE;QACT,UAAU,CAAC,EAAE,iBAAiB,CAAC;KAC/B,GACC,OAAO,CAAC,IAAI,CAAC;CAiBhB"}
|
package/dist/index.cjs
CHANGED
|
@@ -3712,6 +3712,26 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
3712
3712
|
" protected IPage Page { get; }",
|
|
3713
3713
|
` protected ILocator LocatorByTestId(string testId) => Page.Locator($"[${testIdAttribute}=\\"{testId}\\"]");`,
|
|
3714
3714
|
" protected ILocator LocatorWithinTestIdByLabel(string rootTestId, string label, bool exact = true) => LocatorByTestId(rootTestId).GetByLabel(label, new() { Exact = exact });",
|
|
3715
|
+
" protected async Task<ILocator> ResolveEditableLocatorAsync(ILocator locator)",
|
|
3716
|
+
" {",
|
|
3717
|
+
' var isEditable = await locator.EvaluateAsync<bool>(@"el => {',
|
|
3718
|
+
" if (!el || !(el instanceof HTMLElement)) return false;",
|
|
3719
|
+
" const tagName = el.tagName.toLowerCase();",
|
|
3720
|
+
" return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || el.isContentEditable;",
|
|
3721
|
+
' }");',
|
|
3722
|
+
" if (isEditable)",
|
|
3723
|
+
" {",
|
|
3724
|
+
" return locator;",
|
|
3725
|
+
" }",
|
|
3726
|
+
"",
|
|
3727
|
+
` var descendant = locator.Locator("input, textarea, select, [contenteditable=''], [contenteditable='true'], [contenteditable]:not([contenteditable='false'])").First;`,
|
|
3728
|
+
" if (await descendant.CountAsync() > 0)",
|
|
3729
|
+
" {",
|
|
3730
|
+
" return descendant;",
|
|
3731
|
+
" }",
|
|
3732
|
+
"",
|
|
3733
|
+
" return locator;",
|
|
3734
|
+
" }",
|
|
3715
3735
|
" protected async Task ClickWithinTestIdByLabelAsync(string rootTestId, string label, bool exact = true)",
|
|
3716
3736
|
" {",
|
|
3717
3737
|
" await LocatorWithinTestIdByLabel(rootTestId, label, exact).ClickAsync();",
|
|
@@ -3813,7 +3833,8 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
3813
3833
|
const callSuffix = pom.formattedDataTestId.includes("${") ? `(${args})` : "";
|
|
3814
3834
|
const emitActionCall = (locatorAccess) => {
|
|
3815
3835
|
if (pom.nativeRole === "input") {
|
|
3816
|
-
chunks.push(` await ${locatorAccess}
|
|
3836
|
+
chunks.push(` var editableLocator = await ResolveEditableLocatorAsync(${locatorAccess});`);
|
|
3837
|
+
chunks.push(" await editableLocator.FillAsync(text);");
|
|
3817
3838
|
} else if (pom.nativeRole === "select") {
|
|
3818
3839
|
chunks.push(` await ${locatorAccess}.SelectOptionAsync(value);`);
|
|
3819
3840
|
} else if (pom.nativeRole === "vselect") {
|
|
@@ -3841,7 +3862,8 @@ function generateAggregatedCSharpFiles(componentHierarchyMap, outDir, options =
|
|
|
3841
3862
|
chunks.push(" if (await locator.CountAsync() > 0)");
|
|
3842
3863
|
chunks.push(" {");
|
|
3843
3864
|
if (pom.nativeRole === "input") {
|
|
3844
|
-
chunks.push(" await locator
|
|
3865
|
+
chunks.push(" var editableLocator = await ResolveEditableLocatorAsync(locator);");
|
|
3866
|
+
chunks.push(" await editableLocator.FillAsync(text);");
|
|
3845
3867
|
} else if (pom.nativeRole === "select") {
|
|
3846
3868
|
chunks.push(" await locator.SelectOptionAsync(value);");
|
|
3847
3869
|
} else {
|
|
@@ -4065,7 +4087,8 @@ function generateViewObjectModelContent(componentName, dependencies, componentHi
|
|
|
4065
4087
|
if (!Object.prototype.hasOwnProperty.call(customPomClassIdentifierMap, a.className))
|
|
4066
4088
|
return false;
|
|
4067
4089
|
const scope = a.attachTo ?? "views";
|
|
4068
|
-
const
|
|
4090
|
+
const scopeMatchesBoth = scope === "both" || scope === "pagesAndComponents";
|
|
4091
|
+
const scopeOk = isView ? scope === "views" || scopeMatchesBoth : scope === "components" || scopeMatchesBoth;
|
|
4069
4092
|
if (!scopeOk)
|
|
4070
4093
|
return false;
|
|
4071
4094
|
return a.attachWhenUsesComponents.some((c) => hasChildComponent(c));
|