pomwright 1.5.0 → 2.0.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/CHANGELOG.md +36 -0
  2. package/README.md +5 -5
  3. package/dist/index.d.mts +91 -989
  4. package/dist/index.d.ts +91 -989
  5. package/dist/index.js +627 -1887
  6. package/dist/index.mjs +633 -1888
  7. package/package.json +9 -11
  8. package/AGENTS.md +0 -37
  9. package/docs/v1/BaseApi-explanation.md +0 -63
  10. package/docs/v1/BasePage-explanation.md +0 -96
  11. package/docs/v1/LocatorSchema-explanation.md +0 -271
  12. package/docs/v1/LocatorSchemaPath-explanation.md +0 -165
  13. package/docs/v1/PlaywrightReportLogger-explanation.md +0 -56
  14. package/docs/v1/get-locator-methods-explanation.md +0 -250
  15. package/docs/v1/intro-to-using-pomwright.md +0 -899
  16. package/docs/v1/sessionStorage-methods-explanation.md +0 -38
  17. package/docs/v1/tips-folder-structure.md +0 -38
  18. package/docs/v1-to-v2-migration/bridge-migration-guide.md +0 -159
  19. package/docs/v1-to-v2-migration/direct-migration-guide.md +0 -238
  20. package/docs/v1-to-v2-migration/v1-to-v2-comparison.md +0 -547
  21. package/docs/v2/PageObject.md +0 -293
  22. package/docs/v2/composing-locator-modules.md +0 -93
  23. package/docs/v2/locator-registry.md +0 -693
  24. package/docs/v2/logging.md +0 -168
  25. package/docs/v2/overview.md +0 -515
  26. package/docs/v2/session-storage.md +0 -160
  27. package/index.ts +0 -75
  28. package/intTestV2/.env +0 -0
  29. package/intTestV2/fixtures/testApp.fixtures.ts +0 -43
  30. package/intTestV2/package.json +0 -22
  31. package/intTestV2/page-object-models/testApp/pages/iframe/iframe.locatorSchema.ts +0 -24
  32. package/intTestV2/page-object-models/testApp/pages/iframe/iframe.page.ts +0 -17
  33. package/intTestV2/page-object-models/testApp/pages/testPage.locatorSchema.ts +0 -32
  34. package/intTestV2/page-object-models/testApp/pages/testPage.page.ts +0 -119
  35. package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.locatorSchema.ts +0 -29
  36. package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.page.ts +0 -48
  37. package/intTestV2/page-object-models/testApp/pages/testPath/testPath.locatorSchema.ts +0 -9
  38. package/intTestV2/page-object-models/testApp/pages/testPath/testPath.page.ts +0 -23
  39. package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.locatorSchema.ts +0 -114
  40. package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.page.ts +0 -23
  41. package/intTestV2/page-object-models/testApp/testApp.base.ts +0 -20
  42. package/intTestV2/playwright.config.ts +0 -54
  43. package/intTestV2/server.js +0 -216
  44. package/intTestV2/test-data/staticPage/index.html +0 -280
  45. package/intTestV2/test-data/staticPage/w3images/avatar2.png +0 -0
  46. package/intTestV2/test-data/staticPage/w3images/avatar3.png +0 -0
  47. package/intTestV2/test-data/staticPage/w3images/avatar5.png +0 -0
  48. package/intTestV2/test-data/staticPage/w3images/avatar6.png +0 -0
  49. package/intTestV2/test-data/staticPage/w3images/forest.jpg +0 -0
  50. package/intTestV2/test-data/staticPage/w3images/lights.jpg +0 -0
  51. package/intTestV2/test-data/staticPage/w3images/mountains.jpg +0 -0
  52. package/intTestV2/test-data/staticPage/w3images/nature.jpg +0 -0
  53. package/intTestV2/test-data/staticPage/w3images/snow.jpg +0 -0
  54. package/intTestV2/tests/locatorRegistry/add/add.describe.spec.ts +0 -54
  55. package/intTestV2/tests/locatorRegistry/add/add.filter.spec.ts +0 -143
  56. package/intTestV2/tests/locatorRegistry/add/add.frameLocator.spec.ts +0 -23
  57. package/intTestV2/tests/locatorRegistry/add/add.getByAltText.spec.ts +0 -23
  58. package/intTestV2/tests/locatorRegistry/add/add.getById.spec.ts +0 -45
  59. package/intTestV2/tests/locatorRegistry/add/add.getByLabel.spec.ts +0 -23
  60. package/intTestV2/tests/locatorRegistry/add/add.getByPlaceholder.spec.ts +0 -23
  61. package/intTestV2/tests/locatorRegistry/add/add.getByRole.spec.ts +0 -23
  62. package/intTestV2/tests/locatorRegistry/add/add.getByTestId.spec.ts +0 -23
  63. package/intTestV2/tests/locatorRegistry/add/add.getByText.spec.ts +0 -23
  64. package/intTestV2/tests/locatorRegistry/add/add.getByTitle.spec.ts +0 -23
  65. package/intTestV2/tests/locatorRegistry/add/add.locator.spec.ts +0 -23
  66. package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +0 -66
  67. package/intTestV2/tests/locatorRegistry/add/add.reuseReusable.spec.ts +0 -311
  68. package/intTestV2/tests/locatorRegistry/add/add.spec.ts +0 -159
  69. package/intTestV2/tests/locatorRegistry/filter.cycle.spec.ts +0 -39
  70. package/intTestV2/tests/locatorRegistry/getLocator/getLocator.spec.ts +0 -253
  71. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.clearSteps.spec.ts +0 -105
  72. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.describe.spec.ts +0 -23
  73. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.filter.spec.ts +0 -368
  74. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getLocator.spec.ts +0 -56
  75. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getNestedLocator.spec.ts +0 -175
  76. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.nth.spec.ts +0 -60
  77. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.remove.spec.ts +0 -32
  78. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.replace.spec.ts +0 -24
  79. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.spec.ts +0 -110
  80. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.update.spec.ts +0 -322
  81. package/intTestV2/tests/locatorRegistry/getNestedLocator/getNestedLocator.spec.ts +0 -412
  82. package/intTestV2/tests/locatorRegistry/registry/registry.binding.spec.ts +0 -50
  83. package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.spec.ts +0 -115
  84. package/intTestV2/tests/locatorRegistry/validation/validation.sub-path.spec.ts +0 -45
  85. package/intTestV2/tests/step/step.spec.ts +0 -49
  86. package/intTestV2/tests/testApp/color.spec.ts +0 -15
  87. package/intTestV2/tests/testApp/iframe.spec.ts +0 -57
  88. package/intTestV2/tests/testApp/testFilters.spec.ts +0 -24
  89. package/intTestV2/tests/testApp/testPage.spec.ts +0 -161
  90. package/intTestV2/tests/testApp/testPath.spec.ts +0 -18
  91. package/pack-build.sh +0 -11
  92. package/pack-test-v2.sh +0 -36
  93. package/playwright.base.ts +0 -42
  94. package/skills/README.md +0 -56
  95. package/skills/pomwright-v1-5-bridge-migration/SKILL.md +0 -40
  96. package/skills/pomwright-v1-5-bridge-migration/references/call-site-migration.md +0 -178
  97. package/skills/pomwright-v1-5-bridge-migration/references/schema-translation.md +0 -183
  98. package/skills/pomwright-v2-migration/SKILL.md +0 -63
  99. package/skills/pomwright-v2-migration/references/call-site-migration.md +0 -265
  100. package/skills/pomwright-v2-migration/references/class-migration.md +0 -266
  101. package/skills/pomwright-v2-migration/references/fixture-and-helpers.md +0 -423
  102. package/skills/pomwright-v2-migration/references/locator-registration.md +0 -344
  103. package/srcV2/fixture/base.fixtures.ts +0 -23
  104. package/srcV2/helpers/navigation.ts +0 -153
  105. package/srcV2/helpers/playwrightReportLogger.ts +0 -196
  106. package/srcV2/helpers/sessionStorage.ts +0 -251
  107. package/srcV2/helpers/stepDecorator.ts +0 -106
  108. package/srcV2/locators/index.ts +0 -15
  109. package/srcV2/locators/locatorQueryBuilder.ts +0 -427
  110. package/srcV2/locators/locatorRegistrationBuilder.ts +0 -558
  111. package/srcV2/locators/locatorRegistry.ts +0 -541
  112. package/srcV2/locators/locatorUpdateBuilder.ts +0 -602
  113. package/srcV2/locators/reusableLocatorBuilder.ts +0 -200
  114. package/srcV2/locators/types.ts +0 -256
  115. package/srcV2/locators/utils.ts +0 -309
  116. package/srcV2/locators/v1SchemaTranslator.ts +0 -178
  117. package/srcV2/pageObject.ts +0 -105
@@ -1,541 +0,0 @@
1
- import type { Locator, Page } from "@playwright/test";
2
- import { LocatorQueryBuilder, type LocatorQueryBuilderPublic } from "./locatorQueryBuilder";
3
- import {
4
- LocatorRegistrationBuilder,
5
- type LocatorRegistrationPreDefinitionBuilder,
6
- type LocatorRegistrationSeededBuilderForType,
7
- } from "./locatorRegistrationBuilder";
8
- import { ReusableLocatorFactory } from "./reusableLocatorBuilder";
9
- import type {
10
- FilterDefinition,
11
- FilterLocatorReference,
12
- IndexSelector,
13
- LocatorBuilderTarget,
14
- LocatorDescription,
15
- LocatorSchemaPathErrors,
16
- LocatorSchemaRecord,
17
- LocatorStep,
18
- LocatorStrategyDefinition,
19
- PlaywrightFilterDefinition,
20
- RegistryPath,
21
- ResolvedFilterDefinition,
22
- ReusableLocator,
23
- } from "./types";
24
- import {
25
- applyIndexSelector,
26
- cloneLocatorStrategyDefinition,
27
- createLocator,
28
- expandSchemaPath,
29
- isFrameLocatorDefinition,
30
- isLocatorInstance,
31
- normalizeSteps,
32
- stringifyForLog,
33
- validateLocatorSchemaPath,
34
- } from "./utils";
35
-
36
- export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
37
- private readonly schemas = new Map<
38
- RegistryPath<LocatorSchemaPathType>,
39
- LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>
40
- >();
41
-
42
- /**
43
- * Factory for reusable locator seeds that capture a locator strategy plus any chained
44
- * `filter`/`nth` steps without registering them. Pass the resulting seed to
45
- * {@link LocatorRegistryInternal.add} via `{ reuse }` to register a path that inherits the
46
- * stored definition and steps.
47
- *
48
- * @example
49
- * ```ts
50
- * const seed = registry.createReusable.getByRole("heading", { level: 2 }).filter({ hasText: /Summary/ });
51
- * registry.add("hero.title", { reuse: seed }).getByRole({ name: "Summary" });
52
- * ```
53
- */
54
- readonly createReusable: ReusableLocatorFactory<LocatorSchemaPathType>;
55
-
56
- constructor(private readonly page: Page) {
57
- this.createReusable = new ReusableLocatorFactory<LocatorSchemaPathType>();
58
- }
59
-
60
- private normalizeRecord(record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>) {
61
- return {
62
- locatorSchemaPath: record.locatorSchemaPath,
63
- definition: record.definition,
64
- steps: normalizeSteps<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>(record.steps),
65
- ...(record.description !== undefined ? { description: record.description } : {}),
66
- } satisfies LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
67
- }
68
-
69
- private cloneRecordForReuse(
70
- record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>,
71
- path: RegistryPath<LocatorSchemaPathType>,
72
- ) {
73
- return {
74
- locatorSchemaPath: path,
75
- definition: cloneLocatorStrategyDefinition(record.definition),
76
- steps: normalizeSteps<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>(record.steps),
77
- ...(record.description !== undefined ? { description: record.description } : {}),
78
- } satisfies LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
79
- }
80
-
81
- /**
82
- * Registers a locator schema at the provided dot-delimited path.
83
- * Accepts exactly one locator strategy (`getByRole`, `locator`, etc.) and any number of
84
- * `filter`/`nth` steps chained in call order. When `{ reuse }` is supplied, the seeded
85
- * definition is applied first and one matching override is allowed as a PATCH of the seed;
86
- * otherwise, calling multiple locator strategies will throw.
87
- *
88
- * @example
89
- * ```ts
90
- * registry
91
- * .add("list.item")
92
- * .getByRole("listitem", { name: /Row/ })
93
- * .filter({ hasText: "Row" })
94
- * .nth("last");
95
- * ```
96
- */
97
- add(
98
- path: RegistryPath<LocatorSchemaPathType>,
99
- ): LocatorRegistrationPreDefinitionBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false>;
100
- add(path: RegistryPath<LocatorSchemaPathType>, options: { reuse: RegistryPath<LocatorSchemaPathType> }): void;
101
- add<
102
- Reuse extends ReusableLocator<
103
- LocatorSchemaPathType,
104
- RegistryPath<LocatorSchemaPathType>,
105
- LocatorStrategyDefinition["type"]
106
- >,
107
- >(
108
- path: RegistryPath<LocatorSchemaPathType>,
109
- options: { reuse: Reuse },
110
- ): LocatorRegistrationSeededBuilderForType<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, Reuse["type"]>;
111
- add(
112
- path: RegistryPath<LocatorSchemaPathType>,
113
- options?: {
114
- reuse?:
115
- | ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]>
116
- | RegistryPath<LocatorSchemaPathType>;
117
- },
118
- ):
119
- | LocatorRegistrationPreDefinitionBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false>
120
- | LocatorRegistrationSeededBuilderForType<
121
- LocatorSchemaPathType,
122
- RegistryPath<LocatorSchemaPathType>,
123
- LocatorStrategyDefinition["type"]
124
- >
125
- | undefined {
126
- const reuse = options?.reuse;
127
-
128
- if (!reuse) {
129
- return new LocatorRegistrationBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false>(
130
- this,
131
- path,
132
- );
133
- }
134
-
135
- if (typeof reuse === "string") {
136
- const sourceRecord = this.get(reuse as RegistryPath<LocatorSchemaPathType>);
137
- const cloned = this.cloneRecordForReuse(sourceRecord, path);
138
-
139
- this.register(path, cloned);
140
- return undefined;
141
- }
142
-
143
- const reusedRecord: LocatorSchemaRecord<
144
- LocatorSchemaPathType,
145
- RegistryPath<LocatorSchemaPathType>
146
- > = this.cloneRecordForReuse(
147
- {
148
- locatorSchemaPath: path,
149
- definition: reuse.definition,
150
- steps: reuse.steps,
151
- description: reuse.description,
152
- },
153
- path,
154
- );
155
-
156
- return new LocatorRegistrationBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, true>(
157
- this,
158
- path,
159
- {
160
- initialDefinition: reusedRecord.definition,
161
- initialSteps: reusedRecord.steps,
162
- reuseType: reusedRecord.definition.type,
163
- initialDescription: reusedRecord.description,
164
- },
165
- ).persistSeededDefinition();
166
- }
167
-
168
- register(
169
- path: RegistryPath<LocatorSchemaPathType>,
170
- record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>,
171
- ) {
172
- validateLocatorSchemaPath(path);
173
- if (this.schemas.has(path)) {
174
- const existing = this.schemas.get(path);
175
- if (!existing) {
176
- throw new Error(`A locator schema with the path "${path}" already exists.`);
177
- }
178
- const errorDetails = stringifyForLog({
179
- existing: existing,
180
- attempted: record,
181
- });
182
- throw new Error(`A locator schema with the path "${path}" already exists.\nExisting Schema: ${errorDetails}`);
183
- }
184
- this.schemas.set(path, this.normalizeRecord(record));
185
- }
186
-
187
- replace(
188
- path: RegistryPath<LocatorSchemaPathType>,
189
- record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>,
190
- ) {
191
- validateLocatorSchemaPath(path);
192
- if (!this.schemas.has(path)) {
193
- throw new Error(`No locator schema registered for path "${path}".`);
194
- }
195
- this.schemas.set(path, this.normalizeRecord(record));
196
- }
197
-
198
- get(
199
- path: RegistryPath<LocatorSchemaPathType>,
200
- ): LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>> {
201
- const record = this.schemas.get(path);
202
- if (!record) {
203
- throw new Error(`No locator schema registered for path "${path}".`);
204
- }
205
- return {
206
- locatorSchemaPath: record.locatorSchemaPath,
207
- definition: record.definition,
208
- steps: normalizeSteps(record.steps),
209
- ...(record.description !== undefined ? { description: record.description } : {}),
210
- } satisfies LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
211
- }
212
-
213
- unregister(path: RegistryPath<LocatorSchemaPathType>) {
214
- this.schemas.delete(path);
215
- }
216
-
217
- getIfExists(
218
- path: RegistryPath<LocatorSchemaPathType>,
219
- ): LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>> | undefined {
220
- const record = this.schemas.get(path);
221
- if (!record) {
222
- return undefined;
223
- }
224
-
225
- return {
226
- locatorSchemaPath: record.locatorSchemaPath,
227
- definition: record.definition,
228
- steps: normalizeSteps(record.steps),
229
- ...(record.description !== undefined ? { description: record.description } : {}),
230
- } satisfies LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
231
- }
232
-
233
- private resolveFilterLocator(
234
- locatorReference: FilterLocatorReference<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>,
235
- context: {
236
- rootPath: RegistryPath<LocatorSchemaPathType>;
237
- resolvingPaths: Set<string>;
238
- },
239
- ) {
240
- if (isLocatorInstance(locatorReference)) {
241
- return locatorReference;
242
- }
243
-
244
- if (typeof locatorReference === "string") {
245
- return this.getLocatorWithContext(locatorReference as RegistryPath<LocatorSchemaPathType>, context);
246
- }
247
-
248
- throw new Error(
249
- `Unsupported filter reference while resolving "${context.rootPath}". ` +
250
- "Filter has/hasNot supports Playwright Locator instances or registry path strings.",
251
- );
252
- }
253
-
254
- private createResolutionContext(rootPath: RegistryPath<LocatorSchemaPathType>) {
255
- return {
256
- rootPath,
257
- resolvingPaths: new Set<string>(),
258
- };
259
- }
260
-
261
- private getLocatorWithContext(
262
- path: RegistryPath<LocatorSchemaPathType>,
263
- context: {
264
- rootPath: RegistryPath<LocatorSchemaPathType>;
265
- resolvingPaths: Set<string>;
266
- },
267
- ) {
268
- if (context.resolvingPaths.has(path)) {
269
- throw new Error(`Detected cyclic filter reference while resolving "${context.rootPath}": "${path}".`);
270
- }
271
-
272
- context.resolvingPaths.add(path);
273
- try {
274
- const record = this.get(path);
275
- const definitions = new Map<string, LocatorStrategyDefinition>([[path, record.definition]]);
276
- const steps = new Map<
277
- string,
278
- LocatorStep<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>[]
279
- >([[path, normalizeSteps(record.steps)]]);
280
- const { locator } = this.buildLocatorChain(path, definitions, steps, undefined, record.description, context);
281
- if (!locator) {
282
- throw new Error(`Unable to resolve direct locator for path "${path}".`);
283
- }
284
- return locator as Locator;
285
- } finally {
286
- context.resolvingPaths.delete(path);
287
- }
288
- }
289
-
290
- private resolveFiltersForTarget(
291
- filters: FilterDefinition<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>[] | undefined,
292
- context: {
293
- rootPath: RegistryPath<LocatorSchemaPathType>;
294
- resolvingPaths: Set<string>;
295
- },
296
- ) {
297
- if (!filters || filters.length === 0) {
298
- return [] as ResolvedFilterDefinition[];
299
- }
300
-
301
- const resolved: ResolvedFilterDefinition[] = [];
302
- for (const filter of filters) {
303
- if (filter && typeof filter === "object" && ("has" in filter || "hasNot" in filter)) {
304
- const { has, hasNot, ...rest } = filter as FilterDefinition<
305
- RegistryPath<LocatorSchemaPathType>,
306
- RegistryPath<LocatorSchemaPathType>
307
- > & {
308
- has?: FilterLocatorReference<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>;
309
- hasNot?: FilterLocatorReference<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>;
310
- };
311
-
312
- const normalizedFilter: ResolvedFilterDefinition = {
313
- ...(rest as PlaywrightFilterDefinition),
314
- } as ResolvedFilterDefinition;
315
-
316
- if (has !== undefined) {
317
- normalizedFilter.has = this.resolveFilterLocator(has, context);
318
- }
319
-
320
- if (hasNot !== undefined) {
321
- normalizedFilter.hasNot = this.resolveFilterLocator(hasNot, context);
322
- }
323
-
324
- resolved.push(normalizedFilter);
325
- continue;
326
- }
327
-
328
- resolved.push(filter as ResolvedFilterDefinition);
329
- }
330
-
331
- return resolved;
332
- }
333
-
334
- /**
335
- * Returns a mutable query builder clone for the provided path. Use the builder to add or clear
336
- * `filter`/`nth` steps, or to `update`/`replace` locator definitions before resolving locators.
337
- * Changes affect only the builder clone; the registry’s stored schema remains untouched.
338
- *
339
- * @example
340
- * ```ts
341
- * const builder = registry
342
- * .getLocatorSchema("section.button")
343
- * .filter("section.button", { hasText: /Save/ })
344
- * .nth("section", 0);
345
- * const locator = builder.getNestedLocator();
346
- * ```
347
- */
348
- getLocatorSchema<Path extends RegistryPath<LocatorSchemaPathType>>(
349
- path: Path,
350
- ): LocatorQueryBuilderPublic<LocatorSchemaPathType, Path> {
351
- return new LocatorQueryBuilder<LocatorSchemaPathType, Path>(this, path);
352
- }
353
-
354
- /**
355
- * Resolves the Playwright {@link Locator} for the provided path, applying only the terminal
356
- * definition and its recorded steps (no ancestor chaining). Throws if the path is not registered.
357
- *
358
- * @example
359
- * ```ts
360
- * const button = registry.getLocator("form.submit");
361
- * await button.click();
362
- * ```
363
- */
364
- getLocator<Path extends RegistryPath<LocatorSchemaPathType>>(path: Path): Locator {
365
- return this.getLocatorSchema(path).getLocator();
366
- }
367
-
368
- /**
369
- * Resolves a chained Playwright {@link Locator} for a nested path, traversing each registered
370
- * segment and applying their steps in order. Throws if any segment in the chain is missing.
371
- *
372
- * @example
373
- * ```ts
374
- * const nested = registry.getNestedLocator("list.item");
375
- * await expect(nested).toBeVisible();
376
- * ```
377
- */
378
- getNestedLocator<Path extends RegistryPath<LocatorSchemaPathType>>(path: Path): Locator {
379
- return this.getLocatorSchema(path).getNestedLocator();
380
- }
381
-
382
- buildLocatorChain(
383
- path: RegistryPath<LocatorSchemaPathType>,
384
- definitions: Map<string, LocatorStrategyDefinition>,
385
- steps: Map<string, LocatorStep<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>[]>,
386
- tombstones?: Set<string>,
387
- terminalDescription?: LocatorDescription,
388
- context?: {
389
- rootPath: RegistryPath<LocatorSchemaPathType>;
390
- resolvingPaths: Set<string>;
391
- },
392
- ) {
393
- const resolutionContext = context ?? this.createResolutionContext(path);
394
- const chain = expandSchemaPath(path);
395
- const registeredChain = chain.filter((part) => definitions.has(part));
396
-
397
- if (tombstones?.has(path) || !definitions.has(path)) {
398
- throw new Error(`No locator schema registered for path "${path}".`);
399
- }
400
-
401
- let currentTarget: LocatorBuilderTarget = this.page;
402
- let lastLocator: Locator | null = null;
403
- const debugSteps: {
404
- path: string;
405
- definition: LocatorStrategyDefinition;
406
- appliedFilters: ResolvedFilterDefinition[];
407
- index?: IndexSelector | null | undefined;
408
- recordedSteps: LocatorStep<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>[];
409
- }[] = [];
410
-
411
- const lastIndex = registeredChain.length - 1;
412
-
413
- for (const [index, part] of registeredChain.entries()) {
414
- const isTerminalStep = index === lastIndex;
415
- const definition = definitions.get(part);
416
- if (tombstones?.has(part)) {
417
- if (isTerminalStep) {
418
- throw new Error(`No locator schema registered for path "${path}".`);
419
- }
420
- continue;
421
- }
422
-
423
- if (!definition) {
424
- throw new Error(`Missing locator definition for "${part}" while resolving "${path}".`);
425
- }
426
-
427
- if (isFrameLocatorDefinition(definition)) {
428
- const frameLocator = createLocator(currentTarget, definition) as ReturnType<Page["frameLocator"]>;
429
- const isTerminalStep = index === lastIndex;
430
-
431
- if (isTerminalStep) {
432
- const ownerLocator = frameLocator.owner();
433
- currentTarget = ownerLocator;
434
- lastLocator = ownerLocator;
435
- } else {
436
- currentTarget = frameLocator as LocatorBuilderTarget;
437
- lastLocator = frameLocator as unknown as Locator;
438
- }
439
-
440
- if (isTerminalStep && terminalDescription && isLocatorInstance(lastLocator)) {
441
- lastLocator = lastLocator.describe(terminalDescription);
442
- currentTarget = lastLocator;
443
- }
444
-
445
- debugSteps.push({ path: part, definition, appliedFilters: [], recordedSteps: [] });
446
- continue;
447
- }
448
-
449
- const locatorResult = createLocator(currentTarget, definition) as Locator;
450
- const recordedSteps = steps.get(part) ?? [];
451
-
452
- let resolvedLocator = locatorResult;
453
- const appliedFilters: ResolvedFilterDefinition[] = [];
454
- let appliedIndex: IndexSelector | null | undefined;
455
-
456
- for (const step of recordedSteps) {
457
- if (step.kind === "filter") {
458
- const [resolvedFilter] = this.resolveFiltersForTarget([step.filter], resolutionContext);
459
- if (resolvedFilter) {
460
- resolvedLocator = resolvedLocator.filter(resolvedFilter);
461
- appliedFilters.push(resolvedFilter);
462
- }
463
- } else {
464
- appliedIndex = step.index ?? null;
465
- resolvedLocator = applyIndexSelector(resolvedLocator, appliedIndex ?? undefined);
466
- }
467
- }
468
-
469
- if (isTerminalStep && terminalDescription) {
470
- resolvedLocator = resolvedLocator.describe(terminalDescription);
471
- }
472
-
473
- currentTarget = resolvedLocator;
474
- lastLocator = resolvedLocator;
475
- debugSteps.push({
476
- path: part,
477
- definition,
478
- appliedFilters,
479
- index: appliedIndex,
480
- recordedSteps,
481
- });
482
- }
483
-
484
- return { locator: lastLocator, steps: debugSteps };
485
- }
486
- }
487
-
488
- export type GetLocatorAccessor<LocatorSchemaPathType extends string> = <
489
- Path extends RegistryPath<LocatorSchemaPathType>,
490
- >(
491
- path: Path,
492
- ) => Locator;
493
-
494
- export type AddAccessor<LocatorSchemaPathType extends string> = LocatorRegistryInternal<LocatorSchemaPathType>["add"];
495
-
496
- export type GetLocatorSchemaAccessor<LocatorSchemaPathType extends string> = <
497
- Path extends RegistryPath<LocatorSchemaPathType>,
498
- >(
499
- path: Path,
500
- ) => LocatorQueryBuilderPublic<LocatorSchemaPathType, Path>;
501
-
502
- export type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <
503
- Path extends RegistryPath<LocatorSchemaPathType>,
504
- >(
505
- path: Path,
506
- ) => Locator;
507
-
508
- export type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<
509
- LocatorRegistryInternal<LocatorSchemaPathType>,
510
- "add" | "createReusable" | "getLocator" | "getLocatorSchema" | "getNestedLocator"
511
- >;
512
-
513
- /**
514
- * Creates a v2 locator registry bound to a Playwright {@link Page} and returns the registry plus
515
- * pre-bound helpers for ergonomic use in page objects and tests. Path unions are validated at
516
- * compile time and runtime. The returned `add`/`getLocator`/`getNestedLocator`/`getLocatorSchema`
517
- * accessors are pre-bound to the registry instance for dependency injection.
518
- *
519
- * @example
520
- * ```ts
521
- * const { registry, add, getNestedLocator, getLocatorSchema } =
522
- * createRegistryWithAccessors<"root" | "root.child">(page);
523
- *
524
- * registry.add("root").locator("section");
525
- * registry.add("root.child").getByRole("heading", { level: 2 });
526
- *
527
- * const nested = getNestedLocator("root.child");
528
- * const patched = getLocatorSchema("root.child").filter("root.child", { hasText: /Docs/ }).getNestedLocator();
529
- * ```
530
- */
531
- export const createRegistryWithAccessors = <Paths extends string>(page: Page) => {
532
- type PathErrors = LocatorSchemaPathErrors<Paths>;
533
- const _assertValidPaths: PathErrors extends never ? true : never = true as PathErrors extends never ? true : never;
534
- const registryInstance = new LocatorRegistryInternal<Paths>(page);
535
- const add: LocatorRegistryInternal<Paths>["add"] = registryInstance.add.bind(registryInstance);
536
- const getLocator: GetLocatorAccessor<Paths> = registryInstance.getLocator.bind(registryInstance);
537
- const getNestedLocator: GetNestedLocatorAccessor<Paths> = registryInstance.getNestedLocator.bind(registryInstance);
538
- const getLocatorSchema: GetLocatorSchemaAccessor<Paths> = registryInstance.getLocatorSchema.bind(registryInstance);
539
- const registry: LocatorRegistry<Paths> = registryInstance;
540
- return { registry, add, getLocator, getNestedLocator, getLocatorSchema } as const;
541
- };