pomwright 1.5.1 → 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 (119) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +5 -5
  3. package/dist/index.d.mts +75 -970
  4. package/dist/index.d.ts +75 -970
  5. package/dist/index.js +585 -1872
  6. package/dist/index.mjs +598 -1880
  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 -695
  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.get.clone.spec.ts +0 -76
  58. package/intTestV2/tests/locatorRegistry/add/add.getByAltText.spec.ts +0 -23
  59. package/intTestV2/tests/locatorRegistry/add/add.getById.spec.ts +0 -45
  60. package/intTestV2/tests/locatorRegistry/add/add.getByLabel.spec.ts +0 -23
  61. package/intTestV2/tests/locatorRegistry/add/add.getByPlaceholder.spec.ts +0 -23
  62. package/intTestV2/tests/locatorRegistry/add/add.getByRole.spec.ts +0 -23
  63. package/intTestV2/tests/locatorRegistry/add/add.getByTestId.spec.ts +0 -23
  64. package/intTestV2/tests/locatorRegistry/add/add.getByText.spec.ts +0 -23
  65. package/intTestV2/tests/locatorRegistry/add/add.getByTitle.spec.ts +0 -23
  66. package/intTestV2/tests/locatorRegistry/add/add.locator.spec.ts +0 -23
  67. package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +0 -107
  68. package/intTestV2/tests/locatorRegistry/add/add.reuseReusable.spec.ts +0 -311
  69. package/intTestV2/tests/locatorRegistry/add/add.spec.ts +0 -159
  70. package/intTestV2/tests/locatorRegistry/filter.cycle.spec.ts +0 -39
  71. package/intTestV2/tests/locatorRegistry/getLocator/getLocator.spec.ts +0 -253
  72. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.clearSteps.spec.ts +0 -105
  73. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.describe.spec.ts +0 -23
  74. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.filter.spec.ts +0 -368
  75. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getLocator.spec.ts +0 -56
  76. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getNestedLocator.spec.ts +0 -175
  77. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.nth.spec.ts +0 -60
  78. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.remove.spec.ts +0 -32
  79. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.replace.spec.ts +0 -24
  80. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.spec.ts +0 -110
  81. package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.update.spec.ts +0 -322
  82. package/intTestV2/tests/locatorRegistry/getNestedLocator/getNestedLocator.spec.ts +0 -412
  83. package/intTestV2/tests/locatorRegistry/registry/registry.binding.spec.ts +0 -50
  84. package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.spec.ts +0 -115
  85. package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.typecheck.ts +0 -86
  86. package/intTestV2/tests/locatorRegistry/validation/validation.sub-path.spec.ts +0 -45
  87. package/intTestV2/tests/step/step.spec.ts +0 -49
  88. package/intTestV2/tests/testApp/color.spec.ts +0 -15
  89. package/intTestV2/tests/testApp/iframe.spec.ts +0 -57
  90. package/intTestV2/tests/testApp/testFilters.spec.ts +0 -24
  91. package/intTestV2/tests/testApp/testPage.spec.ts +0 -161
  92. package/intTestV2/tests/testApp/testPath.spec.ts +0 -18
  93. package/pack-build.sh +0 -11
  94. package/pack-test-v2.sh +0 -36
  95. package/playwright.base.ts +0 -42
  96. package/skills/README.md +0 -56
  97. package/skills/pomwright-v1-5-bridge-migration/SKILL.md +0 -40
  98. package/skills/pomwright-v1-5-bridge-migration/references/call-site-migration.md +0 -178
  99. package/skills/pomwright-v1-5-bridge-migration/references/schema-translation.md +0 -183
  100. package/skills/pomwright-v2-migration/SKILL.md +0 -63
  101. package/skills/pomwright-v2-migration/references/call-site-migration.md +0 -265
  102. package/skills/pomwright-v2-migration/references/class-migration.md +0 -266
  103. package/skills/pomwright-v2-migration/references/fixture-and-helpers.md +0 -423
  104. package/skills/pomwright-v2-migration/references/locator-registration.md +0 -344
  105. package/srcV2/fixture/base.fixtures.ts +0 -23
  106. package/srcV2/helpers/navigation.ts +0 -153
  107. package/srcV2/helpers/playwrightReportLogger.ts +0 -196
  108. package/srcV2/helpers/sessionStorage.ts +0 -251
  109. package/srcV2/helpers/stepDecorator.ts +0 -106
  110. package/srcV2/locators/index.ts +0 -15
  111. package/srcV2/locators/locatorQueryBuilder.ts +0 -427
  112. package/srcV2/locators/locatorRegistrationBuilder.ts +0 -558
  113. package/srcV2/locators/locatorRegistry.ts +0 -583
  114. package/srcV2/locators/locatorUpdateBuilder.ts +0 -602
  115. package/srcV2/locators/reusableLocatorBuilder.ts +0 -200
  116. package/srcV2/locators/types.ts +0 -256
  117. package/srcV2/locators/utils.ts +0 -309
  118. package/srcV2/locators/v1SchemaTranslator.ts +0 -178
  119. package/srcV2/pageObject.ts +0 -105
@@ -1,695 +0,0 @@
1
- # Locator Registry (v2)
2
-
3
- ## Table of contents
4
-
5
- 1. [Locator Registry overview](#locator-registry-overview)
6
- 2. [Mental model](#mental-model)
7
- 3. [Path types and validation](#path-types-and-validation)
8
- 1. [Compile-time path constraints](#compile-time-path-constraints)
9
- 2. [Runtime validation rules](#runtime-validation-rules)
10
- 3. [Common path mistakes and errors](#common-path-mistakes-and-errors)
11
- 4. [Factory and accessors](#factory-and-accessors)
12
- 1. [`createRegistryWithAccessors`](#createregistrywithaccessors)
13
- 2. [Accessor types](#accessor-types)
14
- 3. [Public `LocatorRegistry` vs internal registry](#public-locatorregistry-vs-internal-registry)
15
- 5. [Registering locators with `add`](#registering-locators-with-add)
16
- 1. [All strategy methods](#all-strategy-methods)
17
- 2. [Post-definition chain methods](#post-definition-chain-methods)
18
- 3. [One-strategy-per-registration rule](#one-strategy-per-registration-rule)
19
- 4. [Duplicate path behavior](#duplicate-path-behavior)
20
- 6. [`getById` deep dive](#getbyid-deep-dive)
21
- 1. [String normalization](#string-normalization)
22
- 2. [RegExp behavior and escaping](#regexp-behavior-and-escaping)
23
- 3. [Examples](#examples)
24
- 7. [`filter` deep dive](#filter-deep-dive)
25
- 1. [Accepted `has`/`hasNot` reference forms](#accepted-hashasnot-reference-forms)
26
- 2. [Path examples](#path-examples)
27
- 3. [Frame-locator restrictions in filters](#frame-locator-restrictions-in-filters)
28
- 8. [Resolution APIs](#resolution-apis)
29
- 1. [`getLocator(path)`](#getlocatorpath)
30
- 2. [`getNestedLocator(path)`](#getnestedlocatorpath)
31
- 3. [Frame behavior: terminal vs non-terminal](#frame-behavior-terminal-vs-non-terminal)
32
- 4. [Sparse chain behavior](#sparse-chain-behavior)
33
- 9. [Query builder: `getLocatorSchema(path)`](#query-builder-getlocatorschemapath)
34
- 1. [Clone semantics (registry is not mutated)](#clone-semantics-registry-is-not-mutated)
35
- 2. [Sub-path scope rules](#sub-path-scope-rules)
36
- 3. [Method-by-method reference](#method-by-method-reference)
37
- 4. [Default sub-path behavior table](#default-sub-path-behavior-table)
38
- 10. [`update` and `replace` semantics](#update-and-replace-semantics)
39
- 1. [`update`: PATCH-style merge](#update-patch-style-merge)
40
- 2. [`replace`: POST-style overwrite](#replace-post-style-overwrite)
41
- 3. [Required fields and type switching](#required-fields-and-type-switching)
42
- 11. [`remove` and tombstones](#remove-and-tombstones)
43
- 1. [Non-terminal removal](#non-terminal-removal)
44
- 2. [Terminal removal behavior](#terminal-removal-behavior)
45
- 3. [Rehydrating removed segments](#rehydrating-removed-segments)
46
- 12. [Reusable locators: `createReusable`](#reusable-locators-createreusable)
47
- 1. [Seed creation](#seed-creation)
48
- 2. [Reuse by object seed](#reuse-by-object-seed)
49
- 3. [Reuse by existing path string](#reuse-by-existing-path-string)
50
- 4. [Reuse patch rules and limitations](#reuse-patch-rules-and-limitations)
51
- 5. [Seed immutability](#seed-immutability)
52
- 13. [`describe` semantics](#describe-semantics)
53
- 1. [Registry-level descriptions](#registry-level-descriptions)
54
- 2. [Query-level overrides](#query-level-overrides)
55
- 14. [Error reference and troubleshooting](#error-reference-and-troubleshooting)
56
- 15. [Tested behavioral guarantees](#tested-behavioral-guarantees)
57
- 16. [Migration notes from v1](#migration-notes-from-v1)
58
- 17. [Best practices](#best-practices)
59
-
60
- ---
61
-
62
- ## Locator Registry overview
63
-
64
- The v2 Locator Registry is a typed, fluent DSL for defining and resolving Playwright locators.
65
-
66
- At a high level it provides:
67
-
68
- - **Typed locator paths** (`"root.section.button"`) validated both at compile time and runtime.
69
- - **Fluent registration** (`add(path).getByRole(...).filter(...).nth(...)`).
70
- - **Two resolution modes**:
71
- - `getLocator(path)` resolves only the terminal segment.
72
- - `getNestedLocator(path)` resolves the full chain.
73
- - **Clone-based query mutation** (`getLocatorSchema(path)`) for temporary overrides (`update`, `replace`, `remove`, `filter`, `nth`, `clearSteps`, `describe`) without mutating the registry.
74
-
75
- You can use it directly via `createRegistryWithAccessors(page)` or through `PageObject` (`this.add`, `this.getLocator`, `this.getNestedLocator`, `this.getLocatorSchema`).
76
-
77
- ---
78
-
79
- ## Mental model
80
-
81
- Think of the registry as:
82
-
83
- 1. A map of **path -> locator definition** (`getByRole`, `locator`, `frameLocator`, etc.).
84
- 2. A map of **path -> ordered steps** (`filter` / `nth`).
85
- 3. An optional **path description** (`describe`).
86
-
87
- When resolving:
88
-
89
- - `getLocator(path)` applies only the terminal definition + terminal steps.
90
- - `getNestedLocator(path)` traverses the chain (`a`, `a.b`, `a.b.c`) and applies each registered segment in order.
91
-
92
- When querying with `getLocatorSchema(path)`:
93
-
94
- - A mutable clone is created for the chain.
95
- - All updates are local to the clone.
96
- - The underlying registry remains unchanged.
97
-
98
- ---
99
-
100
- ## Path types and validation
101
-
102
- ### Compile-time path constraints
103
-
104
- Paths are generic string literal unions that are checked using v2 type utilities.
105
-
106
- Rules:
107
-
108
- - Path cannot be empty.
109
- - Path cannot start or end with `.`.
110
- - Path cannot contain consecutive dots (`..`).
111
- - Path cannot contain Unicode whitespace characters.
112
-
113
- ```ts
114
- type Paths =
115
- | "main"
116
- | "main.form@login"
117
- | "main.form@login.input@username"
118
- | "main.form@login.input@password"
119
- | "main.button@login";
120
- ```
121
-
122
- If invalid literals are included in the union, TypeScript blocks those values at locator accessor argument usage
123
- (`add`, `getLocator`, `getNestedLocator`, `getLocatorSchema`) with path-format diagnostics while preserving
124
- valid path autocomplete suggestions. Runtime validation still applies for non-literal strings.
125
-
126
- ### Runtime validation rules
127
-
128
- Runtime validation applies the same structure checks:
129
-
130
- - empty string
131
- - leading dot
132
- - trailing dot
133
- - consecutive dots
134
- - whitespace characters
135
-
136
- This means even if a path reaches runtime as a plain string, it is still validated.
137
-
138
- ### Common path mistakes and errors
139
-
140
- Typical runtime errors:
141
-
142
- - `LocatorSchemaPath string cannot be empty`
143
- - `LocatorSchemaPath string cannot start with a dot: .foo`
144
- - `LocatorSchemaPath string cannot end with a dot: foo.`
145
- - `LocatorSchemaPath string cannot contain consecutive dots: foo..bar`
146
- - `LocatorSchemaPath string cannot contain whitespace chars: ...`
147
-
148
- ---
149
-
150
- ## Factory and accessors
151
-
152
- ### `createRegistryWithAccessors`
153
-
154
- `createRegistryWithAccessors(page)` creates a registry instance plus bound helpers.
155
-
156
- ```ts
157
- import { createRegistryWithAccessors } from "pomwright";
158
-
159
- type Paths = "main" | "main.button";
160
-
161
- const { registry, add, getLocator, getNestedLocator, getLocatorSchema } =
162
- createRegistryWithAccessors<Paths>(page);
163
-
164
- add("main").locator("main");
165
- add("main.button").getByRole("button", { name: "Save" });
166
-
167
- await getNestedLocator("main.button").click();
168
- ```
169
-
170
- ### Accessor types
171
-
172
- The package exports accessor types so you can inject/wire them in custom classes or fixtures:
173
-
174
- - `AddAccessor<Paths>`
175
- - `GetLocatorAccessor<Paths>`
176
- - `GetNestedLocatorAccessor<Paths>`
177
- - `GetLocatorSchemaAccessor<Paths>`
178
-
179
- ```ts
180
- import type {
181
- AddAccessor,
182
- GetLocatorAccessor,
183
- GetNestedLocatorAccessor,
184
- GetLocatorSchemaAccessor,
185
- LocatorRegistry,
186
- } from "pomwright";
187
-
188
- class MyPage<Paths extends string> {
189
- constructor(
190
- public readonly registry: LocatorRegistry<Paths>,
191
- public readonly add: AddAccessor<Paths>,
192
- public readonly getLocator: GetLocatorAccessor<Paths>,
193
- public readonly getNestedLocator: GetNestedLocatorAccessor<Paths>,
194
- public readonly getLocatorSchema: GetLocatorSchemaAccessor<Paths>,
195
- ) {}
196
- }
197
- ```
198
-
199
- ### Public `LocatorRegistry` vs internal registry
200
-
201
- The **public** `LocatorRegistry` intentionally exposes only:
202
-
203
- - `add`
204
- - `createReusable`
205
- - `getLocator`
206
- - `getNestedLocator`
207
- - `getLocatorSchema`
208
-
209
- Internal lifecycle methods (`register`, `replace`, `get`, `unregister`) exist on the internal implementation and are intentionally not part of the public API.
210
-
211
- ---
212
-
213
- ## Registering locators with `add`
214
-
215
- `add(path)` begins a registration chain.
216
-
217
- ```ts
218
- registry.add("main").locator("main");
219
- registry.add("main.button@login").getByRole("button", { name: "Login" });
220
- registry.add("main.form@login").getByRole("form", { name: "Login" });
221
- registry.add("main.form@login.input@username").getByLabel("Username");
222
- registry.add("main.form@login.input@password").getByLabel("Password");
223
- ```
224
-
225
- ### All strategy methods
226
-
227
- Each registration can choose exactly one strategy method:
228
-
229
- - `getByRole(role, options?)`
230
- - `getByText(text, options?)`
231
- - `getByLabel(text, options?)`
232
- - `getByPlaceholder(text, options?)`
233
- - `getByAltText(text, options?)`
234
- - `getByTitle(text, options?)`
235
- - `locator(selector, options?)`
236
- - `frameLocator(selector)`
237
- - `getByTestId(testId)`
238
- - `getById(id)`
239
-
240
- ### Post-definition chain methods
241
-
242
- After a strategy is set, you can chain:
243
-
244
- - `filter(filterDefinition)`
245
- - `nth(index)` where index is `number | "first" | "last"`
246
- - `describe(description)`
247
-
248
- ```ts
249
- registry
250
- .add("main.list.item")
251
- .getByRole("listitem", { name: /Row/ })
252
- .filter({ hasText: "Row" })
253
- .nth("last")
254
- .describe("Last matching row");
255
- ```
256
-
257
- ### One-strategy-per-registration rule
258
-
259
- Without reuse, calling a second strategy in the same registration throws.
260
-
261
- ```ts
262
- registry.add("main.button").getByRole("button");
263
- // ❌ Later trying to set .locator(...) for same add-chain is invalid.
264
- ```
265
-
266
- ### Duplicate path behavior
267
-
268
- A path can only be registered once. Attempting to register the same path again throws with details about existing vs attempted schema.
269
-
270
- ---
271
-
272
- ## `getById` deep dive
273
-
274
- ### String normalization
275
-
276
- For string IDs, v2 normalizes:
277
-
278
- - `"#login"` -> `"login"`
279
- - `"id=login"` -> `"login"`
280
-
281
- Then resolves as `locator('#login')` with CSS escaping.
282
-
283
- ### RegExp behavior and escaping
284
-
285
- For RegExp IDs, v2 uses the regex source and resolves as a substring selector:
286
-
287
- - `getById(/panel-/)` -> `locator('[id*="panel-"]')` (escaped)
288
-
289
- This is **substring** matching of the regex source string in the `id` attribute, not runtime regex evaluation in the browser selector engine.
290
-
291
- ### Examples
292
-
293
- ```ts
294
- registry.add("modal.close").getById("close-modal");
295
- registry.add("modal.close2").getById("#close-modal");
296
- registry.add("modal.dynamic").getById(/modal-/);
297
- ```
298
-
299
- ---
300
-
301
- ## `filter` deep dive
302
-
303
- ### Accepted `has`/`hasNot` reference forms
304
-
305
- For `filter({ has })` and `filter({ hasNot })`, v2 accepts:
306
-
307
- 1. A Playwright `Locator`
308
- 2. A registry path string (e.g. `"main.section.heading"`)
309
-
310
- Also supports standard Playwright `hasText` / `hasNotText` options.
311
-
312
- > **Tip:** Because `filter` accepts Playwright `Locator` instances, you can also pass locators returned from
313
- > `registry.getLocator(path)`, `registry.getNestedLocator(path)`, or
314
- > `registry.getLocatorSchema(path)...getLocator()` / `getNestedLocator()` in addition to `page.locator(...)`
315
- > and `page.getBy...(...)` calls.
316
-
317
- When you pass a **path string**, the registry resolves that path directly by building a locator for the terminal
318
- definition and its own recorded steps (`filter(...)`, `nth(...)`) only. Ancestor segments are **not** chained. If you
319
- want ancestor chaining, pass a Playwright `Locator` built from `registry.getNestedLocator(path)` or from a query
320
- builder `registry.getNestedLocator(path)...getNestedLocator()` which also resolves the chain.
321
-
322
- Be careful to avoid **cyclic filter references** (for example, when `pathA` uses `filter({ has: "pathB" })` and `pathB`
323
- eventually filters back to `pathA`). Cycles are detected and throw with:
324
- `Detected cyclic filter reference while resolving "${context.rootPath}": "${path}".`
325
-
326
- ### Path examples
327
-
328
- ```ts
329
- registry.add("main").locator("main");
330
- registry.add("main.section").locator("section");
331
- registry.add("main.section.heading").getByRole("heading", { level: 2 });
332
-
333
- registry
334
- .add("main.section.warning")
335
- .locator(".warning")
336
- .filter({ has: "main.section.heading" })
337
- .filter({ hasNot: "main.section" })
338
- .filter({ hasText: /Warning/i });
339
- ```
340
-
341
- ### Frame-locator restrictions in filters
342
-
343
- Inline frame locator definitions are not supported as filter references. If you need to target a frame, resolve a
344
- registry path so it yields the frame **owner locator** (the iframe element) or pass a Playwright
345
- `page.frameLocator(...).owner()` locator to `has`/`hasNot`.
346
-
347
- ---
348
-
349
- ## Resolution APIs
350
-
351
- ### `getLocator(path)`
352
-
353
- Resolves terminal-only:
354
-
355
- ```ts
356
- registry.add("main.form.username").getByLabel("Username");
357
- const terminal = registry.getLocator("main.form.username");
358
- // Equivalent to direct getByLabel("Username") from page context.
359
- ```
360
-
361
- ### `getNestedLocator(path)`
362
-
363
- Resolves the full registered chain:
364
-
365
- ```ts
366
- registry.add("main").locator("main");
367
- registry.add("main.form").getByRole("form", { name: "Login" });
368
- registry.add("main.form.username").getByLabel("Username");
369
-
370
- const nested = registry.getNestedLocator("main.form.username");
371
- // locator("main").getByRole("form", { name: "Login" }).getByLabel("Username")
372
- ```
373
-
374
- ### Frame behavior: terminal vs non-terminal
375
-
376
- If a segment is `frameLocator`:
377
-
378
- - **Non-terminal frame segment**: resolution context enters frame for descendants.
379
- - **Terminal frame segment**: resolves to the iframe **owner locator**, not the frame target.
380
-
381
- ```ts
382
- registry.add("shell.frame@login").frameLocator("iframe#login");
383
- registry.add("shell.frame@login.username").getByLabel("Username");
384
-
385
- const frameOwner = registry.getNestedLocator("shell.frame@login");
386
- const insideFrame = registry.getNestedLocator("shell.frame@login.username");
387
- ```
388
-
389
- ### Sparse chain behavior
390
-
391
- During nested resolution, chain parts/sub-paths that are not registered are skipped, except for the terminal chain/path which throws if no locator definition is registered.
392
-
393
- This enables partial/sparse registration patterns, but for readability and maintainability you should generally prefer explicit ancestor registration.
394
-
395
- ---
396
-
397
- ## Query builder: `getLocatorSchema(path)`
398
-
399
- ### Clone semantics (registry is not mutated)
400
-
401
- `getLocatorSchema(path)` creates a mutable clone of the selected chain.
402
-
403
- Any `filter`, `nth`, `clearSteps`, `update`, `replace`, `remove`, or `describe` call affects only the builder instance.
404
-
405
- ```ts
406
- const builder = registry.getLocatorSchema("main.form.username");
407
- const patched = builder.update().getByLabel("Username", { exact: true }).getNestedLocator();
408
-
409
- // Registry remains unchanged.
410
- ```
411
-
412
- ### Sub-path scope rules
413
-
414
- For a builder rooted at `"a.b.c"`, valid sub-paths are chain segments within that root (`"a"`, `"a.b"`, `"a.b.c"`) **if they exist in the builder clone**.
415
-
416
- If a sub-path is not valid in that context, methods throw:
417
-
418
- - `"<subPath>" is not a valid sub-path of "<rootPath>".`
419
-
420
- ### Method-by-method reference
421
-
422
- On query builder:
423
-
424
- - `filter(subPath?, filter)`
425
- - `nth(subPath?, index)`
426
- - `clearSteps(subPath?)`
427
- - `describe(description)` (terminal path only)
428
- - `update(subPath?)` -> returns update builder (PATCH semantics)
429
- - `replace(subPath?)` -> returns update builder in replace mode (POST semantics)
430
- - `remove(subPath?)`
431
- - `getLocator()`
432
- - `getNestedLocator()`
433
-
434
- Examples:
435
-
436
- ```ts
437
- const locator = registry
438
- .getLocatorSchema("main.form.button")
439
- .filter("main.form", { hasText: "Login" })
440
- .nth("main.form.button", "first")
441
- .getNestedLocator();
442
-
443
- const noSteps = registry
444
- .getLocatorSchema("main.form.button")
445
- .clearSteps("main.form.button")
446
- .getNestedLocator();
447
- ```
448
-
449
- ### Default sub-path behavior table
450
-
451
- If `subPath` is omitted, the terminal path passed to `getLocatorSchema(path)` is used.
452
-
453
- | Method | Optional subPath? | Omitted behavior |
454
- | --- | --- | --- |
455
- | `filter(subPath?, filter)` | Yes | Uses terminal path |
456
- | `nth(subPath?, index)` | Yes | Uses terminal path |
457
- | `clearSteps(subPath?)` | Yes | Uses terminal path |
458
- | `update(subPath?)` | Yes | Uses terminal path |
459
- | `replace(subPath?)` | Yes | Uses terminal path |
460
- | `remove(subPath?)` | Yes | Uses terminal path |
461
- | `describe(description)` | N/A | Always terminal path |
462
-
463
- ---
464
-
465
- ## `update` and `replace` semantics
466
-
467
- ### `update`: PATCH-style merge
468
-
469
- `update(subPath?)` merges fields into the existing definition for that sub-path.
470
-
471
- - Omitted required fields may be inherited from current/baseline definitions.
472
- - Options are merged where applicable.
473
-
474
- ```ts
475
- const updated = registry
476
- .getLocatorSchema("main.button")
477
- .update()
478
- .getByRole({ name: "Sign in" })
479
- .getNestedLocator();
480
- ```
481
-
482
- ### `replace`: POST-style overwrite
483
-
484
- `replace(subPath?)` requires enough data to build a complete definition for the chosen strategy.
485
-
486
- ```ts
487
- const replaced = registry
488
- .getLocatorSchema("main.button")
489
- .replace()
490
- .locator("button.primary", { hasText: "Sign in" })
491
- .getNestedLocator();
492
- ```
493
-
494
- ### Required fields and type switching
495
-
496
- Important behavior:
497
-
498
- - `update` can switch strategy type, but the target strategy must have required fields resolved via provided values or available baselines.
499
- - `replace` requires the required field for the target strategy in the replacement call chain.
500
- - `update` / `replace` only change definitions, not steps. Use `filter` / `nth` / `clearSteps` for steps.
501
-
502
- ---
503
-
504
- ## `remove` and tombstones
505
-
506
- ### Non-terminal removal
507
-
508
- Removing an ancestor sub-path soft-deletes that segment in the builder clone. During nested resolution, removed non-terminal segments are skipped.
509
-
510
- ### Terminal removal behavior
511
-
512
- If terminal segment is removed and not restored, resolving throws (no schema for terminal path).
513
-
514
- ```ts
515
- const builder = registry.getLocatorSchema("main.form.username").remove();
516
- // builder.getNestedLocator() => throws
517
- ```
518
-
519
- ### Rehydrating removed segments
520
-
521
- A removed segment can be repopulated within the same builder chain via `update` or `replace`, then resolved successfully.
522
-
523
- ```ts
524
- const locator = registry
525
- .getLocatorSchema("main.form.username")
526
- .remove()
527
- .replace()
528
- .getByLabel("Email")
529
- .getNestedLocator();
530
- ```
531
-
532
- ---
533
-
534
- ## Reusable locators: `createReusable`
535
-
536
- ### Seed creation
537
-
538
- `createReusable` provides the same strategy entry methods as `add`.
539
-
540
- ```ts
541
- const h2 = registry.createReusable.getByRole("heading", { level: 2 }).filter({ hasText: /Summary/ });
542
- const firstRow = registry.createReusable.locator("tr").nth(0).describe("First row");
543
- ```
544
-
545
- ### Reuse by object seed
546
-
547
- Pass a reusable seed to `add` via `{ reuse: seed }`.
548
-
549
- ```ts
550
- registry.add("card.title", { reuse: h2 });
551
- registry.add("card.title@first", { reuse: h2 }).nth(0);
552
- ```
553
-
554
- ### Reuse by existing path string
555
-
556
- Pass a previously registered path to clone that path definition/steps/description as-is.
557
-
558
- ```ts
559
- registry.add("errors.invalidPassword").getByText("Invalid password");
560
- registry.add("main.form.error@invalidPassword", { reuse: "errors.invalidPassword" });
561
- ```
562
-
563
- Note: path-based reuse registers immediately and does not return a chainable builder.
564
-
565
- ### Reuse patch rules and limitations
566
-
567
- With object-seed reuse:
568
-
569
- - Seeded definition is persisted first.
570
- - You may provide **one matching-strategy override** (same discriminant/type).
571
- - Mismatched strategy overrides throw.
572
- - More than one strategy override in that chain throws.
573
- - `filter` / `nth` / `describe` may still be chained.
574
-
575
- ```ts
576
- const seed = registry.createReusable.getByRole("heading", { level: 2 });
577
-
578
- registry.add("heading.summary", { reuse: seed }).getByRole({ name: "Summary" }); // ✅
579
-
580
- // ❌ mismatched strategy, throws at runtime and compile-time should also guide against this
581
- // registry.add("heading.bad", { reuse: seed }).locator("h2");
582
- ```
583
-
584
- ### Seed immutability
585
-
586
- Using a seed in multiple `add(..., { reuse: seed })` chains does not mutate the seed object itself.
587
-
588
- ---
589
-
590
- ## `describe` semantics
591
-
592
- ### Registry-level descriptions
593
-
594
- Calling `.describe(...)` on `add` stores description with the path definition and applies it to resolved terminal locator.
595
-
596
- ```ts
597
- registry
598
- .add("main.submit")
599
- .getByRole("button", { name: "Submit" })
600
- .describe("Primary submit button");
601
- ```
602
-
603
- ### Query-level overrides
604
-
605
- Calling `.describe(...)` on query builder overrides description only for that builder resolution and does not mutate the stored registry description.
606
-
607
- ```ts
608
- const override = registry
609
- .getLocatorSchema("main.submit")
610
- .describe("Temporary override")
611
- .getLocator();
612
- ```
613
-
614
- ---
615
-
616
- ## Error reference and troubleshooting
617
-
618
- Common errors and what they usually mean:
619
-
620
- - `No locator schema registered for path "...".`
621
- - Path was never registered, was removed on builder, or is terminal tombstone.
622
- - `"..." is not a valid sub-path of "...".`
623
- - Query method targeted a path outside the builder’s chain context.
624
- - `A locator schema with the path "..." already exists.`
625
- - Duplicate registration for same path.
626
- - `A locator definition must be provided before applying filters or indices for "...".`
627
- - `filter`/`nth` called before strategy during registration.
628
- - `A locator definition for "..." has already been provided; only one locator type can be set for a registration.`
629
- - Multiple strategy calls on a non-reuse registration.
630
- - `The locator definition for "..." must use the "..." strategy when reusing a locator.`
631
- - Mismatched strategy override while using `{ reuse: seed }`.
632
- - `A locator definition for "..." was already provided from reuse; only one matching override is allowed.`
633
- - More than one seeded strategy override attempted.
634
- - `Frame locators cannot be used as filter locators.`
635
- - Inline filter `has`/`hasNot` passed a frame locator definition.
636
- - `Detected cyclic filter reference while resolving "...": "...".`
637
- - A `has`/`hasNot` path reference re-entered an active filter resolution; break/fix the loop or pass a resolved locator.
638
-
639
- Troubleshooting checklist:
640
-
641
- 1. Confirm path literal is valid and registered.
642
- 2. Confirm query sub-path is in the same chain root.
643
- 3. Confirm reuse override strategy matches seed type.
644
- 4. Confirm terminal path still exists if `remove()` was called.
645
-
646
- ---
647
-
648
- ## Tested behavioral guarantees
649
-
650
- The v2 integration suite (`intTestV2`) verifies Locator Registry behavior in depth, including:
651
-
652
- - path validation (compile-time + runtime)
653
- - sub-path validation
654
- - all registration strategies
655
- - `filter` behavior, including `has`/`hasNot` variants
656
- - `nth` chaining and ordering
657
- - `describe` behavior
658
- - `update` / `replace` / `remove`
659
- - `clearSteps`
660
- - reusable locators and reuse constraints
661
- - frame locator behavior
662
-
663
- For implementation-level examples, see the Locator Registry tests under `intTestV2/tests/locatorRegistry`.
664
-
665
- ---
666
-
667
- ## Migration notes from v1
668
-
669
- Key v1 -> v2 Locator Registry differences:
670
-
671
- - v2 uses fluent registration (`add(...).getBy...`) instead of v1 object schemas.
672
- - `getLocator` / `getNestedLocator` are synchronous in v2.
673
- - v1 index maps for nested lookup are replaced by query-builder `.nth(...)`.
674
- - v1 `addFilter` style maps to v2 `.filter(...)`.
675
- - v2 query builder adds `replace`, `remove`, `clearSteps`, and `describe`.
676
- - frame terminal behavior is explicit (terminal frame resolves to owner locator).
677
-
678
- See migration docs in `docs/v1-to-v2-migration` for side-by-side mappings and staged migration guidance.
679
-
680
- ---
681
-
682
- ## Best practices
683
-
684
- 1. **Prefer explicit ancestor registration**
685
- - Sparse chains are supported, but explicit ancestors improve readability.
686
- 2. **Use descriptive path names**
687
- - Favor semantic segments (`form@login.input@username`) over anonymous names.
688
- 3. **Use `getLocatorSchema` for temporary overrides**
689
- - Keep registry definitions stable; mutate clones for scenario-specific behavior.
690
- 4. **Use reusable seeds for repeated patterns**
691
- - Centralize repeated strategy + steps, then reuse with a single matching override.
692
- 5. **Keep filters intentional**
693
- - Be explicit with `has`/`hasNot` references; prefer path-based references for readability.
694
- 6. **Document intentional strategy switching in query updates**
695
- - Type switches are powerful; use them deliberately and clearly in test code.