pomwright 1.5.0 → 1.5.1
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 +14 -0
- package/dist/index.d.mts +19 -22
- package/dist/index.d.ts +19 -22
- package/dist/index.js +35 -8
- package/dist/index.mjs +35 -8
- package/docs/v2/locator-registry.md +3 -1
- package/intTestV2/package.json +1 -1
- package/intTestV2/tests/locatorRegistry/add/add.get.clone.spec.ts +76 -0
- package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +41 -0
- package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.typecheck.ts +86 -0
- package/package.json +1 -1
- package/srcV2/locators/locatorRegistry.ts +104 -62
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# pomwright
|
|
2
2
|
|
|
3
|
+
## 1.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#40](https://github.com/DyHex/POMWright/pull/40) [`c8232d2`](https://github.com/DyHex/POMWright/commit/c8232d21e2d7f0149042f4844037c67289d26068) Thanks [@DyHex](https://github.com/DyHex)! - # Summary
|
|
8
|
+
|
|
9
|
+
This PR focuses on preventative quality improvements to v2 locator-registry typing and internal safety.
|
|
10
|
+
|
|
11
|
+
## What changed
|
|
12
|
+
|
|
13
|
+
- Refined compile-time diagnostics for invalid locator schema paths so errors are clearer and more actionable at the literal argument level.
|
|
14
|
+
- Hardened internal registry retrieval behavior (`get` / `getIfExists`) for safer, more predictable handling of missing paths and record cloning semantics.
|
|
15
|
+
- Added both compile-time and runtime guards to reject path-based reuse when the source path equals the target registration path `registry.add(path, { reuse: path })`.
|
|
16
|
+
|
|
3
17
|
## 1.5.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -1232,6 +1232,11 @@ declare class ReusableLocatorFactory<LocatorSchemaPathType extends string> {
|
|
|
1232
1232
|
private create;
|
|
1233
1233
|
}
|
|
1234
1234
|
|
|
1235
|
+
type PathArgument<Paths extends string, Path extends Paths> = LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>;
|
|
1236
|
+
type InvalidReusePath<Path extends string> = [
|
|
1237
|
+
`Invalid reuse path, reuse path cannot be the same as registration path: ${Path}`
|
|
1238
|
+
];
|
|
1239
|
+
type ReusePathArgument<Paths extends string, Path extends Paths, ReusePath extends Paths> = Exclude<ReusePath, Path> extends never ? InvalidReusePath<Path> : PathArgument<Paths, ReusePath>;
|
|
1235
1240
|
declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
1236
1241
|
private readonly page;
|
|
1237
1242
|
private readonly schemas;
|
|
@@ -1267,13 +1272,11 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1267
1272
|
* .nth("last");
|
|
1268
1273
|
* ```
|
|
1269
1274
|
*/
|
|
1270
|
-
add
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
reuse: Reuse;
|
|
1276
|
-
}): LocatorRegistrationSeededBuilderForType<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, Reuse["type"]>;
|
|
1275
|
+
add<Path extends LocatorSchemaPathType, Reuse extends LocatorSchemaPathType | ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]> | undefined = undefined>(path: PathArgument<LocatorSchemaPathType, Path>, ...args: Reuse extends undefined ? [] : [
|
|
1276
|
+
options: {
|
|
1277
|
+
reuse: Reuse extends LocatorSchemaPathType ? ReusePathArgument<LocatorSchemaPathType, Path, Reuse> : Extract<Reuse, ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]>>;
|
|
1278
|
+
}
|
|
1279
|
+
]): Reuse extends undefined ? LocatorRegistrationPreDefinitionBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false> : Reuse extends LocatorSchemaPathType ? void : LocatorRegistrationSeededBuilderForType<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, Extract<Reuse, ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]>>["type"]>;
|
|
1277
1280
|
register(path: RegistryPath<LocatorSchemaPathType>, record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>): void;
|
|
1278
1281
|
replace(path: RegistryPath<LocatorSchemaPathType>, record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>): void;
|
|
1279
1282
|
get(path: RegistryPath<LocatorSchemaPathType>): LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
|
|
@@ -1297,7 +1300,7 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1297
1300
|
* const locator = builder.getNestedLocator();
|
|
1298
1301
|
* ```
|
|
1299
1302
|
*/
|
|
1300
|
-
getLocatorSchema<Path extends
|
|
1303
|
+
getLocatorSchema<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): LocatorQueryBuilderPublic<LocatorSchemaPathType, Extract<Path, RegistryPath<LocatorSchemaPathType>>>;
|
|
1301
1304
|
/**
|
|
1302
1305
|
* Resolves the Playwright {@link Locator} for the provided path, applying only the terminal
|
|
1303
1306
|
* definition and its recorded steps (no ancestor chaining). Throws if the path is not registered.
|
|
@@ -1308,7 +1311,7 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1308
1311
|
* await button.click();
|
|
1309
1312
|
* ```
|
|
1310
1313
|
*/
|
|
1311
|
-
getLocator<Path extends
|
|
1314
|
+
getLocator<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): Locator;
|
|
1312
1315
|
/**
|
|
1313
1316
|
* Resolves a chained Playwright {@link Locator} for a nested path, traversing each registered
|
|
1314
1317
|
* segment and applying their steps in order. Throws if any segment in the chain is missing.
|
|
@@ -1319,7 +1322,7 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1319
1322
|
* await expect(nested).toBeVisible();
|
|
1320
1323
|
* ```
|
|
1321
1324
|
*/
|
|
1322
|
-
getNestedLocator<Path extends
|
|
1325
|
+
getNestedLocator<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): Locator;
|
|
1323
1326
|
buildLocatorChain(path: RegistryPath<LocatorSchemaPathType>, definitions: Map<string, LocatorStrategyDefinition>, steps: Map<string, LocatorStep<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>[]>, tombstones?: Set<string>, terminalDescription?: LocatorDescription, context?: {
|
|
1324
1327
|
rootPath: RegistryPath<LocatorSchemaPathType>;
|
|
1325
1328
|
resolvingPaths: Set<string>;
|
|
@@ -1334,10 +1337,10 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1334
1337
|
}[];
|
|
1335
1338
|
};
|
|
1336
1339
|
}
|
|
1337
|
-
type GetLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends
|
|
1340
|
+
type GetLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>) => Locator;
|
|
1338
1341
|
type AddAccessor<LocatorSchemaPathType extends string> = LocatorRegistryInternal<LocatorSchemaPathType>["add"];
|
|
1339
|
-
type GetLocatorSchemaAccessor<LocatorSchemaPathType extends string> = <Path extends
|
|
1340
|
-
type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends
|
|
1342
|
+
type GetLocatorSchemaAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>) => LocatorQueryBuilderPublic<LocatorSchemaPathType, Extract<Path, RegistryPath<LocatorSchemaPathType>>>;
|
|
1343
|
+
type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>) => Locator;
|
|
1341
1344
|
type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<LocatorRegistryInternal<LocatorSchemaPathType>, "add" | "createReusable" | "getLocator" | "getLocatorSchema" | "getNestedLocator">;
|
|
1342
1345
|
/**
|
|
1343
1346
|
* Creates a v2 locator registry bound to a Playwright {@link Page} and returns the registry plus
|
|
@@ -1359,15 +1362,9 @@ type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<LocatorRegistr
|
|
|
1359
1362
|
*/
|
|
1360
1363
|
declare const createRegistryWithAccessors: <Paths extends string>(page: Page) => {
|
|
1361
1364
|
readonly registry: LocatorRegistry<Paths>;
|
|
1362
|
-
readonly add: {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
reuse: RegistryPath<Paths>;
|
|
1366
|
-
}): void;
|
|
1367
|
-
<Reuse extends ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id">>(path: RegistryPath<Paths>, options: {
|
|
1368
|
-
reuse: Reuse;
|
|
1369
|
-
}): LocatorRegistrationSeededBuilderForType<Paths, RegistryPath<Paths>, Reuse["type"]>;
|
|
1370
|
-
};
|
|
1365
|
+
readonly add: <Path extends Paths, Reuse extends Paths | ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id"> | undefined = undefined>(path: PathArgument<Paths, Path>, ...args: Reuse extends undefined ? [] : [options: {
|
|
1366
|
+
reuse: Reuse extends Paths ? ReusePathArgument<Paths, Path, Reuse> : Extract<Reuse, ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id">>;
|
|
1367
|
+
}]) => Reuse extends undefined ? LocatorRegistrationPreDefinitionBuilder<Paths, RegistryPath<Paths>, false> : Reuse extends Paths ? void : LocatorRegistrationSeededBuilderForType<Paths, RegistryPath<Paths>, Extract<Reuse, ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id">>["type"]>;
|
|
1371
1368
|
readonly getLocator: GetLocatorAccessor<Paths>;
|
|
1372
1369
|
readonly getNestedLocator: GetNestedLocatorAccessor<Paths>;
|
|
1373
1370
|
readonly getLocatorSchema: GetLocatorSchemaAccessor<Paths>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1232,6 +1232,11 @@ declare class ReusableLocatorFactory<LocatorSchemaPathType extends string> {
|
|
|
1232
1232
|
private create;
|
|
1233
1233
|
}
|
|
1234
1234
|
|
|
1235
|
+
type PathArgument<Paths extends string, Path extends Paths> = LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>;
|
|
1236
|
+
type InvalidReusePath<Path extends string> = [
|
|
1237
|
+
`Invalid reuse path, reuse path cannot be the same as registration path: ${Path}`
|
|
1238
|
+
];
|
|
1239
|
+
type ReusePathArgument<Paths extends string, Path extends Paths, ReusePath extends Paths> = Exclude<ReusePath, Path> extends never ? InvalidReusePath<Path> : PathArgument<Paths, ReusePath>;
|
|
1235
1240
|
declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
1236
1241
|
private readonly page;
|
|
1237
1242
|
private readonly schemas;
|
|
@@ -1267,13 +1272,11 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1267
1272
|
* .nth("last");
|
|
1268
1273
|
* ```
|
|
1269
1274
|
*/
|
|
1270
|
-
add
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
reuse: Reuse;
|
|
1276
|
-
}): LocatorRegistrationSeededBuilderForType<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, Reuse["type"]>;
|
|
1275
|
+
add<Path extends LocatorSchemaPathType, Reuse extends LocatorSchemaPathType | ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]> | undefined = undefined>(path: PathArgument<LocatorSchemaPathType, Path>, ...args: Reuse extends undefined ? [] : [
|
|
1276
|
+
options: {
|
|
1277
|
+
reuse: Reuse extends LocatorSchemaPathType ? ReusePathArgument<LocatorSchemaPathType, Path, Reuse> : Extract<Reuse, ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]>>;
|
|
1278
|
+
}
|
|
1279
|
+
]): Reuse extends undefined ? LocatorRegistrationPreDefinitionBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false> : Reuse extends LocatorSchemaPathType ? void : LocatorRegistrationSeededBuilderForType<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, Extract<Reuse, ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]>>["type"]>;
|
|
1277
1280
|
register(path: RegistryPath<LocatorSchemaPathType>, record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>): void;
|
|
1278
1281
|
replace(path: RegistryPath<LocatorSchemaPathType>, record: LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>): void;
|
|
1279
1282
|
get(path: RegistryPath<LocatorSchemaPathType>): LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
|
|
@@ -1297,7 +1300,7 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1297
1300
|
* const locator = builder.getNestedLocator();
|
|
1298
1301
|
* ```
|
|
1299
1302
|
*/
|
|
1300
|
-
getLocatorSchema<Path extends
|
|
1303
|
+
getLocatorSchema<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): LocatorQueryBuilderPublic<LocatorSchemaPathType, Extract<Path, RegistryPath<LocatorSchemaPathType>>>;
|
|
1301
1304
|
/**
|
|
1302
1305
|
* Resolves the Playwright {@link Locator} for the provided path, applying only the terminal
|
|
1303
1306
|
* definition and its recorded steps (no ancestor chaining). Throws if the path is not registered.
|
|
@@ -1308,7 +1311,7 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1308
1311
|
* await button.click();
|
|
1309
1312
|
* ```
|
|
1310
1313
|
*/
|
|
1311
|
-
getLocator<Path extends
|
|
1314
|
+
getLocator<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): Locator;
|
|
1312
1315
|
/**
|
|
1313
1316
|
* Resolves a chained Playwright {@link Locator} for a nested path, traversing each registered
|
|
1314
1317
|
* segment and applying their steps in order. Throws if any segment in the chain is missing.
|
|
@@ -1319,7 +1322,7 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1319
1322
|
* await expect(nested).toBeVisible();
|
|
1320
1323
|
* ```
|
|
1321
1324
|
*/
|
|
1322
|
-
getNestedLocator<Path extends
|
|
1325
|
+
getNestedLocator<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): Locator;
|
|
1323
1326
|
buildLocatorChain(path: RegistryPath<LocatorSchemaPathType>, definitions: Map<string, LocatorStrategyDefinition>, steps: Map<string, LocatorStep<RegistryPath<LocatorSchemaPathType>, RegistryPath<LocatorSchemaPathType>>[]>, tombstones?: Set<string>, terminalDescription?: LocatorDescription, context?: {
|
|
1324
1327
|
rootPath: RegistryPath<LocatorSchemaPathType>;
|
|
1325
1328
|
resolvingPaths: Set<string>;
|
|
@@ -1334,10 +1337,10 @@ declare class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
1334
1337
|
}[];
|
|
1335
1338
|
};
|
|
1336
1339
|
}
|
|
1337
|
-
type GetLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends
|
|
1340
|
+
type GetLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>) => Locator;
|
|
1338
1341
|
type AddAccessor<LocatorSchemaPathType extends string> = LocatorRegistryInternal<LocatorSchemaPathType>["add"];
|
|
1339
|
-
type GetLocatorSchemaAccessor<LocatorSchemaPathType extends string> = <Path extends
|
|
1340
|
-
type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends
|
|
1342
|
+
type GetLocatorSchemaAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>) => LocatorQueryBuilderPublic<LocatorSchemaPathType, Extract<Path, RegistryPath<LocatorSchemaPathType>>>;
|
|
1343
|
+
type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>) => Locator;
|
|
1341
1344
|
type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<LocatorRegistryInternal<LocatorSchemaPathType>, "add" | "createReusable" | "getLocator" | "getLocatorSchema" | "getNestedLocator">;
|
|
1342
1345
|
/**
|
|
1343
1346
|
* Creates a v2 locator registry bound to a Playwright {@link Page} and returns the registry plus
|
|
@@ -1359,15 +1362,9 @@ type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<LocatorRegistr
|
|
|
1359
1362
|
*/
|
|
1360
1363
|
declare const createRegistryWithAccessors: <Paths extends string>(page: Page) => {
|
|
1361
1364
|
readonly registry: LocatorRegistry<Paths>;
|
|
1362
|
-
readonly add: {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
reuse: RegistryPath<Paths>;
|
|
1366
|
-
}): void;
|
|
1367
|
-
<Reuse extends ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id">>(path: RegistryPath<Paths>, options: {
|
|
1368
|
-
reuse: Reuse;
|
|
1369
|
-
}): LocatorRegistrationSeededBuilderForType<Paths, RegistryPath<Paths>, Reuse["type"]>;
|
|
1370
|
-
};
|
|
1365
|
+
readonly add: <Path extends Paths, Reuse extends Paths | ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id"> | undefined = undefined>(path: PathArgument<Paths, Path>, ...args: Reuse extends undefined ? [] : [options: {
|
|
1366
|
+
reuse: Reuse extends Paths ? ReusePathArgument<Paths, Path, Reuse> : Extract<Reuse, ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id">>;
|
|
1367
|
+
}]) => Reuse extends undefined ? LocatorRegistrationPreDefinitionBuilder<Paths, RegistryPath<Paths>, false> : Reuse extends Paths ? void : LocatorRegistrationSeededBuilderForType<Paths, RegistryPath<Paths>, Extract<Reuse, ReusableLocator<Paths, RegistryPath<Paths>, "role" | "text" | "label" | "placeholder" | "altText" | "title" | "locator" | "frameLocator" | "testId" | "id">>["type"]>;
|
|
1371
1368
|
readonly getLocator: GetLocatorAccessor<Paths>;
|
|
1372
1369
|
readonly getNestedLocator: GetNestedLocatorAccessor<Paths>;
|
|
1373
1370
|
readonly getLocatorSchema: GetLocatorSchemaAccessor<Paths>;
|
package/dist/index.js
CHANGED
|
@@ -2498,20 +2498,45 @@ var LocatorRegistryInternal = class {
|
|
|
2498
2498
|
...record.description !== void 0 ? { description: record.description } : {}
|
|
2499
2499
|
};
|
|
2500
2500
|
}
|
|
2501
|
-
|
|
2501
|
+
/**
|
|
2502
|
+
* Registers a locator schema at the provided dot-delimited path.
|
|
2503
|
+
* Accepts exactly one locator strategy (`getByRole`, `locator`, etc.) and any number of
|
|
2504
|
+
* `filter`/`nth` steps chained in call order. When `{ reuse }` is supplied, the seeded
|
|
2505
|
+
* definition is applied first and one matching override is allowed as a PATCH of the seed;
|
|
2506
|
+
* otherwise, calling multiple locator strategies will throw.
|
|
2507
|
+
*
|
|
2508
|
+
* @example
|
|
2509
|
+
* ```ts
|
|
2510
|
+
* registry
|
|
2511
|
+
* .add("list.item")
|
|
2512
|
+
* .getByRole("listitem", { name: /Row/ })
|
|
2513
|
+
* .filter({ hasText: "Row" })
|
|
2514
|
+
* .nth("last");
|
|
2515
|
+
* ```
|
|
2516
|
+
*/
|
|
2517
|
+
add(path, ...args) {
|
|
2518
|
+
const options = args[0];
|
|
2502
2519
|
const reuse = options?.reuse;
|
|
2503
|
-
if (
|
|
2520
|
+
if (reuse === void 0) {
|
|
2504
2521
|
return new LocatorRegistrationBuilder(
|
|
2505
2522
|
this,
|
|
2506
2523
|
path
|
|
2507
2524
|
);
|
|
2508
2525
|
}
|
|
2509
2526
|
if (typeof reuse === "string") {
|
|
2527
|
+
const targetPath = path;
|
|
2528
|
+
if (reuse === targetPath) {
|
|
2529
|
+
throw new Error(`Locator reuse path cannot be the same as registration path: "${targetPath}".`);
|
|
2530
|
+
}
|
|
2510
2531
|
const sourceRecord = this.get(reuse);
|
|
2511
|
-
const cloned = this.cloneRecordForReuse(sourceRecord,
|
|
2512
|
-
this.register(
|
|
2532
|
+
const cloned = this.cloneRecordForReuse(sourceRecord, targetPath);
|
|
2533
|
+
this.register(targetPath, cloned);
|
|
2513
2534
|
return void 0;
|
|
2514
2535
|
}
|
|
2536
|
+
if (Array.isArray(reuse)) {
|
|
2537
|
+
const [reason] = reuse;
|
|
2538
|
+
throw new Error(`Invalid reuse path configuration for "${path}": ${reason}`);
|
|
2539
|
+
}
|
|
2515
2540
|
const reusedRecord = this.cloneRecordForReuse(
|
|
2516
2541
|
{
|
|
2517
2542
|
locatorSchemaPath: path,
|
|
@@ -2562,7 +2587,7 @@ Existing Schema: ${errorDetails}`);
|
|
|
2562
2587
|
}
|
|
2563
2588
|
return {
|
|
2564
2589
|
locatorSchemaPath: record.locatorSchemaPath,
|
|
2565
|
-
definition: record.definition,
|
|
2590
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
2566
2591
|
steps: normalizeSteps(record.steps),
|
|
2567
2592
|
...record.description !== void 0 ? { description: record.description } : {}
|
|
2568
2593
|
};
|
|
@@ -2577,7 +2602,7 @@ Existing Schema: ${errorDetails}`);
|
|
|
2577
2602
|
}
|
|
2578
2603
|
return {
|
|
2579
2604
|
locatorSchemaPath: record.locatorSchemaPath,
|
|
2580
|
-
definition: record.definition,
|
|
2605
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
2581
2606
|
steps: normalizeSteps(record.steps),
|
|
2582
2607
|
...record.description !== void 0 ? { description: record.description } : {}
|
|
2583
2608
|
};
|
|
@@ -2656,7 +2681,10 @@ Existing Schema: ${errorDetails}`);
|
|
|
2656
2681
|
* ```
|
|
2657
2682
|
*/
|
|
2658
2683
|
getLocatorSchema(path) {
|
|
2659
|
-
return new LocatorQueryBuilder(
|
|
2684
|
+
return new LocatorQueryBuilder(
|
|
2685
|
+
this,
|
|
2686
|
+
path
|
|
2687
|
+
);
|
|
2660
2688
|
}
|
|
2661
2689
|
/**
|
|
2662
2690
|
* Resolves the Playwright {@link Locator} for the provided path, applying only the terminal
|
|
@@ -2759,7 +2787,6 @@ Existing Schema: ${errorDetails}`);
|
|
|
2759
2787
|
}
|
|
2760
2788
|
};
|
|
2761
2789
|
var createRegistryWithAccessors = (page) => {
|
|
2762
|
-
const _assertValidPaths = true;
|
|
2763
2790
|
const registryInstance = new LocatorRegistryInternal(page);
|
|
2764
2791
|
const add = registryInstance.add.bind(registryInstance);
|
|
2765
2792
|
const getLocator = registryInstance.getLocator.bind(registryInstance);
|
package/dist/index.mjs
CHANGED
|
@@ -2462,20 +2462,45 @@ var LocatorRegistryInternal = class {
|
|
|
2462
2462
|
...record.description !== void 0 ? { description: record.description } : {}
|
|
2463
2463
|
};
|
|
2464
2464
|
}
|
|
2465
|
-
|
|
2465
|
+
/**
|
|
2466
|
+
* Registers a locator schema at the provided dot-delimited path.
|
|
2467
|
+
* Accepts exactly one locator strategy (`getByRole`, `locator`, etc.) and any number of
|
|
2468
|
+
* `filter`/`nth` steps chained in call order. When `{ reuse }` is supplied, the seeded
|
|
2469
|
+
* definition is applied first and one matching override is allowed as a PATCH of the seed;
|
|
2470
|
+
* otherwise, calling multiple locator strategies will throw.
|
|
2471
|
+
*
|
|
2472
|
+
* @example
|
|
2473
|
+
* ```ts
|
|
2474
|
+
* registry
|
|
2475
|
+
* .add("list.item")
|
|
2476
|
+
* .getByRole("listitem", { name: /Row/ })
|
|
2477
|
+
* .filter({ hasText: "Row" })
|
|
2478
|
+
* .nth("last");
|
|
2479
|
+
* ```
|
|
2480
|
+
*/
|
|
2481
|
+
add(path, ...args) {
|
|
2482
|
+
const options = args[0];
|
|
2466
2483
|
const reuse = options?.reuse;
|
|
2467
|
-
if (
|
|
2484
|
+
if (reuse === void 0) {
|
|
2468
2485
|
return new LocatorRegistrationBuilder(
|
|
2469
2486
|
this,
|
|
2470
2487
|
path
|
|
2471
2488
|
);
|
|
2472
2489
|
}
|
|
2473
2490
|
if (typeof reuse === "string") {
|
|
2491
|
+
const targetPath = path;
|
|
2492
|
+
if (reuse === targetPath) {
|
|
2493
|
+
throw new Error(`Locator reuse path cannot be the same as registration path: "${targetPath}".`);
|
|
2494
|
+
}
|
|
2474
2495
|
const sourceRecord = this.get(reuse);
|
|
2475
|
-
const cloned = this.cloneRecordForReuse(sourceRecord,
|
|
2476
|
-
this.register(
|
|
2496
|
+
const cloned = this.cloneRecordForReuse(sourceRecord, targetPath);
|
|
2497
|
+
this.register(targetPath, cloned);
|
|
2477
2498
|
return void 0;
|
|
2478
2499
|
}
|
|
2500
|
+
if (Array.isArray(reuse)) {
|
|
2501
|
+
const [reason] = reuse;
|
|
2502
|
+
throw new Error(`Invalid reuse path configuration for "${path}": ${reason}`);
|
|
2503
|
+
}
|
|
2479
2504
|
const reusedRecord = this.cloneRecordForReuse(
|
|
2480
2505
|
{
|
|
2481
2506
|
locatorSchemaPath: path,
|
|
@@ -2526,7 +2551,7 @@ Existing Schema: ${errorDetails}`);
|
|
|
2526
2551
|
}
|
|
2527
2552
|
return {
|
|
2528
2553
|
locatorSchemaPath: record.locatorSchemaPath,
|
|
2529
|
-
definition: record.definition,
|
|
2554
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
2530
2555
|
steps: normalizeSteps(record.steps),
|
|
2531
2556
|
...record.description !== void 0 ? { description: record.description } : {}
|
|
2532
2557
|
};
|
|
@@ -2541,7 +2566,7 @@ Existing Schema: ${errorDetails}`);
|
|
|
2541
2566
|
}
|
|
2542
2567
|
return {
|
|
2543
2568
|
locatorSchemaPath: record.locatorSchemaPath,
|
|
2544
|
-
definition: record.definition,
|
|
2569
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
2545
2570
|
steps: normalizeSteps(record.steps),
|
|
2546
2571
|
...record.description !== void 0 ? { description: record.description } : {}
|
|
2547
2572
|
};
|
|
@@ -2620,7 +2645,10 @@ Existing Schema: ${errorDetails}`);
|
|
|
2620
2645
|
* ```
|
|
2621
2646
|
*/
|
|
2622
2647
|
getLocatorSchema(path) {
|
|
2623
|
-
return new LocatorQueryBuilder(
|
|
2648
|
+
return new LocatorQueryBuilder(
|
|
2649
|
+
this,
|
|
2650
|
+
path
|
|
2651
|
+
);
|
|
2624
2652
|
}
|
|
2625
2653
|
/**
|
|
2626
2654
|
* Resolves the Playwright {@link Locator} for the provided path, applying only the terminal
|
|
@@ -2723,7 +2751,6 @@ Existing Schema: ${errorDetails}`);
|
|
|
2723
2751
|
}
|
|
2724
2752
|
};
|
|
2725
2753
|
var createRegistryWithAccessors = (page) => {
|
|
2726
|
-
const _assertValidPaths = true;
|
|
2727
2754
|
const registryInstance = new LocatorRegistryInternal(page);
|
|
2728
2755
|
const add = registryInstance.add.bind(registryInstance);
|
|
2729
2756
|
const getLocator = registryInstance.getLocator.bind(registryInstance);
|
|
@@ -119,7 +119,9 @@ type Paths =
|
|
|
119
119
|
| "main.button@login";
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
If invalid literals are included in the union,
|
|
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.
|
|
123
125
|
|
|
124
126
|
### Runtime validation rules
|
|
125
127
|
|
package/intTestV2/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { expect, test } from "@fixtures-v2/testApp.fixtures";
|
|
2
|
+
import type { Page } from "@playwright/test";
|
|
3
|
+
import { LocatorRegistryInternal } from "../../../../srcV2/locators";
|
|
4
|
+
|
|
5
|
+
const createTestRegistry = <Paths extends string>(page: Page) => new LocatorRegistryInternal<Paths>(page);
|
|
6
|
+
|
|
7
|
+
test("registry.get returns a cloned definition copy", async ({ page }) => {
|
|
8
|
+
type LocatorSchemaPaths = "button";
|
|
9
|
+
|
|
10
|
+
const registry = createTestRegistry<LocatorSchemaPaths>(page);
|
|
11
|
+
|
|
12
|
+
registry.add("button").getByRole("button", { name: "Submit", exact: true });
|
|
13
|
+
|
|
14
|
+
const firstRecord = registry.get("button");
|
|
15
|
+
|
|
16
|
+
expect(firstRecord).toEqual({
|
|
17
|
+
locatorSchemaPath: "button",
|
|
18
|
+
definition: {
|
|
19
|
+
type: "role",
|
|
20
|
+
role: "button",
|
|
21
|
+
options: { name: "Submit", exact: true },
|
|
22
|
+
},
|
|
23
|
+
steps: [],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (firstRecord.definition.type !== "role") {
|
|
27
|
+
throw new Error("Expected role definition for mutation test");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
firstRecord.definition.options = { name: "Mutated" };
|
|
31
|
+
|
|
32
|
+
const secondRecord = registry.get("button");
|
|
33
|
+
|
|
34
|
+
expect(secondRecord).toEqual({
|
|
35
|
+
locatorSchemaPath: "button",
|
|
36
|
+
definition: {
|
|
37
|
+
type: "role",
|
|
38
|
+
role: "button",
|
|
39
|
+
options: { name: "Submit", exact: true },
|
|
40
|
+
},
|
|
41
|
+
steps: [],
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("registry.getIfExists returns a cloned definition copy", async ({ page }) => {
|
|
46
|
+
type LocatorSchemaPaths = "banner";
|
|
47
|
+
|
|
48
|
+
const registry = createTestRegistry<LocatorSchemaPaths>(page);
|
|
49
|
+
|
|
50
|
+
registry.add("banner").locator("section.banner", { hasText: "Welcome" });
|
|
51
|
+
|
|
52
|
+
const firstRecord = registry.getIfExists("banner");
|
|
53
|
+
expect(firstRecord).toBeDefined();
|
|
54
|
+
|
|
55
|
+
if (!firstRecord || firstRecord.definition.type !== "locator") {
|
|
56
|
+
throw new Error("Expected locator definition for mutation test");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
firstRecord.definition.options = { hasText: "Mutated" };
|
|
60
|
+
if (!firstRecord.steps) {
|
|
61
|
+
throw new Error("Expected steps array on cloned record");
|
|
62
|
+
}
|
|
63
|
+
firstRecord.steps.push({ kind: "index", index: 0 });
|
|
64
|
+
|
|
65
|
+
const secondRecord = registry.getIfExists("banner");
|
|
66
|
+
|
|
67
|
+
expect(secondRecord).toEqual({
|
|
68
|
+
locatorSchemaPath: "banner",
|
|
69
|
+
definition: {
|
|
70
|
+
type: "locator",
|
|
71
|
+
selector: "section.banner",
|
|
72
|
+
options: { hasText: "Welcome" },
|
|
73
|
+
},
|
|
74
|
+
steps: [],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -21,6 +21,47 @@ test("add reuses with existing record by path does not have chainable methods",
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
test("add reuse by path with empty string does not silently skip reuse", async ({ page }) => {
|
|
25
|
+
type LocatorSchemaPaths = "button";
|
|
26
|
+
|
|
27
|
+
const registry = createTestRegistry<LocatorSchemaPaths>(page);
|
|
28
|
+
|
|
29
|
+
// @ts-expect-error - testing runtime error when reuse is an empty string
|
|
30
|
+
expect(() => registry.add("button", { reuse: "" })).toThrowError('No locator schema registered for path "".');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("add reuse by path throws when source path matches target path", async ({ page }) => {
|
|
34
|
+
type LocatorSchemaPaths = "button";
|
|
35
|
+
|
|
36
|
+
const registry = createTestRegistry<LocatorSchemaPaths>(page);
|
|
37
|
+
|
|
38
|
+
// @ts-expect-error - testing runtime error when reuse path is the same as registration path
|
|
39
|
+
expect(() => registry.add("button", { reuse: "button" })).toThrowError(
|
|
40
|
+
'Locator reuse path cannot be the same as registration path: "button".',
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("add reuse by path reports compile-time fallback reason when forced at runtime", async ({ page }) => {
|
|
45
|
+
type LocatorSchemaPaths = "button" | "button.copy";
|
|
46
|
+
|
|
47
|
+
const registry = createTestRegistry<LocatorSchemaPaths>(page);
|
|
48
|
+
|
|
49
|
+
const addUnsafe = registry.add as unknown as (
|
|
50
|
+
path: string,
|
|
51
|
+
options: {
|
|
52
|
+
reuse: [string];
|
|
53
|
+
},
|
|
54
|
+
) => void;
|
|
55
|
+
|
|
56
|
+
expect(() =>
|
|
57
|
+
addUnsafe("button.copy", {
|
|
58
|
+
reuse: ["Invalid reuse path, reuse path cannot be the same as registration path: button.copy"],
|
|
59
|
+
}),
|
|
60
|
+
).toThrowError(
|
|
61
|
+
'Invalid reuse path configuration for "button.copy": Invalid reuse path, reuse path cannot be the same as registration path: button.copy',
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
24
65
|
test("add reuse by path clones records so mutations do not leak", async ({ page }) => {
|
|
25
66
|
type LocatorSchemaPaths = "button" | "button.copy";
|
|
26
67
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Page } from "@playwright/test";
|
|
2
|
+
import { createRegistryWithAccessors } from "pomwright";
|
|
3
|
+
|
|
4
|
+
declare const page: Page;
|
|
5
|
+
|
|
6
|
+
type ValidPaths = "valid" | "validReuse";
|
|
7
|
+
|
|
8
|
+
const validRegistryFactoryResult = createRegistryWithAccessors<ValidPaths>(page);
|
|
9
|
+
|
|
10
|
+
validRegistryFactoryResult.add("valid").locator("body");
|
|
11
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
12
|
+
validRegistryFactoryResult.add("does.not.exist").locator("body");
|
|
13
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
14
|
+
validRegistryFactoryResult.add("valid", { reuse: "does.not.exist" });
|
|
15
|
+
// @ts-expect-error reuse path cannot be the same as registration path
|
|
16
|
+
validRegistryFactoryResult.add("validReuse", { reuse: "validReuse" });
|
|
17
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
18
|
+
validRegistryFactoryResult.getLocator("does.not.exist");
|
|
19
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
20
|
+
validRegistryFactoryResult.getLocatorSchema("does.not.exist");
|
|
21
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
22
|
+
validRegistryFactoryResult.getNestedLocator("does.not.exist");
|
|
23
|
+
|
|
24
|
+
type InvalidPaths = ValidPaths | "" | ".leading" | "trailing." | "double..dot" | "contains whitespace";
|
|
25
|
+
|
|
26
|
+
const invalidRegistryFactoryResult = createRegistryWithAccessors<InvalidPaths>(page);
|
|
27
|
+
|
|
28
|
+
invalidRegistryFactoryResult.add("valid").locator("body");
|
|
29
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
30
|
+
invalidRegistryFactoryResult.add("").locator("body");
|
|
31
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
32
|
+
invalidRegistryFactoryResult.add(".leading").locator("body");
|
|
33
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
34
|
+
invalidRegistryFactoryResult.add("trailing.").locator("body");
|
|
35
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
36
|
+
invalidRegistryFactoryResult.add("double..dot").locator("body");
|
|
37
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
38
|
+
invalidRegistryFactoryResult.add("contains whitespace").locator("body");
|
|
39
|
+
|
|
40
|
+
invalidRegistryFactoryResult.add("validReuse", { reuse: "valid" });
|
|
41
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
42
|
+
invalidRegistryFactoryResult.add("validReuse", { reuse: "" });
|
|
43
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
44
|
+
invalidRegistryFactoryResult.add("validReuse", { reuse: ".leading" });
|
|
45
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
46
|
+
invalidRegistryFactoryResult.add("validReuse", { reuse: "trailing." });
|
|
47
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
48
|
+
invalidRegistryFactoryResult.add("validReuse", { reuse: "double..dot" });
|
|
49
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
50
|
+
invalidRegistryFactoryResult.add("validReuse", { reuse: "contains whitespace" });
|
|
51
|
+
|
|
52
|
+
invalidRegistryFactoryResult.getLocator("valid");
|
|
53
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
54
|
+
invalidRegistryFactoryResult.getLocator("");
|
|
55
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
56
|
+
invalidRegistryFactoryResult.getLocator(".leading");
|
|
57
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
58
|
+
invalidRegistryFactoryResult.getLocator("trailing.");
|
|
59
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
60
|
+
invalidRegistryFactoryResult.getLocator("double..dot");
|
|
61
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
62
|
+
invalidRegistryFactoryResult.getLocator("contains whitespace");
|
|
63
|
+
|
|
64
|
+
invalidRegistryFactoryResult.getLocatorSchema("valid");
|
|
65
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
66
|
+
invalidRegistryFactoryResult.getLocatorSchema("");
|
|
67
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
68
|
+
invalidRegistryFactoryResult.getLocatorSchema(".leading");
|
|
69
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
70
|
+
invalidRegistryFactoryResult.getLocatorSchema("trailing.");
|
|
71
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
72
|
+
invalidRegistryFactoryResult.getLocatorSchema("double..dot");
|
|
73
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
74
|
+
invalidRegistryFactoryResult.getLocatorSchema("contains whitespace");
|
|
75
|
+
|
|
76
|
+
invalidRegistryFactoryResult.getNestedLocator("valid");
|
|
77
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
78
|
+
invalidRegistryFactoryResult.getNestedLocator("");
|
|
79
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
80
|
+
invalidRegistryFactoryResult.getNestedLocator(".leading");
|
|
81
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
82
|
+
invalidRegistryFactoryResult.getNestedLocator("trailing.");
|
|
83
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
84
|
+
invalidRegistryFactoryResult.getNestedLocator("double..dot");
|
|
85
|
+
// @ts-expect-error invalid path literal should fail at the accessor argument with path-format details
|
|
86
|
+
invalidRegistryFactoryResult.getNestedLocator("contains whitespace");
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
IndexSelector,
|
|
13
13
|
LocatorBuilderTarget,
|
|
14
14
|
LocatorDescription,
|
|
15
|
-
|
|
15
|
+
LocatorSchemaPathFormat,
|
|
16
16
|
LocatorSchemaRecord,
|
|
17
17
|
LocatorStep,
|
|
18
18
|
LocatorStrategyDefinition,
|
|
@@ -33,6 +33,21 @@ import {
|
|
|
33
33
|
validateLocatorSchemaPath,
|
|
34
34
|
} from "./utils";
|
|
35
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
|
+
|
|
36
51
|
export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
37
52
|
private readonly schemas = new Map<
|
|
38
53
|
RegistryPath<LocatorSchemaPathType>,
|
|
@@ -94,50 +109,82 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
94
109
|
* .nth("last");
|
|
95
110
|
* ```
|
|
96
111
|
*/
|
|
97
|
-
add(
|
|
98
|
-
path: RegistryPath<LocatorSchemaPathType>,
|
|
99
|
-
): LocatorRegistrationPreDefinitionBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false>;
|
|
100
|
-
add(path: RegistryPath<LocatorSchemaPathType>, options: { reuse: RegistryPath<LocatorSchemaPathType> }): void;
|
|
101
112
|
add<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
LocatorStrategyDefinition["type"]
|
|
106
|
-
|
|
113
|
+
Path extends LocatorSchemaPathType,
|
|
114
|
+
Reuse extends
|
|
115
|
+
| LocatorSchemaPathType
|
|
116
|
+
| ReusableLocator<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, LocatorStrategyDefinition["type"]>
|
|
117
|
+
| undefined = undefined,
|
|
107
118
|
>(
|
|
108
|
-
path:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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;
|
|
126
164
|
const reuse = options?.reuse;
|
|
127
165
|
|
|
128
|
-
if (
|
|
166
|
+
if (reuse === undefined) {
|
|
129
167
|
return new LocatorRegistrationBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, false>(
|
|
130
168
|
this,
|
|
131
|
-
path
|
|
132
|
-
);
|
|
169
|
+
path as RegistryPath<LocatorSchemaPathType>,
|
|
170
|
+
) as never;
|
|
133
171
|
}
|
|
134
172
|
|
|
135
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
|
+
}
|
|
136
178
|
const sourceRecord = this.get(reuse as RegistryPath<LocatorSchemaPathType>);
|
|
137
|
-
const cloned = this.cloneRecordForReuse(sourceRecord,
|
|
179
|
+
const cloned = this.cloneRecordForReuse(sourceRecord, targetPath);
|
|
180
|
+
|
|
181
|
+
this.register(targetPath, cloned);
|
|
182
|
+
return undefined as never;
|
|
183
|
+
}
|
|
138
184
|
|
|
139
|
-
|
|
140
|
-
|
|
185
|
+
if (Array.isArray(reuse)) {
|
|
186
|
+
const [reason] = reuse;
|
|
187
|
+
throw new Error(`Invalid reuse path configuration for "${path}": ${reason}`);
|
|
141
188
|
}
|
|
142
189
|
|
|
143
190
|
const reusedRecord: LocatorSchemaRecord<
|
|
@@ -145,24 +192,24 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
145
192
|
RegistryPath<LocatorSchemaPathType>
|
|
146
193
|
> = this.cloneRecordForReuse(
|
|
147
194
|
{
|
|
148
|
-
locatorSchemaPath: path
|
|
195
|
+
locatorSchemaPath: path as RegistryPath<LocatorSchemaPathType>,
|
|
149
196
|
definition: reuse.definition,
|
|
150
197
|
steps: reuse.steps,
|
|
151
198
|
description: reuse.description,
|
|
152
199
|
},
|
|
153
|
-
path
|
|
200
|
+
path as RegistryPath<LocatorSchemaPathType>,
|
|
154
201
|
);
|
|
155
202
|
|
|
156
203
|
return new LocatorRegistrationBuilder<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>, true>(
|
|
157
204
|
this,
|
|
158
|
-
path
|
|
205
|
+
path as RegistryPath<LocatorSchemaPathType>,
|
|
159
206
|
{
|
|
160
207
|
initialDefinition: reusedRecord.definition,
|
|
161
208
|
initialSteps: reusedRecord.steps,
|
|
162
209
|
reuseType: reusedRecord.definition.type,
|
|
163
210
|
initialDescription: reusedRecord.description,
|
|
164
211
|
},
|
|
165
|
-
).persistSeededDefinition();
|
|
212
|
+
).persistSeededDefinition() as never;
|
|
166
213
|
}
|
|
167
214
|
|
|
168
215
|
register(
|
|
@@ -204,7 +251,7 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
204
251
|
}
|
|
205
252
|
return {
|
|
206
253
|
locatorSchemaPath: record.locatorSchemaPath,
|
|
207
|
-
definition: record.definition,
|
|
254
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
208
255
|
steps: normalizeSteps(record.steps),
|
|
209
256
|
...(record.description !== undefined ? { description: record.description } : {}),
|
|
210
257
|
} satisfies LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
|
|
@@ -219,12 +266,12 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
219
266
|
): LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>> | undefined {
|
|
220
267
|
const record = this.schemas.get(path);
|
|
221
268
|
if (!record) {
|
|
222
|
-
return undefined;
|
|
269
|
+
return undefined as never;
|
|
223
270
|
}
|
|
224
271
|
|
|
225
272
|
return {
|
|
226
273
|
locatorSchemaPath: record.locatorSchemaPath,
|
|
227
|
-
definition: record.definition,
|
|
274
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
228
275
|
steps: normalizeSteps(record.steps),
|
|
229
276
|
...(record.description !== undefined ? { description: record.description } : {}),
|
|
230
277
|
} satisfies LocatorSchemaRecord<LocatorSchemaPathType, RegistryPath<LocatorSchemaPathType>>;
|
|
@@ -345,10 +392,13 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
345
392
|
* const locator = builder.getNestedLocator();
|
|
346
393
|
* ```
|
|
347
394
|
*/
|
|
348
|
-
getLocatorSchema<Path extends
|
|
349
|
-
path: Path
|
|
350
|
-
): LocatorQueryBuilderPublic<LocatorSchemaPathType, Path
|
|
351
|
-
return new LocatorQueryBuilder<LocatorSchemaPathType, Path
|
|
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
|
+
);
|
|
352
402
|
}
|
|
353
403
|
|
|
354
404
|
/**
|
|
@@ -361,7 +411,7 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
361
411
|
* await button.click();
|
|
362
412
|
* ```
|
|
363
413
|
*/
|
|
364
|
-
getLocator<Path extends
|
|
414
|
+
getLocator<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): Locator {
|
|
365
415
|
return this.getLocatorSchema(path).getLocator();
|
|
366
416
|
}
|
|
367
417
|
|
|
@@ -375,7 +425,7 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
375
425
|
* await expect(nested).toBeVisible();
|
|
376
426
|
* ```
|
|
377
427
|
*/
|
|
378
|
-
getNestedLocator<Path extends
|
|
428
|
+
getNestedLocator<Path extends LocatorSchemaPathType>(path: PathArgument<LocatorSchemaPathType, Path>): Locator {
|
|
379
429
|
return this.getLocatorSchema(path).getNestedLocator();
|
|
380
430
|
}
|
|
381
431
|
|
|
@@ -485,24 +535,18 @@ export class LocatorRegistryInternal<LocatorSchemaPathType extends string> {
|
|
|
485
535
|
}
|
|
486
536
|
}
|
|
487
537
|
|
|
488
|
-
export type GetLocatorAccessor<LocatorSchemaPathType extends string> = <
|
|
489
|
-
Path extends
|
|
490
|
-
>(
|
|
491
|
-
path: Path,
|
|
538
|
+
export type GetLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(
|
|
539
|
+
path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>,
|
|
492
540
|
) => Locator;
|
|
493
541
|
|
|
494
542
|
export type AddAccessor<LocatorSchemaPathType extends string> = LocatorRegistryInternal<LocatorSchemaPathType>["add"];
|
|
495
543
|
|
|
496
|
-
export type GetLocatorSchemaAccessor<LocatorSchemaPathType extends string> = <
|
|
497
|
-
Path extends
|
|
498
|
-
|
|
499
|
-
path: Path,
|
|
500
|
-
) => LocatorQueryBuilderPublic<LocatorSchemaPathType, Path>;
|
|
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>>>;
|
|
501
547
|
|
|
502
|
-
export type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <
|
|
503
|
-
Path extends
|
|
504
|
-
>(
|
|
505
|
-
path: Path,
|
|
548
|
+
export type GetNestedLocatorAccessor<LocatorSchemaPathType extends string> = <Path extends LocatorSchemaPathType>(
|
|
549
|
+
path: LocatorSchemaPathFormat<Path> extends Path ? Path : LocatorSchemaPathFormat<Path>,
|
|
506
550
|
) => Locator;
|
|
507
551
|
|
|
508
552
|
export type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<
|
|
@@ -529,8 +573,6 @@ export type LocatorRegistry<LocatorSchemaPathType extends string> = Pick<
|
|
|
529
573
|
* ```
|
|
530
574
|
*/
|
|
531
575
|
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
576
|
const registryInstance = new LocatorRegistryInternal<Paths>(page);
|
|
535
577
|
const add: LocatorRegistryInternal<Paths>["add"] = registryInstance.add.bind(registryInstance);
|
|
536
578
|
const getLocator: GetLocatorAccessor<Paths> = registryInstance.getLocator.bind(registryInstance);
|