@immense/vue-pom-generator 1.0.47 → 1.0.49
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/README.md +30 -11
- package/RELEASE_NOTES.md +20 -33
- package/class-generation/Pointer.ts +69 -10
- package/class-generation/index.ts +1421 -682
- package/dist/class-generation/Pointer.d.ts +1 -1
- package/dist/class-generation/Pointer.d.ts.map +1 -1
- package/dist/class-generation/index.d.ts +8 -0
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/index.cjs +1437 -689
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1440 -692
- package/dist/index.mjs.map +1 -1
- package/dist/manifest-generator.d.ts.map +1 -1
- package/dist/method-generation.d.ts +2 -0
- package/dist/method-generation.d.ts.map +1 -1
- package/dist/plugin/create-vue-pom-generator-plugins.d.ts.map +1 -1
- package/dist/plugin/support/build-plugin.d.ts +2 -1
- package/dist/plugin/support/build-plugin.d.ts.map +1 -1
- package/dist/plugin/support/dev-plugin.d.ts +2 -1
- package/dist/plugin/support/dev-plugin.d.ts.map +1 -1
- package/dist/plugin/support-plugins.d.ts +2 -1
- package/dist/plugin/support-plugins.d.ts.map +1 -1
- package/dist/plugin/types.d.ts +15 -7
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/tests/fixtures/generated-tsc/BasePage.full.d.ts +19 -0
- package/dist/tests/fixtures/generated-tsc/BasePage.full.d.ts.map +1 -0
- package/dist/tests/fixtures/generated-tsc/BasePage.minimal.d.ts +8 -0
- package/dist/tests/fixtures/generated-tsc/BasePage.minimal.d.ts.map +1 -0
- package/dist/tests/fixtures/generated-tsc/Pointer.d.ts +6 -0
- package/dist/tests/fixtures/generated-tsc/Pointer.d.ts.map +1 -0
- package/dist/typescript-codegen.d.ts +34 -0
- package/dist/typescript-codegen.d.ts.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`@immense/vue-pom-generator` is a Vite plugin for Vue 3 that does two compile-time jobs:
|
|
4
4
|
|
|
5
5
|
1. it injects a test-id attribute into interactive elements in `.vue` templates
|
|
6
|
-
2. it turns the collected ids into
|
|
6
|
+
2. it turns the collected ids into a Page Object Model library for Playwright, with aggregated or split TypeScript output, optional Playwright fixtures, and optional C# output
|
|
7
7
|
|
|
8
8
|
If you already use Playwright with `getByTestId`, the point is simple: this package removes the repetitive work of keeping test ids in sync with Vue templates and then hand-writing page objects around those ids.
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ If you already use Playwright with `getByTestId`, the point is simple: this pack
|
|
|
11
11
|
|
|
12
12
|
- **Injects test ids during Vue compilation, not at runtime.** It hooks into the Vue template compiler and rewrites the compiled template output.
|
|
13
13
|
- **Uses real template signals to name ids and methods.** Click handlers, `v-model`, `id`/`name`, `:to`, wrapper configuration, and a few targeted fallbacks all feed the generated API.
|
|
14
|
-
- **Generates one
|
|
14
|
+
- **Generates TypeScript POM output as either one aggregate or split per class, always with a stable `index.ts` barrel.**
|
|
15
15
|
- **Can generate Playwright fixtures** so tests can request `userListPage` instead of constructing `new UserListPage(page)` manually.
|
|
16
16
|
- **Can fail fast on unnameable wrapper-button actions** so complex inline handlers do not silently degrade into low-signal generated APIs.
|
|
17
17
|
- **Can emit a single C# POM file** for Playwright .NET consumers.
|
|
@@ -234,6 +234,7 @@ const pomConfig = defineVuePomGeneratorConfig({
|
|
|
234
234
|
},
|
|
235
235
|
playwright: {
|
|
236
236
|
fixtures: true,
|
|
237
|
+
outputStructure: "split",
|
|
237
238
|
customPoms: {
|
|
238
239
|
dir: "tests/playwright/pom/custom",
|
|
239
240
|
importAliases: {
|
|
@@ -294,9 +295,13 @@ By default, generation writes to `tests/playwright/__generated__`.
|
|
|
294
295
|
|
|
295
296
|
TypeScript output:
|
|
296
297
|
|
|
297
|
-
- `
|
|
298
|
-
- `
|
|
299
|
-
- `
|
|
298
|
+
- default (`generation.playwright.outputStructure: "aggregated"`):
|
|
299
|
+
- `page-object-models.g.ts` — aggregated generated classes
|
|
300
|
+
- `index.ts` — stable barrel re-exporting `page-object-models.g`
|
|
301
|
+
- split mode (`generation.playwright.outputStructure: "split"`):
|
|
302
|
+
- one `*.g.ts` file per generated class
|
|
303
|
+
- `index.ts` — stable barrel re-exporting the split files
|
|
304
|
+
- `_pom-runtime/` — copied runtime support files used by the generated TypeScript output in either mode
|
|
300
305
|
|
|
301
306
|
Optional Playwright fixture output:
|
|
302
307
|
|
|
@@ -313,7 +318,7 @@ If you emit outside a `__generated__` path, the generator also manages `.gitattr
|
|
|
313
318
|
|
|
314
319
|
This is important if you are deciding whether the tool will fit into a real codebase.
|
|
315
320
|
|
|
316
|
-
- **Dev server:** on startup, it scans the configured `scanDirs`, compiles each `.vue` file into a snapshot, writes the
|
|
321
|
+
- **Dev server:** on startup, it scans the configured `scanDirs`, compiles each `.vue` file into a snapshot, writes the configured TypeScript outputs once, then batches add/change/delete events and regenerates incrementally.
|
|
317
322
|
- **Build:** it generates from the richest build pass it sees, which matters because Vite can run multiple passes (for example SSR plus client). The generator avoids letting a thinner pass clobber a richer one.
|
|
318
323
|
- **Always-on virtual module:** `virtual:testids` is registered whether generation is enabled or disabled.
|
|
319
324
|
- **Generation can be disabled:** `generation: false` still keeps compile-time test-id injection and the virtual module, but skips emitted POM files.
|
|
@@ -383,7 +388,7 @@ These two directories solve different problems.
|
|
|
383
388
|
|
|
384
389
|
Default: `tests/playwright/pom/custom`
|
|
385
390
|
|
|
386
|
-
This directory is for handwritten helper classes that the
|
|
391
|
+
This directory is for handwritten helper classes that the generated TypeScript output can import.
|
|
387
392
|
|
|
388
393
|
It is the default helper directory even if you omit `generation.playwright.customPoms` entirely.
|
|
389
394
|
|
|
@@ -392,7 +397,7 @@ Actual current behavior:
|
|
|
392
397
|
- the generator scans the directory **non-recursively**
|
|
393
398
|
- it imports top-level `.ts` files only
|
|
394
399
|
- it expects the file basename to match the exported class name (`Grid.ts` → `export class Grid {}`)
|
|
395
|
-
- the helper files are
|
|
400
|
+
- the helper files are available to generated output; they are **not** automatically attached everywhere
|
|
396
401
|
|
|
397
402
|
What it is for:
|
|
398
403
|
|
|
@@ -434,7 +439,7 @@ A typical override looks like this:
|
|
|
434
439
|
|
|
435
440
|
```ts
|
|
436
441
|
// tests/playwright/pom/overrides/UserListPage.ts
|
|
437
|
-
import { UserListPage as GeneratedUserListPage } from "../../__generated__
|
|
442
|
+
import { UserListPage as GeneratedUserListPage } from "../../__generated__";
|
|
438
443
|
|
|
439
444
|
export class UserListPage extends GeneratedUserListPage {
|
|
440
445
|
async openFirstUser() {
|
|
@@ -449,7 +454,10 @@ This is where most people need precision.
|
|
|
449
454
|
|
|
450
455
|
### Helper imports
|
|
451
456
|
|
|
452
|
-
Every `.ts` file in `customPoms.dir`
|
|
457
|
+
Every `.ts` file in `customPoms.dir` is available for import from the generated TypeScript output.
|
|
458
|
+
|
|
459
|
+
- in aggregated mode, helper files become imports in the shared aggregate
|
|
460
|
+
- in split mode, each generated file imports only the helpers it actually needs
|
|
453
461
|
|
|
454
462
|
Benefits:
|
|
455
463
|
|
|
@@ -1037,6 +1045,17 @@ This object holds Playwright-specific additions on top of the generated TypeScri
|
|
|
1037
1045
|
- `"path"` — if the string ends in `.ts` / `.tsx` / `.mts` / `.cts`, it is treated as a file path; otherwise as an output directory
|
|
1038
1046
|
- `{ outDir }` — emit to a custom directory
|
|
1039
1047
|
|
|
1048
|
+
#### `generation.playwright.outputStructure`
|
|
1049
|
+
|
|
1050
|
+
- **What it does:** Chooses whether generated TypeScript POMs are emitted as one aggregate or split per class.
|
|
1051
|
+
- **Why it exists:** very large generated files are harder to browse, inspect, and discover in editors.
|
|
1052
|
+
- **Benefit:** `"split"` keeps the same stable barrel and fixtures surface while making individual classes and methods easier to find.
|
|
1053
|
+
- **Without it:** the default is `"aggregated"`.
|
|
1054
|
+
- **Accepted values:**
|
|
1055
|
+
- `"aggregated"` — emit `page-object-models.g.ts` plus `index.ts`
|
|
1056
|
+
- `"split"` — emit one `*.g.ts` file per class plus `index.ts`
|
|
1057
|
+
- **Current limit:** this setting only affects the generated TypeScript Playwright output; C# output remains a single aggregated file.
|
|
1058
|
+
|
|
1040
1059
|
#### `generation.playwright.customPoms`
|
|
1041
1060
|
|
|
1042
1061
|
- **What it does:** Configures handwritten helper imports and attachments.
|
|
@@ -1063,7 +1082,7 @@ This object holds Playwright-specific additions on top of the generated TypeScri
|
|
|
1063
1082
|
#### `customPoms.importNameCollisionBehavior`
|
|
1064
1083
|
|
|
1065
1084
|
- **What it does:** Controls how helper import names behave when they collide with generated class names.
|
|
1066
|
-
- **Why it exists:**
|
|
1085
|
+
- **Why it exists:** generated files still need a conflict-free local import namespace.
|
|
1067
1086
|
- **Benefit:** lets you fail hard or auto-alias based on team preference.
|
|
1068
1087
|
- **Without it:** the default is `"error"`.
|
|
1069
1088
|
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,49 +1,36 @@
|
|
|
1
|
-
●
|
|
1
|
+
● I'll gather the release data between v1.0.48 and HEAD to generate accurate release notes.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
at build time
|
|
5
|
-
- Added comprehensive validation logic across plugin lifecycle (dev and build modes)
|
|
6
|
-
- Expanded test coverage with 54+ new test cases for handler naming scenarios
|
|
7
|
-
- Enhanced error reporting when anonymous or unnameable handlers are detected
|
|
8
|
-
- Updated documentation with new validation requirements and best practices
|
|
3
|
+
● Based on the commit data, this is a focused release with one commit. Here are the release notes:
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
11
8
|
|
|
12
|
-
**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
error messages
|
|
17
|
-
- Added validation hooks in both `build-plugin.ts` and `dev-plugin.ts` for consistency across
|
|
18
|
-
modes
|
|
9
|
+
- **Enhanced Playwright visual debugging**: Cursor annotations now render properly during test
|
|
10
|
+
execution
|
|
11
|
+
- Improved test coverage with new Pointer test cases
|
|
12
|
+
- Better visual feedback for automated test runs
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
- Extended plugin types with new validation options and error handling interfaces
|
|
22
|
-
- Added support for configurable validation behavior in plugin options
|
|
14
|
+
## Changes
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
- Added 9 coverage tests in `utils-coverage.test.ts`
|
|
28
|
-
- Enhanced `dev-plugin-options.test.ts` with validation scenario tests
|
|
16
|
+
### Fixes
|
|
17
|
+
- **Render Playwright annotations**: Fixed annotation rendering in the visual cursor overlay
|
|
18
|
+
system, ensuring annotation text displays correctly during animated pointer movements
|
|
29
19
|
|
|
30
|
-
|
|
31
|
-
-
|
|
20
|
+
### Tests
|
|
21
|
+
- Added comprehensive test coverage for Pointer annotation functionality
|
|
32
22
|
|
|
33
23
|
## Breaking Changes
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
named (anonymous functions, computed property names, etc.). Code that previously compiled with
|
|
37
|
-
unnameable handlers will now fail at build time. Ensure all wrapper handlers have statically
|
|
38
|
-
determinable names.
|
|
25
|
+
None
|
|
39
26
|
|
|
40
27
|
## Pull Requests Included
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
Based on the commit in this release, this appears to be a direct commit rather than a merged PR
|
|
30
|
+
within the specified time window.
|
|
44
31
|
|
|
45
32
|
## Testing
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
- New test cases added to `tests/pointer.test.ts` validating annotation rendering
|
|
35
|
+
- Total test additions: 52 new lines of test code
|
|
49
36
|
|
|
@@ -5,6 +5,7 @@ import type { PwLocator, PwPage } from "./playwright-types";
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
7
|
const __PW_CURSOR_ID__ = "__pw_cursor__";
|
|
8
|
+
const __PW_CURSOR_ANNOTATION_ID__ = "__pw_cursor_annotation__";
|
|
8
9
|
const __PW_EDITABLE_DESCENDANT_SELECTOR__
|
|
9
10
|
= "input, textarea, select, [contenteditable=''], [contenteditable='true'], [contenteditable]:not([contenteditable='false'])";
|
|
10
11
|
|
|
@@ -34,8 +35,9 @@ function __pw_set_cursor_pos__(page: PwPage, x: number, y: number): void {
|
|
|
34
35
|
|
|
35
36
|
async function __pw_ensure_cursor__(page: PwPage): Promise<void> {
|
|
36
37
|
const exists = await page.evaluate(
|
|
37
|
-
(
|
|
38
|
-
|
|
38
|
+
({ cursorId, annotationId }: { cursorId: string; annotationId: string }) =>
|
|
39
|
+
document.getElementById(cursorId) != null && document.getElementById(annotationId) != null,
|
|
40
|
+
{ cursorId: __PW_CURSOR_ID__, annotationId: __PW_CURSOR_ANNOTATION_ID__ },
|
|
39
41
|
);
|
|
40
42
|
if (exists) return;
|
|
41
43
|
|
|
@@ -43,7 +45,7 @@ async function __pw_ensure_cursor__(page: PwPage): Promise<void> {
|
|
|
43
45
|
__pw_set_cursor_pos__(page, 0, 0);
|
|
44
46
|
|
|
45
47
|
await page.evaluate(
|
|
46
|
-
({ id, src }: { id: string; src: string }) => {
|
|
48
|
+
({ id, src, annotationId }: { id: string; src: string; annotationId: string }) => {
|
|
47
49
|
const img = document.createElement("img");
|
|
48
50
|
img.setAttribute("src", src);
|
|
49
51
|
img.setAttribute("id", id);
|
|
@@ -53,8 +55,32 @@ async function __pw_ensure_cursor__(page: PwPage): Promise<void> {
|
|
|
53
55
|
"position:fixed;z-index:2147483647;pointer-events:none;left:0;top:0;transform-origin:0 0;",
|
|
54
56
|
);
|
|
55
57
|
document.body.appendChild(img);
|
|
58
|
+
|
|
59
|
+
const annotation = document.createElement("div");
|
|
60
|
+
annotation.setAttribute("id", annotationId);
|
|
61
|
+
annotation.setAttribute(
|
|
62
|
+
"style",
|
|
63
|
+
[
|
|
64
|
+
"position:fixed",
|
|
65
|
+
"z-index:2147483647",
|
|
66
|
+
"pointer-events:none",
|
|
67
|
+
"left:18px",
|
|
68
|
+
"top:18px",
|
|
69
|
+
"max-width:320px",
|
|
70
|
+
"padding:6px 10px",
|
|
71
|
+
"border-radius:999px",
|
|
72
|
+
"background:rgba(15,23,42,0.92)",
|
|
73
|
+
"color:#f8fafc",
|
|
74
|
+
"font:600 13px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
75
|
+
"box-shadow:0 12px 30px rgba(15,23,42,0.35)",
|
|
76
|
+
"opacity:0",
|
|
77
|
+
"white-space:normal",
|
|
78
|
+
"transform:translate3d(0,0,0)",
|
|
79
|
+
].join(";"),
|
|
80
|
+
);
|
|
81
|
+
document.body.appendChild(annotation);
|
|
56
82
|
},
|
|
57
|
-
{ id: __PW_CURSOR_ID__, src: __PW_CURSOR_PNG__ },
|
|
83
|
+
{ id: __PW_CURSOR_ID__, src: __PW_CURSOR_PNG__, annotationId: __PW_CURSOR_ANNOTATION_ID__ },
|
|
58
84
|
);
|
|
59
85
|
}
|
|
60
86
|
|
|
@@ -209,7 +235,7 @@ export class Pointer {
|
|
|
209
235
|
target: ElementTarget,
|
|
210
236
|
executeClick: boolean = true,
|
|
211
237
|
delayMs: number = 100,
|
|
212
|
-
|
|
238
|
+
annotationText: string = "",
|
|
213
239
|
options?: {
|
|
214
240
|
afterClick?: AfterPointerClick;
|
|
215
241
|
},
|
|
@@ -248,6 +274,7 @@ export class Pointer {
|
|
|
248
274
|
const clickDelayMs = opts.pointer?.clickDelayMilliseconds ?? 0;
|
|
249
275
|
const extraDelayMs = Math.max(0, opts.extraDelayMs ?? 0);
|
|
250
276
|
const actionDelayMs = Math.max(0, delayMs);
|
|
277
|
+
const trimmedAnnotationText = annotationText.trim();
|
|
251
278
|
|
|
252
279
|
// Inject the visual cursor if it doesn't exist yet.
|
|
253
280
|
await __pw_ensure_cursor__(this.page);
|
|
@@ -263,22 +290,47 @@ export class Pointer {
|
|
|
263
290
|
if (moveDurationMs > 0 && distance > 0) {
|
|
264
291
|
// Glide the cursor image using a CSS transition.
|
|
265
292
|
await this.page.evaluate(
|
|
266
|
-
({ id, sx, sy, ex, ey, dur, style }: {
|
|
267
|
-
id: string; sx: number; sy: number; ex: number; ey: number; dur: number; style: string;
|
|
293
|
+
({ id, annotationId, sx, sy, ex, ey, dur, style, annotationText }: {
|
|
294
|
+
id: string; annotationId: string; sx: number; sy: number; ex: number; ey: number; dur: number; style: string; annotationText: string;
|
|
268
295
|
}) => {
|
|
269
296
|
const el = document.getElementById(id);
|
|
297
|
+
const annotation = document.getElementById(annotationId);
|
|
270
298
|
if (!el) return;
|
|
271
299
|
el.style.transition = "";
|
|
272
300
|
el.style.willChange = "left, top";
|
|
273
301
|
el.style.left = `${sx}px`;
|
|
274
302
|
el.style.top = `${sy}px`;
|
|
303
|
+
if (annotation) {
|
|
304
|
+
annotation.textContent = annotationText;
|
|
305
|
+
annotation.style.transition = "";
|
|
306
|
+
annotation.style.willChange = "left, top, opacity";
|
|
307
|
+
annotation.style.left = `${sx + 18}px`;
|
|
308
|
+
annotation.style.top = `${sy + 22}px`;
|
|
309
|
+
annotation.style.opacity = annotationText ? "1" : "0";
|
|
310
|
+
}
|
|
275
311
|
// Force reflow so the browser registers the start position before transitioning.
|
|
276
312
|
void el.offsetWidth;
|
|
313
|
+
void annotation?.offsetWidth;
|
|
277
314
|
el.style.transition = `left ${dur}ms ${style}, top ${dur}ms ${style}`;
|
|
278
315
|
el.style.left = `${ex}px`;
|
|
279
316
|
el.style.top = `${ey}px`;
|
|
317
|
+
if (annotation) {
|
|
318
|
+
annotation.style.transition = `left ${dur}ms ${style}, top ${dur}ms ${style}, opacity 120ms ease-in-out`;
|
|
319
|
+
annotation.style.left = `${ex + 18}px`;
|
|
320
|
+
annotation.style.top = `${ey + 22}px`;
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: __PW_CURSOR_ID__,
|
|
325
|
+
annotationId: __PW_CURSOR_ANNOTATION_ID__,
|
|
326
|
+
sx: startX,
|
|
327
|
+
sy: startY,
|
|
328
|
+
ex: endX,
|
|
329
|
+
ey: endY,
|
|
330
|
+
dur: moveDurationMs,
|
|
331
|
+
style: transitionStyle,
|
|
332
|
+
annotationText: trimmedAnnotationText,
|
|
280
333
|
},
|
|
281
|
-
{ id: __PW_CURSOR_ID__, sx: startX, sy: startY, ex: endX, ey: endY, dur: moveDurationMs, style: transitionStyle },
|
|
282
334
|
);
|
|
283
335
|
// Wait for the animation to finish.
|
|
284
336
|
await this.page.waitForTimeout(moveDurationMs + 25);
|
|
@@ -286,11 +338,18 @@ export class Pointer {
|
|
|
286
338
|
else {
|
|
287
339
|
// Teleport (distance 0 or duration 0).
|
|
288
340
|
await this.page.evaluate(
|
|
289
|
-
({ id, x, y }: { id: string; x: number; y: number }) => {
|
|
341
|
+
({ id, annotationId, x, y, annotationText }: { id: string; annotationId: string; x: number; y: number; annotationText: string }) => {
|
|
290
342
|
const el = document.getElementById(id);
|
|
291
343
|
if (el) { el.style.left = `${x}px`; el.style.top = `${y}px`; }
|
|
344
|
+
const annotation = document.getElementById(annotationId);
|
|
345
|
+
if (annotation) {
|
|
346
|
+
annotation.textContent = annotationText;
|
|
347
|
+
annotation.style.left = `${x + 18}px`;
|
|
348
|
+
annotation.style.top = `${y + 22}px`;
|
|
349
|
+
annotation.style.opacity = annotationText ? "1" : "0";
|
|
350
|
+
}
|
|
292
351
|
},
|
|
293
|
-
{ id: __PW_CURSOR_ID__, x: endX, y: endY },
|
|
352
|
+
{ id: __PW_CURSOR_ID__, annotationId: __PW_CURSOR_ANNOTATION_ID__, x: endX, y: endY, annotationText: trimmedAnnotationText },
|
|
294
353
|
);
|
|
295
354
|
}
|
|
296
355
|
|