pomwright 1.4.0 → 1.5.0

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.
Files changed (117) hide show
  1. package/AGENTS.md +37 -0
  2. package/CHANGELOG.md +179 -0
  3. package/README.md +316 -34
  4. package/dist/index.d.mts +1052 -30
  5. package/dist/index.d.ts +1052 -30
  6. package/dist/index.js +2263 -65
  7. package/dist/index.mjs +2260 -67
  8. package/docs/v1-to-v2-migration/bridge-migration-guide.md +159 -0
  9. package/docs/v1-to-v2-migration/direct-migration-guide.md +238 -0
  10. package/docs/v1-to-v2-migration/v1-to-v2-comparison.md +547 -0
  11. package/docs/v2/PageObject.md +293 -0
  12. package/docs/v2/composing-locator-modules.md +93 -0
  13. package/docs/v2/locator-registry.md +693 -0
  14. package/docs/v2/logging.md +168 -0
  15. package/docs/v2/overview.md +515 -0
  16. package/docs/v2/session-storage.md +160 -0
  17. package/index.ts +61 -9
  18. package/intTestV2/.env +0 -0
  19. package/intTestV2/fixtures/testApp.fixtures.ts +43 -0
  20. package/intTestV2/package.json +22 -0
  21. package/intTestV2/page-object-models/testApp/pages/iframe/iframe.locatorSchema.ts +24 -0
  22. package/intTestV2/page-object-models/testApp/pages/iframe/iframe.page.ts +17 -0
  23. package/intTestV2/page-object-models/testApp/pages/testPage.locatorSchema.ts +32 -0
  24. package/intTestV2/page-object-models/testApp/pages/testPage.page.ts +119 -0
  25. package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.locatorSchema.ts +29 -0
  26. package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.page.ts +48 -0
  27. package/intTestV2/page-object-models/testApp/pages/testPath/testPath.locatorSchema.ts +9 -0
  28. package/intTestV2/page-object-models/testApp/pages/testPath/testPath.page.ts +23 -0
  29. package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.locatorSchema.ts +114 -0
  30. package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.page.ts +23 -0
  31. package/intTestV2/page-object-models/testApp/testApp.base.ts +20 -0
  32. package/intTestV2/playwright.config.ts +54 -0
  33. package/intTestV2/server.js +216 -0
  34. package/intTestV2/test-data/staticPage/index.html +280 -0
  35. package/intTestV2/test-data/staticPage/w3images/avatar2.png +0 -0
  36. package/intTestV2/test-data/staticPage/w3images/avatar3.png +0 -0
  37. package/intTestV2/test-data/staticPage/w3images/avatar5.png +0 -0
  38. package/intTestV2/test-data/staticPage/w3images/avatar6.png +0 -0
  39. package/intTestV2/test-data/staticPage/w3images/forest.jpg +0 -0
  40. package/intTestV2/test-data/staticPage/w3images/lights.jpg +0 -0
  41. package/intTestV2/test-data/staticPage/w3images/mountains.jpg +0 -0
  42. package/intTestV2/test-data/staticPage/w3images/nature.jpg +0 -0
  43. package/intTestV2/test-data/staticPage/w3images/snow.jpg +0 -0
  44. package/intTestV2/tests/locatorRegistry/add/add.describe.spec.ts +54 -0
  45. package/intTestV2/tests/locatorRegistry/add/add.filter.spec.ts +143 -0
  46. package/intTestV2/tests/locatorRegistry/add/add.frameLocator.spec.ts +23 -0
  47. package/intTestV2/tests/locatorRegistry/add/add.getByAltText.spec.ts +23 -0
  48. package/intTestV2/tests/locatorRegistry/add/add.getById.spec.ts +45 -0
  49. package/intTestV2/tests/locatorRegistry/add/add.getByLabel.spec.ts +23 -0
  50. package/intTestV2/tests/locatorRegistry/add/add.getByPlaceholder.spec.ts +23 -0
  51. package/intTestV2/tests/locatorRegistry/add/add.getByRole.spec.ts +23 -0
  52. package/intTestV2/tests/locatorRegistry/add/add.getByTestId.spec.ts +23 -0
  53. package/intTestV2/tests/locatorRegistry/add/add.getByText.spec.ts +23 -0
  54. package/intTestV2/tests/locatorRegistry/add/add.getByTitle.spec.ts +23 -0
  55. package/intTestV2/tests/locatorRegistry/add/add.locator.spec.ts +23 -0
  56. package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +66 -0
  57. package/intTestV2/tests/locatorRegistry/add/add.reuseReusable.spec.ts +311 -0
  58. package/intTestV2/tests/locatorRegistry/add/add.spec.ts +159 -0
  59. package/intTestV2/tests/locatorRegistry/filter.cycle.spec.ts +39 -0
  60. package/intTestV2/tests/locatorRegistry/getLocator/getLocator.spec.ts +253 -0
  61. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.clearSteps.spec.ts +105 -0
  62. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.describe.spec.ts +23 -0
  63. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.filter.spec.ts +368 -0
  64. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getLocator.spec.ts +56 -0
  65. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getNestedLocator.spec.ts +175 -0
  66. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.nth.spec.ts +60 -0
  67. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.remove.spec.ts +32 -0
  68. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.replace.spec.ts +24 -0
  69. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.spec.ts +110 -0
  70. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.update.spec.ts +322 -0
  71. package/intTestV2/tests/locatorRegistry/getNestedLocator/getNestedLocator.spec.ts +412 -0
  72. package/intTestV2/tests/locatorRegistry/registry/registry.binding.spec.ts +50 -0
  73. package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.spec.ts +115 -0
  74. package/intTestV2/tests/locatorRegistry/validation/validation.sub-path.spec.ts +45 -0
  75. package/intTestV2/tests/step/step.spec.ts +49 -0
  76. package/intTestV2/tests/testApp/color.spec.ts +15 -0
  77. package/intTestV2/tests/testApp/iframe.spec.ts +57 -0
  78. package/intTestV2/tests/testApp/testFilters.spec.ts +24 -0
  79. package/intTestV2/tests/testApp/testPage.spec.ts +161 -0
  80. package/intTestV2/tests/testApp/testPath.spec.ts +18 -0
  81. package/pack-build.sh +11 -0
  82. package/pack-test-v2.sh +36 -0
  83. package/package.json +10 -3
  84. package/playwright.base.ts +42 -0
  85. package/skills/README.md +56 -0
  86. package/skills/pomwright-v1-5-bridge-migration/SKILL.md +40 -0
  87. package/skills/pomwright-v1-5-bridge-migration/references/call-site-migration.md +178 -0
  88. package/skills/pomwright-v1-5-bridge-migration/references/schema-translation.md +183 -0
  89. package/skills/pomwright-v2-migration/SKILL.md +63 -0
  90. package/skills/pomwright-v2-migration/references/call-site-migration.md +265 -0
  91. package/skills/pomwright-v2-migration/references/class-migration.md +266 -0
  92. package/skills/pomwright-v2-migration/references/fixture-and-helpers.md +423 -0
  93. package/skills/pomwright-v2-migration/references/locator-registration.md +344 -0
  94. package/srcV2/fixture/base.fixtures.ts +23 -0
  95. package/srcV2/helpers/navigation.ts +153 -0
  96. package/srcV2/helpers/playwrightReportLogger.ts +196 -0
  97. package/srcV2/helpers/sessionStorage.ts +251 -0
  98. package/srcV2/helpers/stepDecorator.ts +106 -0
  99. package/srcV2/locators/index.ts +15 -0
  100. package/srcV2/locators/locatorQueryBuilder.ts +427 -0
  101. package/srcV2/locators/locatorRegistrationBuilder.ts +558 -0
  102. package/srcV2/locators/locatorRegistry.ts +541 -0
  103. package/srcV2/locators/locatorUpdateBuilder.ts +602 -0
  104. package/srcV2/locators/reusableLocatorBuilder.ts +200 -0
  105. package/srcV2/locators/types.ts +256 -0
  106. package/srcV2/locators/utils.ts +309 -0
  107. package/srcV2/locators/v1SchemaTranslator.ts +178 -0
  108. package/srcV2/pageObject.ts +105 -0
  109. /package/docs/{BaseApi-explanation.md → v1/BaseApi-explanation.md} +0 -0
  110. /package/docs/{BasePage-explanation.md → v1/BasePage-explanation.md} +0 -0
  111. /package/docs/{LocatorSchema-explanation.md → v1/LocatorSchema-explanation.md} +0 -0
  112. /package/docs/{LocatorSchemaPath-explanation.md → v1/LocatorSchemaPath-explanation.md} +0 -0
  113. /package/docs/{PlaywrightReportLogger-explanation.md → v1/PlaywrightReportLogger-explanation.md} +0 -0
  114. /package/docs/{get-locator-methods-explanation.md → v1/get-locator-methods-explanation.md} +0 -0
  115. /package/docs/{intro-to-using-pomwright.md → v1/intro-to-using-pomwright.md} +0 -0
  116. /package/docs/{sessionStorage-methods-explanation.md → v1/sessionStorage-methods-explanation.md} +0 -0
  117. /package/docs/{tips-folder-structure.md → v1/tips-folder-structure.md} +0 -0
@@ -0,0 +1,558 @@
1
+ import type { Page } from "@playwright/test";
2
+ import type { LocatorRegistryInternal } from "./locatorRegistry";
3
+ import type {
4
+ AltTextDefinition,
5
+ FilterDefinition,
6
+ IdDefinition,
7
+ IndexSelector,
8
+ LabelDefinition,
9
+ LocatorDefinition,
10
+ LocatorDescription,
11
+ LocatorSchemaRecord,
12
+ LocatorStep,
13
+ LocatorStrategyDefinition,
14
+ LocatorStrategyDefinitionPatch,
15
+ PlaceholderDefinition,
16
+ RegistryPath,
17
+ RoleDefinition,
18
+ TextDefinition,
19
+ TitleDefinition,
20
+ } from "./types";
21
+ import { applyDefinitionPatch, normalizeIdValue, normalizeSteps } from "./utils";
22
+
23
+ export type LocatorRegistrationPostDefinitionBuilder<
24
+ LocatorSchemaPathType extends string,
25
+ Path extends RegistryPath<LocatorSchemaPathType>,
26
+ > = {
27
+ filter: (
28
+ filter: FilterDefinition<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>,
29
+ ) => LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
30
+ nth: (index: IndexSelector) => LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
31
+ describe: (description: LocatorDescription) => LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
32
+ };
33
+
34
+ export class LocatorRegistrationBuilder<
35
+ LocatorSchemaPathType extends string,
36
+ Path extends RegistryPath<LocatorSchemaPathType>,
37
+ Seeded extends boolean = false,
38
+ > {
39
+ private steps: LocatorStep<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>[] = [];
40
+ private definition?: LocatorStrategyDefinition;
41
+ private description?: LocatorDescription;
42
+ private registered = false;
43
+ private readonly reuseType?: LocatorStrategyDefinition["type"];
44
+ private readonly seededDefinition: boolean;
45
+ private overrideApplied = false;
46
+ private postDefinitionView?: LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
47
+
48
+ constructor(
49
+ private readonly registry: LocatorRegistryInternal<LocatorSchemaPathType>,
50
+ private readonly path: Path,
51
+ seed?: {
52
+ initialDefinition?: LocatorStrategyDefinition;
53
+ initialSteps?: LocatorStep<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>[];
54
+ reuseType?: LocatorStrategyDefinition["type"];
55
+ initialDescription?: LocatorDescription;
56
+ },
57
+ ) {
58
+ if (seed?.initialSteps) {
59
+ this.steps = normalizeSteps<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>(seed.initialSteps);
60
+ }
61
+
62
+ if (seed?.initialDefinition) {
63
+ this.definition = seed.initialDefinition;
64
+ this.seededDefinition = true;
65
+ } else {
66
+ this.seededDefinition = false;
67
+ }
68
+
69
+ this.reuseType = seed?.reuseType;
70
+ this.description = seed?.initialDescription;
71
+ }
72
+
73
+ persistSeededDefinition() {
74
+ if (this.seededDefinition && !this.registered) {
75
+ this.persist();
76
+ }
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Records a Playwright-style filter on the locator being registered. Filters are applied in the
82
+ * order they are chained and can include `has`/`hasNot` locator references. Requires that a
83
+ * locator strategy has already been set for this registration.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * registry.add("list.item").locator("li").filter({ hasText: /Row/ });
88
+ * ```
89
+ */
90
+ filter(
91
+ filter: FilterDefinition<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>,
92
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
93
+ this.applyFilter(filter);
94
+ return this.getPostDefinitionView();
95
+ }
96
+
97
+ /**
98
+ * Adds an index selector for the locator being registered. Indices are applied in the order they
99
+ * are chained and require a locator definition to have been set first.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * registry.add("table.rows").locator("tr").nth(0);
104
+ * ```
105
+ */
106
+ nth(index: IndexSelector): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
107
+ this.applyIndex(index);
108
+ return this.getPostDefinitionView();
109
+ }
110
+
111
+ describe(description: LocatorDescription): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
112
+ this.description = description;
113
+ this.persist();
114
+ return this.getPostDefinitionView();
115
+ }
116
+
117
+ /**
118
+ * Uses Playwright `getByRole` semantics to define the locator strategy. Accepts a role and
119
+ * optional options; when seeded via `{ reuse }`, an options-only call patches the seeded role.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * registry.add("nav.home").getByRole("link", { name: "Home" });
124
+ * ```
125
+ */
126
+ getByRole(
127
+ role: RoleDefinition["role"],
128
+ options: RoleDefinition["options"],
129
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
130
+ getByRole(role: RoleDefinition["role"]): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
131
+ getByRole(
132
+ options: Seeded extends true ? RoleDefinition["options"] : never,
133
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
134
+ getByRole(roleOrOptions: RoleDefinition["role"] | RoleDefinition["options"], options?: RoleDefinition["options"]) {
135
+ const definition: LocatorStrategyDefinitionPatch =
136
+ typeof roleOrOptions === "string"
137
+ ? options !== undefined
138
+ ? { type: "role", role: roleOrOptions, options }
139
+ : { type: "role", role: roleOrOptions }
140
+ : { type: "role", options: roleOrOptions };
141
+
142
+ return this.commit(definition);
143
+ }
144
+
145
+ /**
146
+ * Uses Playwright `getByText` semantics to define the locator strategy. Accepts string/RegExp
147
+ * text and optional options; when seeded, an options-only call patches the seeded text.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * registry.add("alert.error").getByText(/failed/i);
152
+ * ```
153
+ */
154
+ getByText(
155
+ text: Parameters<Page["getByText"]>[0],
156
+ options: Parameters<Page["getByText"]>[1],
157
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
158
+ getByText(
159
+ text: Parameters<Page["getByText"]>[0],
160
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
161
+ getByText(
162
+ options: Seeded extends true ? Parameters<Page["getByText"]>[1] : never,
163
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
164
+ getByText(
165
+ textOrOptions: Parameters<Page["getByText"]>[0] | Parameters<Page["getByText"]>[1],
166
+ options?: Parameters<Page["getByText"]>[1],
167
+ ) {
168
+ const definition: LocatorStrategyDefinitionPatch =
169
+ typeof textOrOptions === "string" || textOrOptions instanceof RegExp
170
+ ? options !== undefined
171
+ ? ({ type: "text", text: textOrOptions, options } as TextDefinition)
172
+ : ({ type: "text", text: textOrOptions } as TextDefinition)
173
+ : { type: "text", options: textOrOptions };
174
+
175
+ return this.commit(definition);
176
+ }
177
+
178
+ /**
179
+ * Uses Playwright `getByLabel` semantics to define the locator strategy. Accepts label text and
180
+ * optional options; when seeded, an options-only call patches the seeded text.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * registry.add("form.email").getByLabel("Email", { exact: true });
185
+ * ```
186
+ */
187
+ getByLabel(
188
+ text: Parameters<Page["getByLabel"]>[0],
189
+ options: Parameters<Page["getByLabel"]>[1],
190
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
191
+ getByLabel(
192
+ text: Parameters<Page["getByLabel"]>[0],
193
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
194
+ getByLabel(
195
+ options: Seeded extends true ? Parameters<Page["getByLabel"]>[1] : never,
196
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
197
+ getByLabel(
198
+ textOrOptions: Parameters<Page["getByLabel"]>[0] | Parameters<Page["getByLabel"]>[1],
199
+ options?: Parameters<Page["getByLabel"]>[1],
200
+ ) {
201
+ const definition: LocatorStrategyDefinitionPatch =
202
+ typeof textOrOptions === "string" || textOrOptions instanceof RegExp
203
+ ? options !== undefined
204
+ ? ({ type: "label", text: textOrOptions, options } as LabelDefinition)
205
+ : ({ type: "label", text: textOrOptions } as LabelDefinition)
206
+ : { type: "label", options: textOrOptions };
207
+
208
+ return this.commit(definition);
209
+ }
210
+
211
+ /**
212
+ * Uses Playwright `getByPlaceholder` semantics to define the locator strategy. Accepts placeholder
213
+ * text and optional options; when seeded, an options-only call patches the seeded text.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * registry.add("form.search").getByPlaceholder("Search", { exact: true });
218
+ * ```
219
+ */
220
+ getByPlaceholder(
221
+ text: Parameters<Page["getByPlaceholder"]>[0],
222
+ options: Parameters<Page["getByPlaceholder"]>[1],
223
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
224
+ getByPlaceholder(
225
+ text: Parameters<Page["getByPlaceholder"]>[0],
226
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
227
+ getByPlaceholder(
228
+ options: Seeded extends true ? Parameters<Page["getByPlaceholder"]>[1] : never,
229
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
230
+ getByPlaceholder(
231
+ textOrOptions: Parameters<Page["getByPlaceholder"]>[0] | Parameters<Page["getByPlaceholder"]>[1],
232
+ options?: Parameters<Page["getByPlaceholder"]>[1],
233
+ ) {
234
+ const definition: LocatorStrategyDefinitionPatch =
235
+ typeof textOrOptions === "string" || textOrOptions instanceof RegExp
236
+ ? options !== undefined
237
+ ? ({ type: "placeholder", text: textOrOptions, options } as PlaceholderDefinition)
238
+ : ({ type: "placeholder", text: textOrOptions } as PlaceholderDefinition)
239
+ : { type: "placeholder", options: textOrOptions };
240
+
241
+ return this.commit(definition);
242
+ }
243
+
244
+ /**
245
+ * Uses Playwright `getByAltText` semantics to define the locator strategy. Accepts alt text and
246
+ * optional options; when seeded, an options-only call patches the seeded text.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * registry.add("image.logo").getByAltText(/brand/);
251
+ * ```
252
+ */
253
+ getByAltText(
254
+ text: Parameters<Page["getByAltText"]>[0],
255
+ options: Parameters<Page["getByAltText"]>[1],
256
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
257
+ getByAltText(
258
+ text: Parameters<Page["getByAltText"]>[0],
259
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
260
+ getByAltText(
261
+ options: Seeded extends true ? Parameters<Page["getByAltText"]>[1] : never,
262
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
263
+ getByAltText(
264
+ textOrOptions: Parameters<Page["getByAltText"]>[0] | Parameters<Page["getByAltText"]>[1],
265
+ options?: Parameters<Page["getByAltText"]>[1],
266
+ ) {
267
+ const definition: LocatorStrategyDefinitionPatch =
268
+ typeof textOrOptions === "string" || textOrOptions instanceof RegExp
269
+ ? options !== undefined
270
+ ? ({ type: "altText", text: textOrOptions, options } as AltTextDefinition)
271
+ : ({ type: "altText", text: textOrOptions } as AltTextDefinition)
272
+ : { type: "altText", options: textOrOptions };
273
+
274
+ return this.commit(definition);
275
+ }
276
+
277
+ /**
278
+ * Uses Playwright `getByTitle` semantics to define the locator strategy. Accepts title text and
279
+ * optional options; when seeded, an options-only call patches the seeded text.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * registry.add("icon.info").getByTitle("Info");
284
+ * ```
285
+ */
286
+ getByTitle(
287
+ text: Parameters<Page["getByTitle"]>[0],
288
+ options: Parameters<Page["getByTitle"]>[1],
289
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
290
+ getByTitle(
291
+ text: Parameters<Page["getByTitle"]>[0],
292
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
293
+ getByTitle(
294
+ options: Seeded extends true ? Parameters<Page["getByTitle"]>[1] : never,
295
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
296
+ getByTitle(
297
+ textOrOptions: Parameters<Page["getByTitle"]>[0] | Parameters<Page["getByTitle"]>[1],
298
+ options?: Parameters<Page["getByTitle"]>[1],
299
+ ) {
300
+ const definition: LocatorStrategyDefinitionPatch =
301
+ typeof textOrOptions === "string" || textOrOptions instanceof RegExp
302
+ ? options !== undefined
303
+ ? ({ type: "title", text: textOrOptions, options } as TitleDefinition)
304
+ : ({ type: "title", text: textOrOptions } as TitleDefinition)
305
+ : { type: "title", options: textOrOptions };
306
+
307
+ return this.commit(definition);
308
+ }
309
+
310
+ /**
311
+ * Uses Playwright `locator` semantics to define the locator strategy. Accepts a selector and
312
+ * optional options; when seeded, an options-only call patches the seeded selector by merging
313
+ * options while inheriting the selector.
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * registry.add("list.items").locator("ul > li");
318
+ * ```
319
+ */
320
+ locator(
321
+ selector: Parameters<Page["locator"]>[0],
322
+ options: Parameters<Page["locator"]>[1],
323
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
324
+ locator(
325
+ selector: Parameters<Page["locator"]>[0],
326
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
327
+ locator(
328
+ options: Seeded extends true ? Parameters<Page["locator"]>[1] : never,
329
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path>;
330
+ locator(
331
+ selectorOrOptions: Parameters<Page["locator"]>[0] | Parameters<Page["locator"]>[1],
332
+ options?: Parameters<Page["locator"]>[1],
333
+ ) {
334
+ const definition: LocatorStrategyDefinitionPatch =
335
+ typeof selectorOrOptions === "string"
336
+ ? options !== undefined
337
+ ? ({ type: "locator", selector: selectorOrOptions, options } as LocatorDefinition)
338
+ : ({ type: "locator", selector: selectorOrOptions } as LocatorDefinition)
339
+ : { type: "locator", options: selectorOrOptions };
340
+
341
+ return this.commit(definition);
342
+ }
343
+
344
+ /**
345
+ * Uses Playwright `frameLocator` semantics to define the locator strategy, entering the targeted
346
+ * frame for subsequent chained locators. When seeded, `selector` may be omitted to retain the
347
+ * existing selector while overriding options elsewhere.
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * registry.add("frame.login").frameLocator("iframe.auth");
352
+ * ```
353
+ */
354
+ frameLocator(
355
+ selector: Seeded extends true
356
+ ? Parameters<Page["frameLocator"]>[0] | undefined
357
+ : Parameters<Page["frameLocator"]>[0],
358
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
359
+ const definition: LocatorStrategyDefinitionPatch = selector
360
+ ? { type: "frameLocator", selector }
361
+ : { type: "frameLocator" };
362
+ return this.commit(definition);
363
+ }
364
+
365
+ /**
366
+ * Uses Playwright `getByTestId` semantics to define the locator strategy. When seeded, omitting
367
+ * the argument inherits the seeded `testId` while allowing options from other overrides.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * registry.add("card.title").getByTestId("card-title");
372
+ * ```
373
+ */
374
+ getByTestId(
375
+ testId: Seeded extends true ? Parameters<Page["getByTestId"]>[0] | undefined : Parameters<Page["getByTestId"]>[0],
376
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
377
+ const definition: LocatorStrategyDefinitionPatch = testId ? { type: "testId", testId } : { type: "testId" };
378
+ return this.commit(definition);
379
+ }
380
+
381
+ /**
382
+ * Targets elements by `id`, normalizing string or RegExp input. When seeded, the argument can be
383
+ * omitted to inherit the seeded id.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * registry.add("modal.close").getById("close-modal");
388
+ * ```
389
+ */
390
+ getById(
391
+ id: Seeded extends true ? string | RegExp | undefined : string | RegExp,
392
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
393
+ const definition: LocatorStrategyDefinitionPatch = id
394
+ ? { type: "id", id: normalizeIdValue(id) as IdDefinition["id"] }
395
+ : { type: "id" };
396
+ return this.commit(definition);
397
+ }
398
+
399
+ private commit(
400
+ definition: LocatorStrategyDefinition | LocatorStrategyDefinitionPatch,
401
+ ): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
402
+ this.ensureDefinitionAllowedWithRollback(definition as LocatorStrategyDefinition);
403
+ const mergedDefinition = this.seededDefinition
404
+ ? applyDefinitionPatch(this.definition as LocatorStrategyDefinition, definition)
405
+ : (definition as LocatorStrategyDefinition);
406
+ this.definition = mergedDefinition;
407
+ if (this.reuseType && this.seededDefinition) {
408
+ this.overrideApplied = true;
409
+ }
410
+ this.persist();
411
+ return this.getPostDefinitionView();
412
+ }
413
+
414
+ private applyFilter(
415
+ filter: FilterDefinition<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>,
416
+ ) {
417
+ this.ensureDefinition();
418
+ this.steps.push({ kind: "filter", filter });
419
+ this.persist();
420
+ }
421
+
422
+ private applyIndex(index: IndexSelector) {
423
+ this.ensureDefinition();
424
+ this.steps.push({ kind: "index", index });
425
+ this.persist();
426
+ }
427
+
428
+ private ensureDefinitionAllowedWithRollback(definition: LocatorStrategyDefinition) {
429
+ if (this.seededDefinition) {
430
+ if (definition.type !== this.reuseType) {
431
+ this.rollbackSeededRegistration();
432
+ throw new Error(
433
+ `The locator definition for "${this.path}" must use the "${this.reuseType}" strategy when reusing a locator.`,
434
+ );
435
+ }
436
+
437
+ if (this.overrideApplied) {
438
+ throw new Error(
439
+ `A locator definition for "${this.path}" was already provided from reuse; only one matching override is allowed.`,
440
+ );
441
+ }
442
+
443
+ return;
444
+ }
445
+
446
+ if (this.definition) {
447
+ throw new Error(
448
+ `A locator definition for "${this.path}" has already been provided; only one locator type can be set for a registration.`,
449
+ );
450
+ }
451
+ }
452
+
453
+ private ensureDefinition() {
454
+ if (!this.definition) {
455
+ throw new Error(`A locator definition must be provided before applying filters or indices for "${this.path}".`);
456
+ }
457
+ }
458
+
459
+ private persist() {
460
+ if (!this.definition) {
461
+ throw new Error(`No locator schema definition provided for path "${this.path}".`);
462
+ }
463
+
464
+ const record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>> = {
465
+ locatorSchemaPath: this.path,
466
+ definition: this.definition,
467
+ steps: normalizeSteps<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>(this.steps),
468
+ ...(this.description !== undefined ? { description: this.description } : {}),
469
+ };
470
+
471
+ if (this.registered) {
472
+ this.registry.replace(this.path, record);
473
+ } else {
474
+ this.registry.register(this.path, record);
475
+ this.registered = true;
476
+ }
477
+ }
478
+
479
+ private rollbackSeededRegistration() {
480
+ if (this.seededDefinition && this.registered) {
481
+ this.registry.unregister(this.path);
482
+ this.registered = false;
483
+ }
484
+ }
485
+
486
+ private getPostDefinitionView(): LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> {
487
+ if (this.postDefinitionView) {
488
+ return this.postDefinitionView;
489
+ }
490
+
491
+ const view: LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> = {
492
+ filter: (filter: FilterDefinition<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>) => {
493
+ this.applyFilter(filter);
494
+ return view;
495
+ },
496
+ nth: (index: IndexSelector) => {
497
+ this.applyIndex(index);
498
+ return view;
499
+ },
500
+ describe: (description: LocatorDescription) => {
501
+ this.description = description;
502
+ this.persist();
503
+ return view;
504
+ },
505
+ };
506
+
507
+ this.postDefinitionView = view;
508
+ return view;
509
+ }
510
+ }
511
+
512
+ export type LocatorRegistrationPreDefinitionBuilder<
513
+ LocatorSchemaPathType extends string,
514
+ Path extends RegistryPath<LocatorSchemaPathType>,
515
+ Seeded extends boolean = false,
516
+ > = LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> &
517
+ Pick<
518
+ LocatorRegistrationBuilder<LocatorSchemaPathType, Path, Seeded>,
519
+ | "getByRole"
520
+ | "getByText"
521
+ | "getByLabel"
522
+ | "getByPlaceholder"
523
+ | "getByAltText"
524
+ | "getByTitle"
525
+ | "locator"
526
+ | "frameLocator"
527
+ | "getByTestId"
528
+ | "getById"
529
+ >;
530
+
531
+ type LocatorMethodForType<Type extends LocatorStrategyDefinition["type"]> = Type extends "role"
532
+ ? "getByRole"
533
+ : Type extends "text"
534
+ ? "getByText"
535
+ : Type extends "label"
536
+ ? "getByLabel"
537
+ : Type extends "placeholder"
538
+ ? "getByPlaceholder"
539
+ : Type extends "altText"
540
+ ? "getByAltText"
541
+ : Type extends "title"
542
+ ? "getByTitle"
543
+ : Type extends "locator"
544
+ ? "locator"
545
+ : Type extends "frameLocator"
546
+ ? "frameLocator"
547
+ : Type extends "testId"
548
+ ? "getByTestId"
549
+ : Type extends "id"
550
+ ? "getById"
551
+ : never;
552
+
553
+ export type LocatorRegistrationSeededBuilderForType<
554
+ LocatorSchemaPathType extends string,
555
+ Path extends RegistryPath<LocatorSchemaPathType>,
556
+ SeededType extends LocatorStrategyDefinition["type"],
557
+ > = LocatorRegistrationPostDefinitionBuilder<LocatorSchemaPathType, Path> &
558
+ Pick<LocatorRegistrationBuilder<LocatorSchemaPathType, Path, true>, LocatorMethodForType<SeededType>>;