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,602 @@
1
+ import type { Page } from "@playwright/test";
2
+ import type { LocatorQueryBuilder } from "./locatorQueryBuilder";
3
+ import type {
4
+ LocatorChainPaths,
5
+ LocatorStrategyDefinition,
6
+ LocatorUpdate,
7
+ RegistryPath,
8
+ RoleDefinition,
9
+ } from "./types";
10
+ import { normalizeIdValue } from "./utils";
11
+
12
+ const parseUpdateArguments = <Primary, OptionsType>(
13
+ primaryOrOptions?: Primary | OptionsType,
14
+ options?: OptionsType,
15
+ optionsProvided?: boolean,
16
+ ) => {
17
+ let primary: Primary | undefined;
18
+ let parsedOptions: OptionsType | undefined;
19
+ let hasOptions = optionsProvided ?? false;
20
+
21
+ if (options !== undefined) {
22
+ parsedOptions = options;
23
+ hasOptions = true;
24
+ }
25
+
26
+ if (primaryOrOptions !== undefined) {
27
+ const treatAsOptions =
28
+ !hasOptions &&
29
+ options === undefined &&
30
+ typeof primaryOrOptions === "object" &&
31
+ !(primaryOrOptions instanceof RegExp);
32
+
33
+ if (treatAsOptions) {
34
+ parsedOptions = primaryOrOptions as OptionsType;
35
+ hasOptions = true;
36
+ } else {
37
+ primary = primaryOrOptions as Primary;
38
+ }
39
+ }
40
+
41
+ return { primary, options: parsedOptions, hasOptions };
42
+ };
43
+
44
+ const mergeOptions = <OptionsType>(
45
+ currentOptions: OptionsType | undefined,
46
+ updates: { options?: OptionsType } | undefined,
47
+ ) => {
48
+ if (updates && "options" in updates) {
49
+ const updateOptions = updates.options;
50
+ if (updateOptions && typeof updateOptions === "object") {
51
+ return {
52
+ ...(typeof currentOptions === "object" && currentOptions !== null ? currentOptions : {}),
53
+ ...updateOptions,
54
+ } as OptionsType;
55
+ }
56
+ return updateOptions as OptionsType;
57
+ }
58
+
59
+ return currentOptions as OptionsType;
60
+ };
61
+
62
+ type UpdateArgsWithOptions<Primary, Options> = [] | [Primary] | [Options] | [Primary, Options];
63
+
64
+ type UpdateArgsWithoutOptions<Primary> = [] | [Primary];
65
+
66
+ const mergeLocatorDefinition = (
67
+ current: LocatorStrategyDefinition,
68
+ updates: LocatorUpdate,
69
+ path: string,
70
+ preferredSource?: LocatorStrategyDefinition,
71
+ baseline?: LocatorStrategyDefinition,
72
+ ): LocatorStrategyDefinition => {
73
+ if (!updates || typeof updates !== "object" || !("type" in updates)) {
74
+ throw new Error(`Locator update for "${path}" requires a "type" property.`);
75
+ }
76
+
77
+ const source = <TType extends LocatorStrategyDefinition["type"]>(targetType: TType) => {
78
+ if (current.type === targetType) {
79
+ return current as Extract<LocatorStrategyDefinition, { type: TType }>;
80
+ }
81
+ if (preferredSource?.type === targetType) {
82
+ return preferredSource as Extract<LocatorStrategyDefinition, { type: TType }>;
83
+ }
84
+ if (baseline?.type === targetType) {
85
+ return baseline as Extract<LocatorStrategyDefinition, { type: TType }>;
86
+ }
87
+ return undefined;
88
+ };
89
+
90
+ switch (updates.type) {
91
+ case "role": {
92
+ const roleSource = source("role");
93
+ const role = updates.role ?? roleSource?.role;
94
+ if (role === undefined) {
95
+ throw new Error(`Locator update for "${path}" of type "role" requires a "role" value.`);
96
+ }
97
+ const options = mergeOptions(roleSource?.options, updates);
98
+ return options !== undefined
99
+ ? ({ type: "role", role, options } as RoleDefinition)
100
+ : ({ type: "role", role } as RoleDefinition);
101
+ }
102
+ case "text": {
103
+ const textSource = source("text");
104
+ const text = updates.text ?? textSource?.text;
105
+ if (text === undefined) {
106
+ throw new Error(`Locator update for "${path}" of type "text" requires a "text" value.`);
107
+ }
108
+ const options = mergeOptions(textSource?.options, updates);
109
+ return options !== undefined
110
+ ? ({ type: "text", text, options } as LocatorStrategyDefinition)
111
+ : ({ type: "text", text } as LocatorStrategyDefinition);
112
+ }
113
+ case "label": {
114
+ const textSource = source("label");
115
+ const text = updates.text ?? textSource?.text;
116
+ if (text === undefined) {
117
+ throw new Error(`Locator update for "${path}" of type "label" requires a "text" value.`);
118
+ }
119
+ const options = mergeOptions(textSource?.options, updates);
120
+ return options !== undefined
121
+ ? ({ type: "label", text, options } as LocatorStrategyDefinition)
122
+ : ({ type: "label", text } as LocatorStrategyDefinition);
123
+ }
124
+ case "placeholder": {
125
+ const textSource = source("placeholder");
126
+ const text = updates.text ?? textSource?.text;
127
+ if (text === undefined) {
128
+ throw new Error(`Locator update for "${path}" of type "placeholder" requires a "text" value.`);
129
+ }
130
+ const options = mergeOptions(textSource?.options, updates);
131
+ return options !== undefined
132
+ ? ({ type: "placeholder", text, options } as LocatorStrategyDefinition)
133
+ : ({ type: "placeholder", text } as LocatorStrategyDefinition);
134
+ }
135
+ case "altText": {
136
+ const textSource = source("altText");
137
+ const text = updates.text ?? textSource?.text;
138
+ if (text === undefined) {
139
+ throw new Error(`Locator update for "${path}" of type "altText" requires a "text" value.`);
140
+ }
141
+ const options = mergeOptions(textSource?.options, updates);
142
+ return options !== undefined
143
+ ? ({ type: "altText", text, options } as LocatorStrategyDefinition)
144
+ : ({ type: "altText", text } as LocatorStrategyDefinition);
145
+ }
146
+ case "title": {
147
+ const textSource = source("title");
148
+ const text = updates.text ?? textSource?.text;
149
+ if (text === undefined) {
150
+ throw new Error(`Locator update for "${path}" of type "title" requires a "text" value.`);
151
+ }
152
+ const options = mergeOptions(textSource?.options, updates);
153
+ return options !== undefined
154
+ ? ({ type: "title", text, options } as LocatorStrategyDefinition)
155
+ : ({ type: "title", text } as LocatorStrategyDefinition);
156
+ }
157
+ case "locator": {
158
+ const selectorSource = source("locator");
159
+ const selector = updates.selector ?? selectorSource?.selector;
160
+ if (selector === undefined) {
161
+ throw new Error(`Locator update for "${path}" of type "locator" requires a "selector" value.`);
162
+ }
163
+ const options = mergeOptions(selectorSource?.options, updates);
164
+ return options !== undefined
165
+ ? ({ type: "locator", selector, options } as LocatorStrategyDefinition)
166
+ : ({ type: "locator", selector } as LocatorStrategyDefinition);
167
+ }
168
+ case "frameLocator": {
169
+ const selectorSource = source("frameLocator");
170
+ const selector = updates.selector ?? selectorSource?.selector;
171
+ if (selector === undefined) {
172
+ throw new Error(`Locator update for "${path}" of type "frameLocator" requires a "selector" value.`);
173
+ }
174
+ return { type: "frameLocator", selector } as LocatorStrategyDefinition;
175
+ }
176
+ case "testId": {
177
+ const testIdSource = source("testId");
178
+ const testId = updates.testId ?? testIdSource?.testId;
179
+ if (testId === undefined) {
180
+ throw new Error(`Locator update for "${path}" of type "testId" requires a "testId" value.`);
181
+ }
182
+ return { type: "testId", testId } as LocatorStrategyDefinition;
183
+ }
184
+ case "id": {
185
+ const idSource = source("id");
186
+ const rawId = updates.id ?? idSource?.id;
187
+ const id = normalizeIdValue(rawId);
188
+ if (id === undefined) {
189
+ throw new Error(`Locator update for "${path}" of type "id" requires an "id" value.`);
190
+ }
191
+ return { type: "id", id } as LocatorStrategyDefinition;
192
+ }
193
+ default: {
194
+ const exhaustive: never = updates;
195
+ return exhaustive;
196
+ }
197
+ }
198
+ };
199
+
200
+ const buildReplacementDefinition = (updates: LocatorUpdate, path: string): LocatorStrategyDefinition => {
201
+ if (!updates || typeof updates !== "object" || !("type" in updates)) {
202
+ throw new Error(`Locator replace for "${path}" requires a "type" property.`);
203
+ }
204
+
205
+ switch (updates.type) {
206
+ case "role": {
207
+ const { role, options } = updates;
208
+ if (role === undefined) {
209
+ throw new Error(`Locator replace for "${path}" of type "role" requires a "role" value.`);
210
+ }
211
+ return options !== undefined
212
+ ? ({ type: "role", role, options } as RoleDefinition)
213
+ : ({ type: "role", role } as RoleDefinition);
214
+ }
215
+ case "text": {
216
+ const { text, options } = updates;
217
+ if (text === undefined) {
218
+ throw new Error(`Locator replace for "${path}" of type "text" requires a "text" value.`);
219
+ }
220
+ return options !== undefined
221
+ ? ({ type: "text", text, options } as LocatorStrategyDefinition)
222
+ : ({ type: "text", text } as LocatorStrategyDefinition);
223
+ }
224
+ case "label": {
225
+ const { text, options } = updates;
226
+ if (text === undefined) {
227
+ throw new Error(`Locator replace for "${path}" of type "label" requires a "text" value.`);
228
+ }
229
+ return options !== undefined
230
+ ? ({ type: "label", text, options } as LocatorStrategyDefinition)
231
+ : ({ type: "label", text } as LocatorStrategyDefinition);
232
+ }
233
+ case "placeholder": {
234
+ const { text, options } = updates;
235
+ if (text === undefined) {
236
+ throw new Error(`Locator replace for "${path}" of type "placeholder" requires a "text" value.`);
237
+ }
238
+ return options !== undefined
239
+ ? ({ type: "placeholder", text, options } as LocatorStrategyDefinition)
240
+ : ({ type: "placeholder", text } as LocatorStrategyDefinition);
241
+ }
242
+ case "altText": {
243
+ const { text, options } = updates;
244
+ if (text === undefined) {
245
+ throw new Error(`Locator replace for "${path}" of type "altText" requires a "text" value.`);
246
+ }
247
+ return options !== undefined
248
+ ? ({ type: "altText", text, options } as LocatorStrategyDefinition)
249
+ : ({ type: "altText", text } as LocatorStrategyDefinition);
250
+ }
251
+ case "title": {
252
+ const { text, options } = updates;
253
+ if (text === undefined) {
254
+ throw new Error(`Locator replace for "${path}" of type "title" requires a "text" value.`);
255
+ }
256
+ return options !== undefined
257
+ ? ({ type: "title", text, options } as LocatorStrategyDefinition)
258
+ : ({ type: "title", text } as LocatorStrategyDefinition);
259
+ }
260
+ case "locator": {
261
+ const { selector, options } = updates;
262
+ if (selector === undefined) {
263
+ throw new Error(`Locator replace for "${path}" of type "locator" requires a "selector" value.`);
264
+ }
265
+ return options !== undefined
266
+ ? ({ type: "locator", selector, options } as LocatorStrategyDefinition)
267
+ : ({ type: "locator", selector } as LocatorStrategyDefinition);
268
+ }
269
+ case "frameLocator": {
270
+ const { selector } = updates;
271
+ if (selector === undefined) {
272
+ throw new Error(`Locator replace for "${path}" of type "frameLocator" requires a "selector" value.`);
273
+ }
274
+ return { type: "frameLocator", selector } as LocatorStrategyDefinition;
275
+ }
276
+ case "testId": {
277
+ const { testId } = updates;
278
+ if (testId === undefined) {
279
+ throw new Error(`Locator replace for "${path}" of type "testId" requires a "testId" value.`);
280
+ }
281
+ return { type: "testId", testId } as LocatorStrategyDefinition;
282
+ }
283
+ case "id": {
284
+ const rawId = updates.id;
285
+ const id = normalizeIdValue(rawId);
286
+ if (id === undefined) {
287
+ throw new Error(`Locator replace for "${path}" of type "id" requires an "id" value.`);
288
+ }
289
+ return { type: "id", id } as LocatorStrategyDefinition;
290
+ }
291
+ default: {
292
+ const exhaustive: never = updates;
293
+ return exhaustive;
294
+ }
295
+ }
296
+ };
297
+ export class LocatorUpdateBuilder<
298
+ LocatorSchemaPathType extends string,
299
+ LocatorSubstring extends RegistryPath<LocatorSchemaPathType>,
300
+ SubPath extends LocatorChainPaths<RegistryPath<LocatorSchemaPathType>, LocatorSubstring>,
301
+ > {
302
+ constructor(
303
+ private readonly parent: LocatorQueryBuilder<LocatorSchemaPathType, LocatorSubstring>,
304
+ private readonly subPath: SubPath,
305
+ private readonly mode: "update" | "replace" = "update",
306
+ ) {}
307
+
308
+ /**
309
+ * Defines or patches a `getByRole` locator strategy for the target subpath. In `update` mode the
310
+ * `role` and `options` arguments are optional (PATCH semantics); in `replace` mode they follow
311
+ * Playwright requirements (POST semantics) and `role` is required.
312
+ *
313
+ * @example
314
+ * ```ts
315
+ * getLocatorSchema("form.button")
316
+ * .update("form.button")
317
+ * .getByRole({ name: "Save" })
318
+ * .getNestedLocator();
319
+ * ```
320
+ */
321
+ getByRole(...args: UpdateArgsWithOptions<RoleDefinition["role"], RoleDefinition["options"]>) {
322
+ const [roleOrOptions, options] = args;
323
+ const {
324
+ primary: role,
325
+ options: parsedOptions,
326
+ hasOptions,
327
+ } = parseUpdateArguments<RoleDefinition["role"], RoleDefinition["options"]>(
328
+ roleOrOptions,
329
+ options,
330
+ args.length >= 2,
331
+ );
332
+
333
+ const definition: LocatorUpdate = {
334
+ type: "role",
335
+ ...(role !== undefined ? { role } : {}),
336
+ ...(hasOptions ? { options: parsedOptions } : {}),
337
+ };
338
+
339
+ return this.commit(definition);
340
+ }
341
+
342
+ /**
343
+ * Defines or patches a `getByText` locator strategy for the target subpath. In `update` mode,
344
+ * text/options are optional; in `replace` mode text is required.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * getLocatorSchema("banner.message")
349
+ * .replace("banner.message")
350
+ * .getByText(/Updated/, { exact: false })
351
+ * .getNestedLocator();
352
+ * ```
353
+ */
354
+ getByText(...args: UpdateArgsWithOptions<Parameters<Page["getByText"]>[0], Parameters<Page["getByText"]>[1]>) {
355
+ const [textOrOptions, options] = args;
356
+ const {
357
+ primary: text,
358
+ options: parsedOptions,
359
+ hasOptions,
360
+ } = parseUpdateArguments<Parameters<Page["getByText"]>[0], Parameters<Page["getByText"]>[1]>(
361
+ textOrOptions,
362
+ options,
363
+ args.length >= 2,
364
+ );
365
+
366
+ const definition: LocatorUpdate = {
367
+ type: "text",
368
+ ...(text !== undefined ? { text } : {}),
369
+ ...(hasOptions ? { options: parsedOptions } : {}),
370
+ };
371
+
372
+ return this.commit(definition);
373
+ }
374
+
375
+ /**
376
+ * Defines or patches a `getByLabel` locator strategy for the target subpath. In `update` mode the
377
+ * label text/options are optional; in `replace` mode text is required.
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * getLocatorSchema("form.email").update("form.email").getByLabel({ exact: true }).getNestedLocator();
382
+ * ```
383
+ */
384
+ getByLabel(...args: UpdateArgsWithOptions<Parameters<Page["getByLabel"]>[0], Parameters<Page["getByLabel"]>[1]>) {
385
+ const [textOrOptions, options] = args;
386
+ const {
387
+ primary: text,
388
+ options: parsedOptions,
389
+ hasOptions,
390
+ } = parseUpdateArguments<Parameters<Page["getByLabel"]>[0], Parameters<Page["getByLabel"]>[1]>(
391
+ textOrOptions,
392
+ options,
393
+ args.length >= 2,
394
+ );
395
+
396
+ const definition: LocatorUpdate = {
397
+ type: "label",
398
+ ...(text !== undefined ? { text } : {}),
399
+ ...(hasOptions ? { options: parsedOptions } : {}),
400
+ };
401
+
402
+ return this.commit(definition);
403
+ }
404
+
405
+ /**
406
+ * Defines or patches a `getByPlaceholder` locator strategy for the target subpath. In `update`
407
+ * mode text/options are optional; in `replace` mode text is required.
408
+ *
409
+ * @example
410
+ * ```ts
411
+ * getLocatorSchema("form.search").update("form.search").getByPlaceholder({ exact: true }).getNestedLocator();
412
+ * ```
413
+ */
414
+ getByPlaceholder(
415
+ ...args: UpdateArgsWithOptions<Parameters<Page["getByPlaceholder"]>[0], Parameters<Page["getByPlaceholder"]>[1]>
416
+ ) {
417
+ const [textOrOptions, options] = args;
418
+ const {
419
+ primary: text,
420
+ options: parsedOptions,
421
+ hasOptions,
422
+ } = parseUpdateArguments<Parameters<Page["getByPlaceholder"]>[0], Parameters<Page["getByPlaceholder"]>[1]>(
423
+ textOrOptions,
424
+ options,
425
+ args.length >= 2,
426
+ );
427
+
428
+ const definition: LocatorUpdate = {
429
+ type: "placeholder",
430
+ ...(text !== undefined ? { text } : {}),
431
+ ...(hasOptions ? { options: parsedOptions } : {}),
432
+ };
433
+
434
+ return this.commit(definition);
435
+ }
436
+
437
+ /**
438
+ * Defines or patches a `getByAltText` locator strategy for the target subpath. In `update` mode
439
+ * text/options are optional; in `replace` mode text is required.
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * getLocatorSchema("image.logo").replace("image.logo").getByAltText("Logo").getNestedLocator();
444
+ * ```
445
+ */
446
+ getByAltText(
447
+ ...args: UpdateArgsWithOptions<Parameters<Page["getByAltText"]>[0], Parameters<Page["getByAltText"]>[1]>
448
+ ) {
449
+ const [textOrOptions, options] = args;
450
+ const {
451
+ primary: text,
452
+ options: parsedOptions,
453
+ hasOptions,
454
+ } = parseUpdateArguments<Parameters<Page["getByAltText"]>[0], Parameters<Page["getByAltText"]>[1]>(
455
+ textOrOptions,
456
+ options,
457
+ args.length >= 2,
458
+ );
459
+
460
+ const definition: LocatorUpdate = {
461
+ type: "altText",
462
+ ...(text !== undefined ? { text } : {}),
463
+ ...(hasOptions ? { options: parsedOptions } : {}),
464
+ };
465
+
466
+ return this.commit(definition);
467
+ }
468
+
469
+ /**
470
+ * Defines or patches a `getByTitle` locator strategy for the target subpath. In `update` mode
471
+ * text/options are optional; in `replace` mode text is required.
472
+ *
473
+ * @example
474
+ * ```ts
475
+ * getLocatorSchema("icon.info").update("icon.info").getByTitle({ exact: true }).getNestedLocator();
476
+ * ```
477
+ */
478
+ getByTitle(...args: UpdateArgsWithOptions<Parameters<Page["getByTitle"]>[0], Parameters<Page["getByTitle"]>[1]>) {
479
+ const [textOrOptions, options] = args;
480
+ const {
481
+ primary: text,
482
+ options: parsedOptions,
483
+ hasOptions,
484
+ } = parseUpdateArguments<Parameters<Page["getByTitle"]>[0], Parameters<Page["getByTitle"]>[1]>(
485
+ textOrOptions,
486
+ options,
487
+ args.length >= 2,
488
+ );
489
+
490
+ const definition: LocatorUpdate = {
491
+ type: "title",
492
+ ...(text !== undefined ? { text } : {}),
493
+ ...(hasOptions ? { options: parsedOptions } : {}),
494
+ };
495
+
496
+ return this.commit(definition);
497
+ }
498
+
499
+ /**
500
+ * Defines or patches a `locator` strategy for the target subpath. In `update` mode selector and
501
+ * options are optional and merge with the existing definition; in `replace` mode selector is
502
+ * required.
503
+ *
504
+ * @example
505
+ * ```ts
506
+ * getLocatorSchema("list.items")
507
+ * .replace("list.items")
508
+ * .locator("ul > li", { hasText: /Row/ })
509
+ * .getNestedLocator();
510
+ * ```
511
+ */
512
+ locator(...args: UpdateArgsWithOptions<Parameters<Page["locator"]>[0], Parameters<Page["locator"]>[1]>) {
513
+ const [selectorOrOptions, options] = args;
514
+ const {
515
+ primary: selector,
516
+ options: parsedOptions,
517
+ hasOptions,
518
+ } = parseUpdateArguments<Parameters<Page["locator"]>[0], Parameters<Page["locator"]>[1]>(
519
+ selectorOrOptions,
520
+ options,
521
+ args.length >= 2,
522
+ );
523
+
524
+ const definition: LocatorUpdate = {
525
+ type: "locator",
526
+ ...(selector !== undefined ? { selector } : {}),
527
+ ...(hasOptions ? { options: parsedOptions } : {}),
528
+ };
529
+
530
+ return this.commit(definition);
531
+ }
532
+
533
+ /**
534
+ * Defines or patches a `frameLocator` strategy for the target subpath. In `update` mode the
535
+ * selector is optional and, when omitted, inherits the existing selector; in `replace` mode the
536
+ * selector is required.
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * getLocatorSchema("frame.login").replace("frame.login").frameLocator("iframe.auth").getNestedLocator();
541
+ * ```
542
+ */
543
+ frameLocator(...args: UpdateArgsWithoutOptions<Parameters<Page["frameLocator"]>[0]>) {
544
+ const [selector] = args;
545
+
546
+ const definition: LocatorUpdate = {
547
+ type: "frameLocator",
548
+ ...(selector !== undefined ? { selector } : {}),
549
+ };
550
+
551
+ return this.commit(definition);
552
+ }
553
+
554
+ /**
555
+ * Defines or patches a `getByTestId` locator strategy for the target subpath. In `update` mode
556
+ * `testId` is optional and merges with existing options; in `replace` mode it is required.
557
+ *
558
+ * @example
559
+ * ```ts
560
+ * getLocatorSchema("card.title").update("card.title").getByTestId().getNestedLocator();
561
+ * ```
562
+ */
563
+ getByTestId(...args: UpdateArgsWithoutOptions<Parameters<Page["getByTestId"]>[0]>) {
564
+ const [testId] = args;
565
+
566
+ const definition: LocatorUpdate = {
567
+ type: "testId",
568
+ ...(testId !== undefined ? { testId } : {}),
569
+ };
570
+
571
+ return this.commit(definition);
572
+ }
573
+
574
+ /**
575
+ * Defines or patches an `id` locator strategy for the target subpath. In `update` mode the id is
576
+ * optional and will be normalized if provided; in `replace` mode the id is required.
577
+ *
578
+ * @example
579
+ * ```ts
580
+ * getLocatorSchema("modal.close").update("modal.close").getById().getNestedLocator();
581
+ * ```
582
+ */
583
+ getById(...args: UpdateArgsWithoutOptions<string | RegExp>) {
584
+ const [idValue] = args;
585
+ const id = idValue !== undefined ? normalizeIdValue(idValue) : undefined;
586
+
587
+ const definition: LocatorUpdate = {
588
+ type: "id",
589
+ ...(id !== undefined ? { id } : {}),
590
+ };
591
+
592
+ return this.commit(definition);
593
+ }
594
+
595
+ private commit(definition: LocatorUpdate) {
596
+ return this.mode === "replace"
597
+ ? this.parent.applyReplacement(this.subPath, definition)
598
+ : this.parent.applyUpdate(this.subPath, definition);
599
+ }
600
+ }
601
+
602
+ export { buildReplacementDefinition, mergeLocatorDefinition };