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