@immense/vue-pom-generator 1.0.43 → 1.0.45

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 CHANGED
@@ -15,7 +15,7 @@ If you already use Playwright with `getByTestId`, the point is simple: this pack
15
15
  - **Can generate Playwright fixtures** so tests can request `userListPage` instead of constructing `new UserListPage(page)` manually.
16
16
  - **Can emit a single C# POM file** for Playwright .NET consumers.
17
17
  - **Exposes `virtual:testids`** so your app can import the current collected test-id manifest at runtime.
18
- - **Ships ESLint rules** to remove legacy manually-authored test ids and to discourage raw locator actions on generated getters.
18
+ - **Ships ESLint rules** to remove legacy manually-authored test ids, ban raw `page` fixture usage in spec callbacks, and discourage raw locator actions on generated getters.
19
19
 
20
20
  ## What this does not do
21
21
 
@@ -241,7 +241,7 @@ const pomConfig = defineVuePomGeneratorConfig({
241
241
  className: "Grid",
242
242
  propertyName: "grid",
243
243
  attachWhenUsesComponents: ["DataGrid"],
244
- attachTo: "both",
244
+ attachTo: "pagesAndComponents",
245
245
  flatten: true,
246
246
  },
247
247
  ],
@@ -351,7 +351,7 @@ When `generation.router` is enabled, each view POM gets:
351
351
 
352
352
  Important caveats:
353
353
 
354
- - `goToSelf()` literally does `page.goto(route.template)`
354
+ - `goToSelf()` calls `page.goto(...)`, resolving the route template against `PLAYWRIGHT_RUNTIME_BASE_URL`, `PLAYWRIGHT_TEST_BASE_URL`, or `VITE_PLAYWRIGHT_BASE_URL` when those runtime env vars are present
355
355
  - a dynamic route template like `/users/:id` stays `/users/:id`
356
356
  - if a component is matched by multiple routes, the generator currently picks one route template (the shortest one)
357
357
 
@@ -509,7 +509,7 @@ attachments: [
509
509
  className: "Grid",
510
510
  propertyName: "grid",
511
511
  attachWhenUsesComponents: ["DataGrid"],
512
- attachTo: "both",
512
+ attachTo: "pagesAndComponents",
513
513
  flatten: true,
514
514
  },
515
515
  ]
@@ -522,6 +522,7 @@ Actual semantics:
522
522
  - matching is based on the component usage collected from the Vue template, not runtime inspection
523
523
  - the generated constructor instantiates the helper as `new Helper(page, this)`
524
524
  - `attachTo` defaults to `"views"`
525
+ - `"pagesAndComponents"` is the clearer alias for `"both"`; both spellings are accepted for backward compatibility
525
526
 
526
527
  Why it exists:
527
528
 
@@ -717,6 +718,48 @@ rules: {
717
718
 
718
719
  This rule exists too. It flags direct raw Playwright actions on generated PascalCase getters (for example calling `.click()` directly on a generated getter) so teams use the generated action methods instead.
719
720
 
721
+ ### `no-page-fixture-in-specs`
722
+
723
+ This rule flags Playwright's default `page` fixture when it is destructured directly in `*.spec.*` test and hook callbacks.
724
+
725
+ What it does:
726
+
727
+ - flags `test("...", async ({ page }) => { ... })`
728
+ - flags hooks like `test.beforeEach(async ({ page }) => { ... })`
729
+ - ignores non-spec files such as custom fixtures/helpers
730
+ - ignores POM usage like `dashboardPage.page` because the rule is specifically about the raw fixture entry point
731
+
732
+ Why it exists:
733
+
734
+ - fixture-based POM tests are easier to refactor than raw `page`-driven tests
735
+ - it catches regressions where tests quietly slide back to `page.goto(...)` / `page.getBy...(...)`
736
+ - it makes the generator's Playwright-fixture story enforceable during refactors
737
+
738
+ Recommended usage:
739
+
740
+ 1. enable generated fixtures in the generator
741
+ 2. migrate specs from `({ page })` to generated fixtures like `({ dashboardPage })`
742
+ 3. turn this rule on for `tests/playwright/**/*.spec.ts`
743
+
744
+ Example flat config:
745
+
746
+ ```ts
747
+ import { plugin as vuePomGeneratorEslint } from "@immense/vue-pom-generator/eslint";
748
+
749
+ export default [
750
+ {
751
+ files: ["tests/playwright/**/*.spec.ts"],
752
+ plugins: {
753
+ "@immense/vue-pom-generator": vuePomGeneratorEslint,
754
+ },
755
+ rules: {
756
+ "@immense/vue-pom-generator/no-page-fixture-in-specs": "error",
757
+ "@immense/vue-pom-generator/no-raw-locator-action": "error",
758
+ },
759
+ },
760
+ ];
761
+ ```
762
+
720
763
  ## Configuration reference
721
764
 
722
765
  The sections below follow the actual `VuePomGeneratorPluginOptions` shape from `plugin/types.ts`.
@@ -1049,7 +1092,8 @@ This object holds Playwright-specific additions on top of the generated TypeScri
1049
1092
  - **Current options:**
1050
1093
  - `"views"`
1051
1094
  - `"components"`
1052
- - `"both"`
1095
+ - `"pagesAndComponents"`
1096
+ - `"both"` (backward-compatible alias)
1053
1097
 
1054
1098
  #### `attachments[].flatten`
1055
1099
 
package/RELEASE_NOTES.md CHANGED
@@ -1,37 +1,52 @@
1
1
  ● ## Highlights
2
2
 
3
- - Fixed dev-mode POM generation to achieve parity with build mode behavior
4
- - Added comprehensive regression test suite (284 lines) covering build–serve parity scenarios
5
- - Enhanced plugin system to ensure consistent POM generation across development and production
6
- environments
3
+ - New ESLint rule `no-page-fixture-in-specs` enforces Playwright Page Object Model best
4
+ practices
5
+ - Dev-mode POM generation now has parity with build mode
6
+ - Fail-fast behavior added for dev snapshot generation errors
7
+ - Enhanced test coverage with build-serve parity regression tests
8
+ - Improved release workflow with PR preview comments
7
9
 
8
10
  ## Changes
9
11
 
10
- **Bug Fixes**
11
- - Resolved discrepancies between dev-mode and build-mode POM generation (#5)
12
+ **Tooling & Linting**
13
+ - Added new ESLint rule to prevent `page` fixture usage in spec files (112 lines)
14
+ - Updated ESLint configuration and exports
12
15
 
13
- **Testing & Quality**
14
- - Added build–serve parity regression tests to prevent future inconsistencies (#7)
15
- - New test file: `tests/build-serve-parity.test.ts` with comprehensive coverage
16
+ **Code Generation**
17
+ - Fixed keyed POM deduplication logic
18
+ - Improved C# navigation return types
19
+ - Minor adjustments to class generation
16
20
 
17
- **Plugin System**
18
- - Extended build plugin functionality with improved POM generation logic
19
- - Enhanced dev plugin to match build-mode behavior
20
- - Updated support plugins for better parity handling
21
+ **Testing**
22
+ - Added comprehensive vue-plugin-state tests (71 lines)
23
+ - Enhanced ESLint rule test coverage (58 new lines)
24
+ - Added build-serve parity regression tests
25
+ - Updated test assertions for better coverage
26
+
27
+ **Documentation & CI**
28
+ - Updated README with improved guidance (54 lines added)
29
+ - Enhanced release workflow with PR release-notes preview comments
30
+ - Workflow improvements for Playwright generator
21
31
 
22
32
  ## Breaking Changes
23
33
 
24
- None
34
+ None.
25
35
 
26
36
  ## Pull Requests Included
27
37
 
28
- - [#7](https://github.com/immense/vue-pom-generator/pull/7) test: add build–serve parity
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)
38
+ - #7 test: add build–serve parity regression tests
39
+ (https://github.com/immense/vue-pom-generator/pull/7)
40
+ - #6 fix: fail fast on dev snapshot generation errors
41
+ (https://github.com/immense/vue-pom-generator/pull/6)
42
+ - #5 fix: dev-mode POM generation parity with build mode
43
+ (https://github.com/immense/vue-pom-generator/pull/5)
44
+ - #4 Fix keyed POM dedupe and C# navigation returns
45
+ (https://github.com/immense/vue-pom-generator/pull/4)
46
+ - #1 Add PR release-notes preview comments (https://github.com/immense/vue-pom-generator/pull/1)
32
47
 
33
48
  ## Testing
34
49
 
35
- Added 284 lines of regression tests ensuring build–serve parity. Tests validate consistent POM
36
- generation behavior across development and production modes.
50
+ All changes include corresponding test coverage. Added 132 lines of new tests across
51
+ vue-plugin-state and ESLint rule validation.
37
52
 
@@ -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 locator.first().clear();
353
+ await editableLocator.clear();
313
354
  await this.page.keyboard.type(text, { delay: typeDelayMs });
314
355
  }
315
356
  else {
316
- await locator.first().fill(text);
357
+ await editableLocator.fill(text);
317
358
  }
318
359
  }
319
360
  }
@@ -163,7 +163,10 @@ function generateGoToSelfMethod(componentName: string): string {
163
163
  " if (!route) {",
164
164
  ` throw new Error("[pom] No router path found for component/page-object '${componentName}'.");`,
165
165
  " }",
166
- " await this.page.goto(route.template);",
166
+ " const runtimeEnv = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env;",
167
+ " const runtimeBaseUrl = runtimeEnv?.PLAYWRIGHT_RUNTIME_BASE_URL ?? runtimeEnv?.PLAYWRIGHT_TEST_BASE_URL ?? runtimeEnv?.VITE_PLAYWRIGHT_BASE_URL;",
168
+ " const targetUrl = runtimeBaseUrl ? new URL(route.template, runtimeBaseUrl).toString() : route.template;",
169
+ " await this.page.goto(targetUrl);",
167
170
  " }",
168
171
  "",
169
172
  ].join("\n");
@@ -758,6 +761,26 @@ function generateAggregatedCSharpFiles(
758
761
  " protected IPage Page { get; }",
759
762
  ` protected ILocator LocatorByTestId(string testId) => Page.Locator($"[${testIdAttribute}=\\"{testId}\\"]");`,
760
763
  " protected ILocator LocatorWithinTestIdByLabel(string rootTestId, string label, bool exact = true) => LocatorByTestId(rootTestId).GetByLabel(label, new() { Exact = exact });",
764
+ " protected async Task<ILocator> ResolveEditableLocatorAsync(ILocator locator)",
765
+ " {",
766
+ " var isEditable = await locator.EvaluateAsync<bool>(@\"el => {",
767
+ " if (!el || !(el instanceof HTMLElement)) return false;",
768
+ " const tagName = el.tagName.toLowerCase();",
769
+ " return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || el.isContentEditable;",
770
+ " }\");",
771
+ " if (isEditable)",
772
+ " {",
773
+ " return locator;",
774
+ " }",
775
+ "",
776
+ " var descendant = locator.Locator(\"input, textarea, select, [contenteditable=''], [contenteditable='true'], [contenteditable]:not([contenteditable='false'])\").First;",
777
+ " if (await descendant.CountAsync() > 0)",
778
+ " {",
779
+ " return descendant;",
780
+ " }",
781
+ "",
782
+ " return locator;",
783
+ " }",
761
784
  " protected async Task ClickWithinTestIdByLabelAsync(string rootTestId, string label, bool exact = true)",
762
785
  " {",
763
786
  " await LocatorWithinTestIdByLabel(rootTestId, label, exact).ClickAsync();",
@@ -889,7 +912,8 @@ function generateAggregatedCSharpFiles(
889
912
 
890
913
  const emitActionCall = (locatorAccess: string) => {
891
914
  if (pom.nativeRole === "input") {
892
- chunks.push(` await ${locatorAccess}.FillAsync(text);`);
915
+ chunks.push(` var editableLocator = await ResolveEditableLocatorAsync(${locatorAccess});`);
916
+ chunks.push(" await editableLocator.FillAsync(text);");
893
917
  }
894
918
  else if (pom.nativeRole === "select") {
895
919
  chunks.push(` await ${locatorAccess}.SelectOptionAsync(value);`);
@@ -923,7 +947,8 @@ function generateAggregatedCSharpFiles(
923
947
  chunks.push(" if (await locator.CountAsync() > 0)");
924
948
  chunks.push(" {");
925
949
  if (pom.nativeRole === "input") {
926
- chunks.push(" await locator.FillAsync(text);");
950
+ chunks.push(" var editableLocator = await ResolveEditableLocatorAsync(locator);");
951
+ chunks.push(" await editableLocator.FillAsync(text);");
927
952
  }
928
953
  else if (pom.nativeRole === "select") {
929
954
  chunks.push(" await locator.SelectOptionAsync(value);");
@@ -1229,9 +1254,10 @@ function generateViewObjectModelContent(
1229
1254
  return false;
1230
1255
 
1231
1256
  const scope = a.attachTo ?? "views";
1257
+ const scopeMatchesBoth = scope === "both" || scope === "pagesAndComponents";
1232
1258
  const scopeOk = isView
1233
- ? (scope === "views" || scope === "both")
1234
- : (scope === "components" || scope === "both");
1259
+ ? (scope === "views" || scopeMatchesBoth)
1260
+ : (scope === "components" || scopeMatchesBoth);
1235
1261
  if (!scopeOk)
1236
1262
  return false;
1237
1263
  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;AA8D5D,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;IAMV,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;CAgBhB"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../class-generation/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAoE,MAAM,UAAU,CAAC;AAQpH,OAAO,EAAE,oCAAoC,EAAE,CAAC;AA8ChD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AASD,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,oBAAoB,CAAC;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA8PD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,oCAAoC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEzD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAE7C,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,uDAAuD;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,6EAA6E;IAC7E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClD;AA+BD,wBAAsB,aAAa,CACjC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC1D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,oBAAyB,iBAmFnC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../class-generation/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,oCAAoC,EAAE,MAAM,sBAAsB,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAoE,MAAM,UAAU,CAAC;AAQpH,OAAO,EAAE,oCAAoC,EAAE,CAAC;AA8ChD,UAAU,SAAS;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AASD,UAAU,mBAAmB;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,oBAAoB,CAAC;IAClE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAiQD,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1D;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;OAOG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,oCAAoC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAEzD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAE7C,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,uDAAuD;IACvD,aAAa,CAAC,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;IAEvC,6BAA6B;IAC7B,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF,6EAA6E;IAC7E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,mDAAmD;IACnD,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAEnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAEpB,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClD;AA+BD,wBAAsB,aAAa,CACjC,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAC1D,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,oBAAyB,iBAmFnC"}
@@ -1,5 +1,86 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const path = require("node:path");
4
+ const DIRECT_TEST_CALLS = /* @__PURE__ */ new Set(["test", "it"]);
5
+ const TEST_WRAPPER_CALLS = /* @__PURE__ */ new Set(["only", "skip", "fixme", "fail"]);
6
+ const TEST_HOOK_CALLS = /* @__PURE__ */ new Set(["beforeEach", "beforeAll", "afterEach", "afterAll"]);
7
+ const SPEC_FILE_SUFFIXES = /* @__PURE__ */ new Set([
8
+ ".spec.ts",
9
+ ".spec.tsx",
10
+ ".spec.js",
11
+ ".spec.jsx",
12
+ ".spec.cts",
13
+ ".spec.ctsx",
14
+ ".spec.cjs",
15
+ ".spec.cjsx",
16
+ ".spec.mts",
17
+ ".spec.mtsx",
18
+ ".spec.mjs",
19
+ ".spec.mjsx"
20
+ ]);
21
+ function isSpecFile(filename) {
22
+ const basename = path.basename(filename);
23
+ return Array.from(SPEC_FILE_SUFFIXES).some((suffix) => basename.endsWith(suffix));
24
+ }
25
+ function isFunctionExpression(node) {
26
+ return node != null && typeof node === "object" && "type" in node && (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression");
27
+ }
28
+ function getCallbackArgIndex(callee) {
29
+ if (callee.type === "Identifier" && DIRECT_TEST_CALLS.has(callee.name))
30
+ return 1;
31
+ if (callee.type === "MemberExpression" && !callee.computed && callee.object.type === "Identifier" && DIRECT_TEST_CALLS.has(callee.object.name) && callee.property.type === "Identifier") {
32
+ if (TEST_WRAPPER_CALLS.has(callee.property.name))
33
+ return 1;
34
+ if (TEST_HOOK_CALLS.has(callee.property.name))
35
+ return 0;
36
+ }
37
+ return null;
38
+ }
39
+ function getPageFixtureProperty(param) {
40
+ if (!param || param.type !== "ObjectPattern")
41
+ return null;
42
+ for (const property of param.properties) {
43
+ if (property.type !== "Property" || property.computed)
44
+ continue;
45
+ if (property.key.type === "Identifier" && property.key.name === "page")
46
+ return property;
47
+ }
48
+ return null;
49
+ }
50
+ const noPageFixtureInSpecsRule = {
51
+ meta: {
52
+ type: "problem",
53
+ docs: {
54
+ description: "Disallow Playwright's default `page` fixture in spec callbacks. Prefer generated fixtures and POMs instead."
55
+ },
56
+ messages: {
57
+ noPageFixture: "Do not destructure the default `page` fixture in spec callbacks. Use generated fixtures and POMs instead."
58
+ },
59
+ schema: []
60
+ },
61
+ create(context) {
62
+ const filename = context.getFilename();
63
+ if (!isSpecFile(filename))
64
+ return {};
65
+ return {
66
+ CallExpression(node) {
67
+ const callbackArgIndex = getCallbackArgIndex(node.callee);
68
+ if (callbackArgIndex == null)
69
+ return;
70
+ const callback = node.arguments[callbackArgIndex];
71
+ if (!isFunctionExpression(callback))
72
+ return;
73
+ const pageFixtureProperty = getPageFixtureProperty(callback.params[0]);
74
+ if (!pageFixtureProperty)
75
+ return;
76
+ context.report({
77
+ node: pageFixtureProperty,
78
+ messageId: "noPageFixture"
79
+ });
80
+ }
81
+ };
82
+ }
83
+ };
3
84
  function isVueTemplateFile(filename) {
4
85
  return filename.endsWith(".vue");
5
86
  }
@@ -105,10 +186,14 @@ const LOCATOR_ACTIONS = /* @__PURE__ */ new Set([
105
186
  "selectText"
106
187
  ]);
107
188
  const CHAIN_METHODS = /* @__PURE__ */ new Set(["last", "first", "nth", "filter"]);
189
+ function startsWithUppercaseLetter(value) {
190
+ const first = value.charCodeAt(0);
191
+ return first >= 65 && first <= 90;
192
+ }
108
193
  function getPomGetterName(node) {
109
194
  if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
110
195
  const name = node.property.name;
111
- if (/^[A-Z]/.test(name)) return name;
196
+ if (startsWithUppercaseLetter(name)) return name;
112
197
  }
113
198
  if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && !node.callee.computed && node.callee.property.type === "Identifier" && CHAIN_METHODS.has(node.callee.property.name)) {
114
199
  return getPomGetterName(node.callee.object);
@@ -147,10 +232,12 @@ const noRawLocatorActionRule = {
147
232
  };
148
233
  const plugin = {
149
234
  rules: {
235
+ "no-page-fixture-in-specs": noPageFixtureInSpecsRule,
150
236
  "no-raw-locator-action": noRawLocatorActionRule,
151
237
  "remove-existing-test-id-attributes": removeExistingTestIdAttributesRule
152
238
  }
153
239
  };
240
+ exports.noPageFixtureInSpecsRule = noPageFixtureInSpecsRule;
154
241
  exports.noRawLocatorActionRule = noRawLocatorActionRule;
155
242
  exports.plugin = plugin;
156
243
  exports.removeExistingTestIdAttributesRule = removeExistingTestIdAttributesRule;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../eslint/remove-existing-test-id-attributes.ts","../../eslint/index.ts"],"sourcesContent":["import type { Rule } from \"eslint\";\nimport type { AST as VueAST } from \"vue-eslint-parser\";\n\ntype VAttribute = VueAST.VAttribute;\ntype VDirective = VueAST.VDirective;\ntype VElement = VueAST.VElement;\ntype VueAttribute = VAttribute | VDirective;\ntype VueTemplateVisitor = {\n\tVElement: (node: VElement) => void;\n};\n\nfunction isVueTemplateFile(filename: string): boolean {\n\treturn filename.endsWith(\".vue\");\n}\n\nfunction isWhitespaceCharacter(character: string): boolean {\n\treturn character === \" \"\n\t\t|| character === \"\\t\"\n\t\t|| character === \"\\n\"\n\t\t|| character === \"\\r\"\n\t\t|| character === \"\\f\";\n}\n\nfunction removeAttributeWithWhitespace(\n\tattribute: VueAttribute,\n\tcontext: Rule.RuleContext,\n\tfixer: Rule.RuleFixer,\n): Rule.Fix {\n\tconst sourceText = context.sourceCode.getText();\n\tconst [start, end] = attribute.range;\n\n\tlet adjustedStart = start;\n\twhile (adjustedStart > 0 && isWhitespaceCharacter(sourceText[adjustedStart - 1])) {\n\t\tadjustedStart -= 1;\n\t}\n\n\treturn fixer.removeRange([adjustedStart, end]);\n}\n\nfunction isTargetAttribute(attribute: VueAttribute, attributeName: string): boolean {\n\tif (!attribute.directive) {\n\t\treturn attribute.key.type === \"VIdentifier\" && attribute.key.name === attributeName;\n\t}\n\n\tif (attribute.key.type !== \"VDirectiveKey\") {\n\t\treturn false;\n\t}\n\n\tconst directiveName = attribute.key.name;\n\tconst argument = attribute.key.argument;\n\n\treturn directiveName.type === \"VIdentifier\"\n\t\t&& directiveName.name === \"bind\"\n\t\t&& argument?.type === \"VIdentifier\"\n\t\t&& argument.name === attributeName;\n}\n\nfunction findExistingTestIdAttribute(node: VElement, attributeName: string): VueAttribute | undefined {\n\treturn node.startTag.attributes.find(attribute => isTargetAttribute(attribute, attributeName));\n}\n\nfunction defineVueTemplateVisitor(\n\tcontext: Rule.RuleContext,\n\ttemplateVisitor: VueTemplateVisitor,\n): Rule.RuleListener {\n\tconst parserServices = context.sourceCode.parserServices as {\n\t\tdefineTemplateBodyVisitor?: (\n\t\t\ttemplateBodyVisitor: VueTemplateVisitor,\n\t\t\tscriptVisitor: Rule.RuleListener,\n\t\t\toptions: { templateBodyTriggerSelector: \"Program\" },\n\t\t) => Rule.RuleListener;\n\t};\n\n\tif (!parserServices.defineTemplateBodyVisitor) {\n\t\treturn {};\n\t}\n\n\treturn parserServices.defineTemplateBodyVisitor(\n\t\ttemplateVisitor,\n\t\t{},\n\t\t{ templateBodyTriggerSelector: \"Program\" },\n\t);\n}\n\nexport const removeExistingTestIdAttributesRule: Rule.RuleModule = {\n\tmeta: {\n\t\ttype: \"suggestion\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Remove existing test-id attributes from Vue templates so vue-pom-generator can generate them consistently.\",\n\t\t},\n\t\tfixable: \"code\",\n\t\tmessages: {\n\t\t\tremoveExistingTestIdAttribute:\n\t\t\t\t\"Remove explicit {{attribute}}. vue-pom-generator can generate it; run this rule with --fix to clean legacy attributes project-wide.\",\n\t\t},\n\t\tschema: [\n\t\t\t{\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tattribute: {\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\tdescription: \"Attribute name to remove. Defaults to data-testid.\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t],\n\t},\n\tcreate(context): Rule.RuleListener {\n\t\tif (!isVueTemplateFile(context.filename)) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst options = (context.options[0] ?? {}) as { attribute?: string };\n\t\tconst attributeName = (options.attribute ?? \"data-testid\").trim() || \"data-testid\";\n\n\t\treturn defineVueTemplateVisitor(context, {\n\t\t\tVElement(node: VElement) {\n\t\t\t\tconst existingAttribute = findExistingTestIdAttribute(node, attributeName);\n\t\t\t\tif (!existingAttribute) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode: existingAttribute,\n\t\t\t\t\tmessageId: \"removeExistingTestIdAttribute\",\n\t\t\t\t\tdata: { attribute: attributeName },\n\t\t\t\t\tfix(fixer) {\n\t\t\t\t\t\treturn removeAttributeWithWhitespace(existingAttribute, context, fixer);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t},\n};","import type { Rule } from \"eslint\";\nimport type { CallExpression, Expression, MemberExpression } from \"estree\";\n\nimport { removeExistingTestIdAttributesRule } from \"./remove-existing-test-id-attributes\";\n\n/**\n * Playwright locator action methods that should be called via generated POM\n * methods rather than directly on element getters.\n */\nconst LOCATOR_ACTIONS = new Set([\n\t\"click\",\n\t\"dblclick\",\n\t\"fill\",\n\t\"check\",\n\t\"uncheck\",\n\t\"type\",\n\t\"clear\",\n\t\"selectOption\",\n\t\"setInputFiles\",\n\t\"tap\",\n\t\"hover\",\n\t\"focus\",\n\t\"dispatchEvent\",\n\t\"press\",\n\t\"selectText\",\n]);\n\n/**\n * Locator chain methods that are transparent for the purposes of this rule —\n * `.last().click()` is still a raw action on a POM getter.\n */\nconst CHAIN_METHODS = new Set([\"last\", \"first\", \"nth\", \"filter\"]);\n\n/**\n * Returns the PascalCase getter name if `node` is (or chains from) a direct\n * PascalCase member-expression access. Returns null otherwise.\n *\n * Handles:\n * pom.SubmitButton → \"SubmitButton\"\n * pom.SubmitButton.last() → \"SubmitButton\"\n * pom.SubmitButton.nth(0) → \"SubmitButton\"\n */\nfunction getPomGetterName(node: Expression): string | null {\n\tif (node.type === \"MemberExpression\" && !node.computed && node.property.type === \"Identifier\") {\n\t\tconst name = node.property.name;\n\t\tif (/^[A-Z]/.test(name)) return name;\n\t}\n\n\tif (\n\t\tnode.type === \"CallExpression\"\n\t\t&& node.callee.type === \"MemberExpression\"\n\t\t&& !node.callee.computed\n\t\t&& node.callee.property.type === \"Identifier\"\n\t\t&& CHAIN_METHODS.has(node.callee.property.name)\n\t) {\n\t\treturn getPomGetterName((node.callee as MemberExpression).object as Expression);\n\t}\n\n\treturn null;\n}\n\nexport const noRawLocatorActionRule: Rule.RuleModule = {\n\tmeta: {\n\t\ttype: \"suggestion\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Disallow calling raw Playwright action methods directly on POM element getters. Use the generated typed POM methods instead (e.g. `clickSubmitButton()`).\",\n\t\t},\n\t\tmessages: {\n\t\t\tnoRawAction:\n\t\t\t\t\"Use the generated POM method instead of `{{getter}}.{{method}}()`. \"\n\t\t\t\t+ \"Call `click{{getter}}()` / `type{{getter}}(text)` or similar.\",\n\t\t},\n\t\tschema: [],\n\t},\n\tcreate(context) {\n\t\treturn {\n\t\t\tCallExpression(node: CallExpression) {\n\t\t\t\tif (node.callee.type !== \"MemberExpression\") return;\n\t\t\t\tconst callee = node.callee as MemberExpression;\n\t\t\t\tif (callee.computed || callee.property.type !== \"Identifier\") return;\n\n\t\t\t\tconst methodName = callee.property.name;\n\t\t\t\tif (!LOCATOR_ACTIONS.has(methodName)) return;\n\n\t\t\t\tconst getterName = getPomGetterName(callee.object as Expression);\n\t\t\t\tif (!getterName) return;\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode,\n\t\t\t\t\tmessageId: \"noRawAction\",\n\t\t\t\t\tdata: { getter: getterName, method: methodName },\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t},\n};\n\nexport const plugin = {\n\trules: {\n\t\t\"no-raw-locator-action\": noRawLocatorActionRule,\n\t\t\"remove-existing-test-id-attributes\": removeExistingTestIdAttributesRule,\n\t},\n} satisfies { rules: Record<string, Rule.RuleModule> };\n\nexport { removeExistingTestIdAttributesRule };\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../eslint/no-page-fixture-in-specs.ts","../../eslint/remove-existing-test-id-attributes.ts","../../eslint/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Rule } from \"eslint\";\nimport type { ArrowFunctionExpression, CallExpression, FunctionExpression } from \"estree\";\n\nconst DIRECT_TEST_CALLS = new Set([\"test\", \"it\"]);\nconst TEST_WRAPPER_CALLS = new Set([\"only\", \"skip\", \"fixme\", \"fail\"]);\nconst TEST_HOOK_CALLS = new Set([\"beforeEach\", \"beforeAll\", \"afterEach\", \"afterAll\"]);\nconst SPEC_FILE_SUFFIXES = new Set([\n\t\".spec.ts\",\n\t\".spec.tsx\",\n\t\".spec.js\",\n\t\".spec.jsx\",\n\t\".spec.cts\",\n\t\".spec.ctsx\",\n\t\".spec.cjs\",\n\t\".spec.cjsx\",\n\t\".spec.mts\",\n\t\".spec.mtsx\",\n\t\".spec.mjs\",\n\t\".spec.mjsx\",\n]);\n\nfunction isSpecFile(filename: string): boolean {\n\tconst basename = path.basename(filename);\n\treturn Array.from(SPEC_FILE_SUFFIXES).some(suffix => basename.endsWith(suffix));\n}\n\nfunction isFunctionExpression(\n\tnode: CallExpression[\"arguments\"][number] | null | undefined,\n): node is ArrowFunctionExpression | FunctionExpression {\n\treturn node != null\n\t\t&& typeof node === \"object\"\n\t\t&& \"type\" in node\n\t\t&& (node.type === \"ArrowFunctionExpression\" || node.type === \"FunctionExpression\");\n}\n\nfunction getCallbackArgIndex(callee: CallExpression[\"callee\"]): number | null {\n\tif (callee.type === \"Identifier\" && DIRECT_TEST_CALLS.has(callee.name))\n\t\treturn 1;\n\n\tif (\n\t\tcallee.type === \"MemberExpression\"\n\t\t&& !callee.computed\n\t\t&& callee.object.type === \"Identifier\"\n\t\t&& DIRECT_TEST_CALLS.has(callee.object.name)\n\t\t&& callee.property.type === \"Identifier\"\n\t) {\n\t\tif (TEST_WRAPPER_CALLS.has(callee.property.name))\n\t\t\treturn 1;\n\n\t\tif (TEST_HOOK_CALLS.has(callee.property.name))\n\t\t\treturn 0;\n\t}\n\n\treturn null;\n}\n\nfunction getPageFixtureProperty(param: ArrowFunctionExpression[\"params\"][0] | FunctionExpression[\"params\"][0]) {\n\tif (!param || param.type !== \"ObjectPattern\")\n\t\treturn null;\n\n\tfor (const property of param.properties) {\n\t\tif (property.type !== \"Property\" || property.computed)\n\t\t\tcontinue;\n\n\t\tif (property.key.type === \"Identifier\" && property.key.name === \"page\")\n\t\t\treturn property;\n\t}\n\n\treturn null;\n}\n\nexport const noPageFixtureInSpecsRule: Rule.RuleModule = {\n\tmeta: {\n\t\ttype: \"problem\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Disallow Playwright's default `page` fixture in spec callbacks. Prefer generated fixtures and POMs instead.\",\n\t\t},\n\t\tmessages: {\n\t\t\tnoPageFixture:\n\t\t\t\t\"Do not destructure the default `page` fixture in spec callbacks. Use generated fixtures and POMs instead.\",\n\t\t},\n\t\tschema: [],\n\t},\n\tcreate(context) {\n\t\tconst filename = context.getFilename();\n\t\tif (!isSpecFile(filename))\n\t\t\treturn {};\n\n\t\treturn {\n\t\t\tCallExpression(node: CallExpression) {\n\t\t\t\tconst callbackArgIndex = getCallbackArgIndex(node.callee);\n\t\t\t\tif (callbackArgIndex == null)\n\t\t\t\t\treturn;\n\n\t\t\t\tconst callback = node.arguments[callbackArgIndex];\n\t\t\t\tif (!isFunctionExpression(callback))\n\t\t\t\t\treturn;\n\n\t\t\t\tconst pageFixtureProperty = getPageFixtureProperty(callback.params[0]);\n\t\t\t\tif (!pageFixtureProperty)\n\t\t\t\t\treturn;\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode: pageFixtureProperty,\n\t\t\t\t\tmessageId: \"noPageFixture\",\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t},\n};\n","import type { Rule } from \"eslint\";\nimport type { AST as VueAST } from \"vue-eslint-parser\";\n\ntype VAttribute = VueAST.VAttribute;\ntype VDirective = VueAST.VDirective;\ntype VElement = VueAST.VElement;\ntype VueAttribute = VAttribute | VDirective;\ntype VueTemplateVisitor = {\n\tVElement: (node: VElement) => void;\n};\n\nfunction isVueTemplateFile(filename: string): boolean {\n\treturn filename.endsWith(\".vue\");\n}\n\nfunction isWhitespaceCharacter(character: string): boolean {\n\treturn character === \" \"\n\t\t|| character === \"\\t\"\n\t\t|| character === \"\\n\"\n\t\t|| character === \"\\r\"\n\t\t|| character === \"\\f\";\n}\n\nfunction removeAttributeWithWhitespace(\n\tattribute: VueAttribute,\n\tcontext: Rule.RuleContext,\n\tfixer: Rule.RuleFixer,\n): Rule.Fix {\n\tconst sourceText = context.sourceCode.getText();\n\tconst [start, end] = attribute.range;\n\n\tlet adjustedStart = start;\n\twhile (adjustedStart > 0 && isWhitespaceCharacter(sourceText[adjustedStart - 1])) {\n\t\tadjustedStart -= 1;\n\t}\n\n\treturn fixer.removeRange([adjustedStart, end]);\n}\n\nfunction isTargetAttribute(attribute: VueAttribute, attributeName: string): boolean {\n\tif (!attribute.directive) {\n\t\treturn attribute.key.type === \"VIdentifier\" && attribute.key.name === attributeName;\n\t}\n\n\tif (attribute.key.type !== \"VDirectiveKey\") {\n\t\treturn false;\n\t}\n\n\tconst directiveName = attribute.key.name;\n\tconst argument = attribute.key.argument;\n\n\treturn directiveName.type === \"VIdentifier\"\n\t\t&& directiveName.name === \"bind\"\n\t\t&& argument?.type === \"VIdentifier\"\n\t\t&& argument.name === attributeName;\n}\n\nfunction findExistingTestIdAttribute(node: VElement, attributeName: string): VueAttribute | undefined {\n\treturn node.startTag.attributes.find(attribute => isTargetAttribute(attribute, attributeName));\n}\n\nfunction defineVueTemplateVisitor(\n\tcontext: Rule.RuleContext,\n\ttemplateVisitor: VueTemplateVisitor,\n): Rule.RuleListener {\n\tconst parserServices = context.sourceCode.parserServices as {\n\t\tdefineTemplateBodyVisitor?: (\n\t\t\ttemplateBodyVisitor: VueTemplateVisitor,\n\t\t\tscriptVisitor: Rule.RuleListener,\n\t\t\toptions: { templateBodyTriggerSelector: \"Program\" },\n\t\t) => Rule.RuleListener;\n\t};\n\n\tif (!parserServices.defineTemplateBodyVisitor) {\n\t\treturn {};\n\t}\n\n\treturn parserServices.defineTemplateBodyVisitor(\n\t\ttemplateVisitor,\n\t\t{},\n\t\t{ templateBodyTriggerSelector: \"Program\" },\n\t);\n}\n\nexport const removeExistingTestIdAttributesRule: Rule.RuleModule = {\n\tmeta: {\n\t\ttype: \"suggestion\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Remove existing test-id attributes from Vue templates so vue-pom-generator can generate them consistently.\",\n\t\t},\n\t\tfixable: \"code\",\n\t\tmessages: {\n\t\t\tremoveExistingTestIdAttribute:\n\t\t\t\t\"Remove explicit {{attribute}}. vue-pom-generator can generate it; run this rule with --fix to clean legacy attributes project-wide.\",\n\t\t},\n\t\tschema: [\n\t\t\t{\n\t\t\t\ttype: \"object\",\n\t\t\t\tproperties: {\n\t\t\t\t\tattribute: {\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\tdescription: \"Attribute name to remove. Defaults to data-testid.\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tadditionalProperties: false,\n\t\t\t},\n\t\t],\n\t},\n\tcreate(context): Rule.RuleListener {\n\t\tif (!isVueTemplateFile(context.filename)) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst options = (context.options[0] ?? {}) as { attribute?: string };\n\t\tconst attributeName = (options.attribute ?? \"data-testid\").trim() || \"data-testid\";\n\n\t\treturn defineVueTemplateVisitor(context, {\n\t\t\tVElement(node: VElement) {\n\t\t\t\tconst existingAttribute = findExistingTestIdAttribute(node, attributeName);\n\t\t\t\tif (!existingAttribute) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode: existingAttribute,\n\t\t\t\t\tmessageId: \"removeExistingTestIdAttribute\",\n\t\t\t\t\tdata: { attribute: attributeName },\n\t\t\t\t\tfix(fixer) {\n\t\t\t\t\t\treturn removeAttributeWithWhitespace(existingAttribute, context, fixer);\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t});\n\t},\n};","import type { Rule } from \"eslint\";\nimport type { CallExpression, Expression, MemberExpression } from \"estree\";\n\nimport { noPageFixtureInSpecsRule } from \"./no-page-fixture-in-specs\";\nimport { removeExistingTestIdAttributesRule } from \"./remove-existing-test-id-attributes\";\n\n/**\n * Playwright locator action methods that should be called via generated POM\n * methods rather than directly on element getters.\n */\nconst LOCATOR_ACTIONS = new Set([\n\t\"click\",\n\t\"dblclick\",\n\t\"fill\",\n\t\"check\",\n\t\"uncheck\",\n\t\"type\",\n\t\"clear\",\n\t\"selectOption\",\n\t\"setInputFiles\",\n\t\"tap\",\n\t\"hover\",\n\t\"focus\",\n\t\"dispatchEvent\",\n\t\"press\",\n\t\"selectText\",\n]);\n\n/**\n * Locator chain methods that are transparent for the purposes of this rule —\n * `.last().click()` is still a raw action on a POM getter.\n */\nconst CHAIN_METHODS = new Set([\"last\", \"first\", \"nth\", \"filter\"]);\n\nfunction startsWithUppercaseLetter(value: string): boolean {\n\tconst first = value.charCodeAt(0);\n\treturn first >= 65 && first <= 90;\n}\n\n/**\n * Returns the PascalCase getter name if `node` is (or chains from) a direct\n * PascalCase member-expression access. Returns null otherwise.\n *\n * Handles:\n * pom.SubmitButton → \"SubmitButton\"\n * pom.SubmitButton.last() → \"SubmitButton\"\n * pom.SubmitButton.nth(0) → \"SubmitButton\"\n */\nfunction getPomGetterName(node: Expression): string | null {\n\tif (node.type === \"MemberExpression\" && !node.computed && node.property.type === \"Identifier\") {\n\t\tconst name = node.property.name;\n\t\tif (startsWithUppercaseLetter(name)) return name;\n\t}\n\n\tif (\n\t\tnode.type === \"CallExpression\"\n\t\t&& node.callee.type === \"MemberExpression\"\n\t\t&& !node.callee.computed\n\t\t&& node.callee.property.type === \"Identifier\"\n\t\t&& CHAIN_METHODS.has(node.callee.property.name)\n\t) {\n\t\treturn getPomGetterName((node.callee as MemberExpression).object as Expression);\n\t}\n\n\treturn null;\n}\n\nexport const noRawLocatorActionRule: Rule.RuleModule = {\n\tmeta: {\n\t\ttype: \"suggestion\",\n\t\tdocs: {\n\t\t\tdescription:\n\t\t\t\t\"Disallow calling raw Playwright action methods directly on POM element getters. Use the generated typed POM methods instead (e.g. `clickSubmitButton()`).\",\n\t\t},\n\t\tmessages: {\n\t\t\tnoRawAction:\n\t\t\t\t\"Use the generated POM method instead of `{{getter}}.{{method}}()`. \"\n\t\t\t\t+ \"Call `click{{getter}}()` / `type{{getter}}(text)` or similar.\",\n\t\t},\n\t\tschema: [],\n\t},\n\tcreate(context) {\n\t\treturn {\n\t\t\tCallExpression(node: CallExpression) {\n\t\t\t\tif (node.callee.type !== \"MemberExpression\") return;\n\t\t\t\tconst callee = node.callee as MemberExpression;\n\t\t\t\tif (callee.computed || callee.property.type !== \"Identifier\") return;\n\n\t\t\t\tconst methodName = callee.property.name;\n\t\t\t\tif (!LOCATOR_ACTIONS.has(methodName)) return;\n\n\t\t\t\tconst getterName = getPomGetterName(callee.object as Expression);\n\t\t\t\tif (!getterName) return;\n\n\t\t\t\tcontext.report({\n\t\t\t\t\tnode,\n\t\t\t\t\tmessageId: \"noRawAction\",\n\t\t\t\t\tdata: { getter: getterName, method: methodName },\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t},\n};\n\nexport const plugin = {\n\trules: {\n\t\t\"no-page-fixture-in-specs\": noPageFixtureInSpecsRule,\n\t\t\"no-raw-locator-action\": noRawLocatorActionRule,\n\t\t\"remove-existing-test-id-attributes\": removeExistingTestIdAttributesRule,\n\t},\n} satisfies { rules: Record<string, Rule.RuleModule> };\n\nexport { noPageFixtureInSpecsRule };\nexport { removeExistingTestIdAttributesRule };\n"],"names":[],"mappings":"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"}
@@ -1,11 +1,14 @@
1
1
  import type { Rule } from "eslint";
2
+ import { noPageFixtureInSpecsRule } from "./no-page-fixture-in-specs";
2
3
  import { removeExistingTestIdAttributesRule } from "./remove-existing-test-id-attributes";
3
4
  export declare const noRawLocatorActionRule: Rule.RuleModule;
4
5
  export declare const plugin: {
5
6
  rules: {
7
+ "no-page-fixture-in-specs": Rule.RuleModule;
6
8
  "no-raw-locator-action": Rule.RuleModule;
7
9
  "remove-existing-test-id-attributes": Rule.RuleModule;
8
10
  };
9
11
  };
12
+ export { noPageFixtureInSpecsRule };
10
13
  export { removeExistingTestIdAttributesRule };
11
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../eslint/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGnC,OAAO,EAAE,kCAAkC,EAAE,MAAM,sCAAsC,CAAC;AA0D1F,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,UAmCzC,CAAC;AAEF,eAAO,MAAM,MAAM;;;;;CAKmC,CAAC;AAEvD,OAAO,EAAE,kCAAkC,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../eslint/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAGnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,kCAAkC,EAAE,MAAM,sCAAsC,CAAC;AA+D1F,eAAO,MAAM,sBAAsB,EAAE,IAAI,CAAC,UAmCzC,CAAC;AAEF,eAAO,MAAM,MAAM;;;;;;CAMmC,CAAC;AAEvD,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,OAAO,EAAE,kCAAkC,EAAE,CAAC"}