@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 +49 -5
- package/RELEASE_NOTES.md +35 -20
- package/class-generation/Pointer.ts +43 -2
- package/class-generation/index.ts +31 -5
- package/dist/class-generation/Pointer.d.ts +2 -0
- package/dist/class-generation/Pointer.d.ts.map +1 -1
- package/dist/class-generation/index.d.ts.map +1 -1
- package/dist/eslint/index.cjs +88 -1
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.ts +3 -0
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.mjs +88 -1
- package/dist/eslint/index.mjs.map +1 -1
- package/dist/eslint/no-page-fixture-in-specs.d.ts +3 -0
- package/dist/eslint/no-page-fixture-in-specs.d.ts.map +1 -0
- package/dist/index.cjs +30 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +30 -4
- 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/dist/tests/vue-plugin-state.test.d.ts +2 -0
- package/dist/tests/vue-plugin-state.test.d.ts.map +1 -0
- package/package.json +1 -1
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
|
|
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: "
|
|
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()`
|
|
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: "
|
|
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
|
-
- `"
|
|
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
|
-
-
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
|
|
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
|
-
**
|
|
11
|
-
-
|
|
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
|
-
**
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
+
**Code Generation**
|
|
17
|
+
- Fixed keyed POM deduplication logic
|
|
18
|
+
- Improved C# navigation return types
|
|
19
|
+
- Minor adjustments to class generation
|
|
16
20
|
|
|
17
|
-
**
|
|
18
|
-
-
|
|
19
|
-
- Enhanced
|
|
20
|
-
-
|
|
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
|
-
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
"
|
|
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}
|
|
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
|
|
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" ||
|
|
1234
|
-
: (scope === "components" ||
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/eslint/index.cjs
CHANGED
|
@@ -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 (
|
|
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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"}
|
package/dist/eslint/index.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|