pw-core 1.2.0 → 1.2.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/README.md +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +777 -0
- package/dist/codegen/config/context-weights.d.ts +8 -0
- package/dist/codegen/config/context-weights.js +11 -0
- package/dist/codegen/config/ignored-attributes.d.ts +1 -0
- package/dist/codegen/config/ignored-attributes.js +4 -0
- package/dist/codegen/config/ignored-classes.d.ts +1 -0
- package/dist/codegen/config/ignored-classes.js +144 -0
- package/dist/codegen/config/ignored-values.d.ts +1 -0
- package/dist/codegen/config/ignored-values.js +4 -0
- package/dist/codegen/config/index.d.ts +29 -0
- package/dist/codegen/config/index.js +17 -0
- package/dist/codegen/config/strategy-order.d.ts +1 -0
- package/dist/codegen/config/strategy-order.js +18 -0
- package/dist/codegen/config/weights.d.ts +15 -0
- package/dist/codegen/config/weights.js +18 -0
- package/dist/codegen/floating-panel/client.d.ts +1 -0
- package/dist/codegen/floating-panel/client.js +248 -0
- package/dist/codegen/floating-panel/index.d.ts +4 -0
- package/dist/codegen/floating-panel/index.js +20 -0
- package/dist/codegen/floating-panel/manager.d.ts +19 -0
- package/dist/codegen/floating-panel/manager.js +90 -0
- package/dist/codegen/floating-panel/template.d.ts +2 -0
- package/dist/codegen/floating-panel/template.js +58 -0
- package/dist/codegen/floating-panel/types.d.ts +7 -0
- package/dist/codegen/floating-panel/types.js +2 -0
- package/dist/codegen/generator/action.validator.d.ts +5 -0
- package/dist/codegen/generator/action.validator.js +27 -0
- package/dist/codegen/generator/index.d.ts +13 -0
- package/dist/codegen/generator/index.js +724 -0
- package/dist/codegen/generator/locator.restricted.d.ts +36 -0
- package/dist/codegen/generator/locator.restricted.js +127 -0
- package/dist/codegen/generator/locator.validator.d.ts +8 -0
- package/dist/codegen/generator/locator.validator.js +38 -0
- package/dist/codegen/hover-tracker/client.d.ts +1 -0
- package/dist/codegen/hover-tracker/client.js +75 -0
- package/dist/codegen/hover-tracker/index.d.ts +3 -0
- package/dist/codegen/hover-tracker/index.js +19 -0
- package/dist/codegen/hover-tracker/manager.d.ts +11 -0
- package/dist/codegen/hover-tracker/manager.js +28 -0
- package/dist/codegen/hover-tracker/types.d.ts +3 -0
- package/dist/codegen/hover-tracker/types.js +2 -0
- package/dist/codegen/index.d.ts +35 -0
- package/dist/codegen/index.js +1147 -0
- package/dist/codegen/key-utils.d.ts +23 -0
- package/dist/codegen/key-utils.js +68 -0
- package/dist/codegen/scorer/base-score.d.ts +1 -0
- package/dist/codegen/scorer/base-score.js +7 -0
- package/dist/codegen/scorer/context-score.d.ts +2 -0
- package/dist/codegen/scorer/context-score.js +27 -0
- package/dist/codegen/scorer/index.d.ts +5 -0
- package/dist/codegen/scorer/index.js +23 -0
- package/dist/codegen/scorer/locator.ranking.d.ts +15 -0
- package/dist/codegen/scorer/locator.ranking.js +83 -0
- package/dist/codegen/scorer/semantic-score.d.ts +1 -0
- package/dist/codegen/scorer/semantic-score.js +47 -0
- package/dist/codegen/types.d.ts +35 -0
- package/dist/codegen/types.js +2 -0
- package/dist/component/table.js +14 -10
- package/dist/constants/methods.d.ts +2 -0
- package/dist/constants/methods.js +33 -0
- package/dist/constants/strategies.d.ts +2 -0
- package/dist/constants/strategies.js +47 -0
- package/dist/page/actions/locator-actions.js +15 -4
- package/dist/page/assertions/verify-chain.d.ts +39 -32
- package/dist/page/assertions/verify-chain.js +18 -4
- package/dist/page/assertions/verify-helpers.js +12 -3
- package/dist/page/config.d.ts +109 -71
- package/dist/page/config.js +10 -31
- package/dist/page/locators/dynamic-locator-resolver.js +29 -9
- package/dist/page/locators/resolver.d.ts +15 -15
- package/dist/page/locators/resolver.js +151 -32
- package/dist/page/registry.d.ts +43 -6
- package/dist/page/registry.js +90 -18
- package/dist/page/typed-page.d.ts +33 -29
- package/dist/page/typed-page.js +40 -11
- package/dist/page/types/proxy-methods.d.ts +69 -54
- package/dist/page/types/validation.d.ts +48 -1
- package/dist/page/utils/caller-location.js +13 -9
- package/dist/page/utils/formatter.js +6 -3
- package/dist/types/methods.d.ts +4 -0
- package/dist/types/methods.js +2 -0
- package/dist/types/strategies.d.ts +17 -0
- package/dist/types/strategies.js +2 -0
- package/package.json +8 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Locator, expect as playwrightExpect } from '@playwright/test';
|
|
2
|
-
import {
|
|
2
|
+
import { PageKeys, GetStrategyOfKey } from '../config';
|
|
3
3
|
export type VerifyOptions = {
|
|
4
4
|
timeout?: number;
|
|
5
5
|
nth?: number;
|
|
@@ -11,64 +11,71 @@ type ModifyMatcherArgs<Args extends any[]> = Args extends [] ? [options?: {
|
|
|
11
11
|
nth?: number;
|
|
12
12
|
hasText?: string | RegExp;
|
|
13
13
|
message?: string;
|
|
14
|
-
}] : Args extends [any, any?] ? [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
14
|
+
}] : Args extends [any, any?] ? [
|
|
15
|
+
Args[0],
|
|
16
|
+
(Exclude<Args[1], undefined> & {
|
|
17
|
+
nth?: number;
|
|
18
|
+
hasText?: string | RegExp;
|
|
19
|
+
message?: string;
|
|
20
|
+
})?
|
|
21
|
+
] : Args extends [any?] ? Exclude<Args[0], undefined> extends object ? [
|
|
22
|
+
(Exclude<Args[0], undefined> & {
|
|
23
|
+
nth?: number;
|
|
24
|
+
hasText?: string | RegExp;
|
|
25
|
+
message?: string;
|
|
26
|
+
})?
|
|
27
|
+
] : [
|
|
28
|
+
Exclude<Args[0], undefined>,
|
|
29
|
+
options?: {
|
|
30
|
+
nth?: number;
|
|
31
|
+
hasText?: string | RegExp;
|
|
32
|
+
message?: string;
|
|
33
|
+
}
|
|
34
|
+
] : [
|
|
35
|
+
options?: {
|
|
36
|
+
nth?: number;
|
|
37
|
+
hasText?: string | RegExp;
|
|
38
|
+
message?: string;
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
type DynamicallyModifiedMatchers<T, Target> = {
|
|
42
|
+
[K in keyof PlaywrightLocatorMatchers]: K extends 'not' ? Omit<VerifyMatchers<T, Target>, 'not'> : K extends 'toBeChecked' ? Target extends Locator ? (...args: ModifyMatcherArgs<Parameters<PlaywrightLocatorMatchers[K]>>) => Promise<void> : Target extends string ? GetStrategyOfKey<T, Target> extends 'checkbox' | 'radio' ? (...args: ModifyMatcherArgs<Parameters<PlaywrightLocatorMatchers[K]>>) => Promise<void> : never : never : PlaywrightLocatorMatchers[K] extends (...args: infer Args) => any ? (...args: ModifyMatcherArgs<Args>) => Promise<void> : PlaywrightLocatorMatchers[K];
|
|
33
43
|
};
|
|
34
|
-
export type VerifyMatchers<T> = DynamicallyModifiedMatchers<T> & PromiseLike<void> & {
|
|
44
|
+
export type VerifyMatchers<T, Target> = DynamicallyModifiedMatchers<T, Target> & PromiseLike<void> & {
|
|
35
45
|
(options?: Parameters<PlaywrightLocatorMatchers['toBeVisible']>[0] & {
|
|
36
46
|
nth?: number;
|
|
37
47
|
hasText?: string | RegExp;
|
|
38
48
|
message?: string;
|
|
39
49
|
}): Promise<void>;
|
|
40
50
|
};
|
|
41
|
-
export type VerifyFn<T> =
|
|
51
|
+
export type VerifyFn<T> = <Target extends PageKeys<T> | Locator>(target: Target, options?: VerifyOptions) => VerifyMatchers<T, Target>;
|
|
42
52
|
export type AssertionsMethod<T> = {
|
|
43
53
|
verify: VerifyFn<T> & {
|
|
44
54
|
soft: VerifyFn<T>;
|
|
45
55
|
};
|
|
46
|
-
verifyHidden(target: PageKeys<T> |
|
|
56
|
+
verifyHidden(target: PageKeys<T> | Locator, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeHidden']>[0] & {
|
|
47
57
|
nth?: number;
|
|
48
58
|
hasText?: string | RegExp;
|
|
49
59
|
message?: string;
|
|
50
60
|
}): Promise<void>;
|
|
51
|
-
verifyEnabled(target: PageKeys<T> |
|
|
61
|
+
verifyEnabled(target: PageKeys<T> | Locator, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeEnabled']>[0] & {
|
|
52
62
|
nth?: number;
|
|
53
63
|
hasText?: string | RegExp;
|
|
54
64
|
message?: string;
|
|
55
65
|
}): Promise<void>;
|
|
56
|
-
verifyDisabled(target: PageKeys<T> |
|
|
66
|
+
verifyDisabled(target: PageKeys<T> | Locator, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeDisabled']>[0] & {
|
|
57
67
|
nth?: number;
|
|
58
68
|
hasText?: string | RegExp;
|
|
59
69
|
message?: string;
|
|
60
70
|
}): Promise<void>;
|
|
61
|
-
expect(target: PageKeys<T> |
|
|
62
|
-
locator(target: PageKeys<T> |
|
|
71
|
+
expect(target: PageKeys<T> | Locator, message?: string): ReturnType<typeof playwrightExpect<Locator>>;
|
|
72
|
+
locator(target: PageKeys<T> | Locator, options?: Parameters<Locator['filter']>[0] & {
|
|
63
73
|
nth?: number;
|
|
64
74
|
}): Locator;
|
|
65
75
|
};
|
|
66
|
-
export declare function createVerifyChain<T
|
|
67
|
-
testIds?: Record<string, string>;
|
|
68
|
-
selectors?: Record<string, string>;
|
|
69
|
-
}>(resolveLocator: (target: any, options?: {
|
|
76
|
+
export declare function createVerifyChain<T>(resolveLocator: (target: any, options?: {
|
|
70
77
|
nth?: number;
|
|
71
78
|
hasText?: string | RegExp;
|
|
72
79
|
raw?: boolean;
|
|
73
|
-
}) => Locator, target: PageKeys<T> |
|
|
80
|
+
}) => Locator, target: PageKeys<T> | Locator, verifyOptions: VerifyOptions | undefined, isSoft: boolean): VerifyMatchers<T, any>;
|
|
74
81
|
export {};
|
|
@@ -36,7 +36,17 @@ function createVerifyChain(resolveLocator, target, verifyOptions, isSoft) {
|
|
|
36
36
|
return baseFn().then(onfulfilled, onrejected);
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
const skippedProps = new Set([
|
|
39
|
+
const skippedProps = new Set([
|
|
40
|
+
'then',
|
|
41
|
+
'catch',
|
|
42
|
+
'finally',
|
|
43
|
+
'bind',
|
|
44
|
+
'call',
|
|
45
|
+
'apply',
|
|
46
|
+
'toString',
|
|
47
|
+
'valueOf',
|
|
48
|
+
'toLocaleString'
|
|
49
|
+
]);
|
|
40
50
|
if (skippedProps.has(prop)) {
|
|
41
51
|
return targetObj[prop];
|
|
42
52
|
}
|
|
@@ -45,12 +55,16 @@ function createVerifyChain(resolveLocator, target, verifyOptions, isSoft) {
|
|
|
45
55
|
const lastIsOptions = lastArg !== null && typeof lastArg === 'object' && !(lastArg instanceof RegExp) && !Array.isArray(lastArg);
|
|
46
56
|
const valueArgs = lastIsOptions ? args.slice(0, args.length - 1) : args;
|
|
47
57
|
const options = lastIsOptions ? lastArg : undefined;
|
|
48
|
-
const nth =
|
|
49
|
-
const hasText =
|
|
58
|
+
const nth = options && 'nth' in options ? options.nth : defaultNth;
|
|
59
|
+
const hasText = options && 'hasText' in options ? options.hasText : defaultHasText;
|
|
50
60
|
const isHaveCount = prop === 'toHaveCount';
|
|
51
61
|
const stepName = options?.message ?? defaultMessage ?? (0, formatter_1.formatAssertionDescription)(target, String(prop), isNegated, valueArgs);
|
|
52
62
|
await test_1.test.step(stepName, async () => {
|
|
53
|
-
const locator = resolveLocator(target, {
|
|
63
|
+
const locator = resolveLocator(target, {
|
|
64
|
+
nth,
|
|
65
|
+
hasText,
|
|
66
|
+
raw: isHaveCount
|
|
67
|
+
});
|
|
54
68
|
const expectation = expectFn(locator, stepName);
|
|
55
69
|
const match = isNegated ? expectation.not : expectation;
|
|
56
70
|
await match[prop](...args);
|
|
@@ -8,21 +8,30 @@ const formatter_1 = require("../utils/formatter");
|
|
|
8
8
|
async function verifyHidden(resolveLocator, target, options, location) {
|
|
9
9
|
const stepName = options?.message ?? (0, formatter_1.formatAssertionDescription)(target, 'toBeHidden', false, [options]);
|
|
10
10
|
await test_1.test.step(stepName, async () => {
|
|
11
|
-
const locator = resolveLocator(target, {
|
|
11
|
+
const locator = resolveLocator(target, {
|
|
12
|
+
nth: options?.nth,
|
|
13
|
+
hasText: options?.hasText
|
|
14
|
+
});
|
|
12
15
|
await (0, test_1.expect)(locator, stepName).toBeHidden(options);
|
|
13
16
|
}, { box: true, location });
|
|
14
17
|
}
|
|
15
18
|
async function verifyEnabled(resolveLocator, target, options, location) {
|
|
16
19
|
const stepName = options?.message ?? (0, formatter_1.formatAssertionDescription)(target, 'toBeEnabled', false, [options]);
|
|
17
20
|
await test_1.test.step(stepName, async () => {
|
|
18
|
-
const locator = resolveLocator(target, {
|
|
21
|
+
const locator = resolveLocator(target, {
|
|
22
|
+
nth: options?.nth,
|
|
23
|
+
hasText: options?.hasText
|
|
24
|
+
});
|
|
19
25
|
await (0, test_1.expect)(locator, stepName).toBeEnabled(options);
|
|
20
26
|
}, { box: true, location });
|
|
21
27
|
}
|
|
22
28
|
async function verifyDisabled(resolveLocator, target, options, location) {
|
|
23
29
|
const stepName = options?.message ?? (0, formatter_1.formatAssertionDescription)(target, 'toBeDisabled', false, [options]);
|
|
24
30
|
await test_1.test.step(stepName, async () => {
|
|
25
|
-
const locator = resolveLocator(target, {
|
|
31
|
+
const locator = resolveLocator(target, {
|
|
32
|
+
nth: options?.nth,
|
|
33
|
+
hasText: options?.hasText
|
|
34
|
+
});
|
|
26
35
|
await (0, test_1.expect)(locator, stepName).toBeDisabled(options);
|
|
27
36
|
}, { box: true, location });
|
|
28
37
|
}
|
package/dist/page/config.d.ts
CHANGED
|
@@ -1,101 +1,139 @@
|
|
|
1
|
-
import { Locator
|
|
1
|
+
import { Locator } from '@playwright/test';
|
|
2
2
|
import type { DynamicTestIdEntry, DynamicSelectorEntry } from './locators/dynamic-locator-resolver.js';
|
|
3
3
|
export { ProxyLocatorMethods } from './types/proxy-methods.js';
|
|
4
4
|
export type { DynamicLocatorEntry, DynamicTestIdEntry, DynamicSelectorEntry } from './locators/dynamic-locator-resolver.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
type ExpandDynamicKey<Pattern extends string, Entry> = Uncapitalize<ReplacePattern<Pattern, Entry>>;
|
|
30
|
-
/**
|
|
31
|
-
* Resolves every key in a testIds record:
|
|
32
|
-
* - string values → the key itself (static locator)
|
|
33
|
-
* - object values → the expanded dynamic key union
|
|
34
|
-
*/
|
|
35
|
-
type ResolvedTestIdKeys<I> = {
|
|
36
|
-
[K in keyof I & string]: I[K] extends string ? K : ExpandDynamicKey<K, I[K]>;
|
|
37
|
-
}[keyof I & string];
|
|
38
|
-
/**
|
|
39
|
-
* Validates all entries in a testIds or selectors record.
|
|
40
|
-
* Static (string) entries pass through. Dynamic entries are checked via ValidateDynamicEntryProperties.
|
|
41
|
-
*/
|
|
42
|
-
type ValidateTestIds<I, TargetKey extends 'testId' | 'selector'> = {
|
|
43
|
-
[K in keyof I & string]: I[K] extends string ? string : K extends `${string}{${string}}${string}` ? HasDuplicatePlaceholders<K> extends true ? "Error: Pattern contains duplicate placeholders" : ValidateDynamicEntryProperties<K, I[K], TargetKey> : I[K];
|
|
44
|
-
};
|
|
5
|
+
import { AllowedMethodKeys } from '../types/methods';
|
|
6
|
+
import { StrategyType, LocatorStrategyOptions } from '../types/strategies';
|
|
7
|
+
import { ResolvedTestIdKeys, ValidateTestIds } from './types/validation.js';
|
|
8
|
+
export { zeroArgMethodsList, oneArgMethodsList } from '../constants/methods';
|
|
9
|
+
export { rolesList, strategyList } from '../constants/strategies';
|
|
10
|
+
export { AllowedZeroArgMethods, AllowedOneArgMethods, AllowedMethodKeys } from '../types/methods';
|
|
11
|
+
export { RoleType, StrategyType, PlaywrightRoleOptions, PlaywrightTextOptions, LocatorStrategyOptions } from '../types/strategies';
|
|
12
|
+
export type GetStrategyOfKey<T, K extends string> = K extends (T extends {
|
|
13
|
+
testId: infer I;
|
|
14
|
+
} ? ResolvedTestIdKeys<I> : never) ? 'testId' : K extends (T extends {
|
|
15
|
+
testIds: infer I;
|
|
16
|
+
} ? ResolvedTestIdKeys<I> : never) ? 'testId' : K extends (T extends {
|
|
17
|
+
selector: infer S;
|
|
18
|
+
} ? ResolvedTestIdKeys<S> : never) ? 'selector' : K extends (T extends {
|
|
19
|
+
selectors: infer S;
|
|
20
|
+
} ? ResolvedTestIdKeys<S> : never) ? 'selector' : {
|
|
21
|
+
[S in Exclude<StrategyType, 'testId' | 'selector'>]: K extends (T extends Record<S, readonly string[]> ? T[S][number] : never) ? S : never;
|
|
22
|
+
}[Exclude<StrategyType, 'testId' | 'selector'>];
|
|
23
|
+
export type ModifyOptionsForTarget<T, Target, Options> = Options & {
|
|
24
|
+
nth?: number;
|
|
25
|
+
hasText?: string | RegExp;
|
|
26
|
+
} & (Target extends string ? LocatorStrategyOptions<GetStrategyOfKey<T, Target>> : {});
|
|
45
27
|
export type PageKeys<T> = (T extends {
|
|
46
|
-
|
|
47
|
-
} ? I extends Record<string, any> ? ResolvedTestIdKeys<I> : never : never) | (T extends {
|
|
48
|
-
|
|
49
|
-
} ?
|
|
28
|
+
testId: infer I;
|
|
29
|
+
} ? (I extends Record<string, any> ? ResolvedTestIdKeys<I> : never) : never) | (T extends {
|
|
30
|
+
testIds: infer I;
|
|
31
|
+
} ? (I extends Record<string, any> ? ResolvedTestIdKeys<I> : never) : never) | (T extends {
|
|
32
|
+
selector: infer S;
|
|
33
|
+
} ? (S extends Record<string, any> ? ResolvedTestIdKeys<S> : never) : never) | (T extends {
|
|
34
|
+
selectors: infer S;
|
|
35
|
+
} ? (S extends Record<string, any> ? ResolvedTestIdKeys<S> : never) : never) | {
|
|
36
|
+
[S in Exclude<StrategyType, 'testId' | 'selector'>]: T extends Record<S, readonly string[]> ? T[S][number] : never;
|
|
37
|
+
}[Exclude<StrategyType, 'testId' | 'selector'>];
|
|
38
|
+
export type CheckableLocatorKeys<T> = {
|
|
39
|
+
[S in 'checkbox' | 'radio']: T extends Record<S, readonly string[]> ? T[S][number] : never;
|
|
40
|
+
}['checkbox' | 'radio'];
|
|
41
|
+
export type CheckboxLocatorKeys<T> = T extends Record<'checkbox', readonly string[]> ? T['checkbox'][number] : never;
|
|
42
|
+
export type EditableLocatorKeys<T> = {
|
|
43
|
+
[S in 'label' | 'placeholder' | 'textbox' | 'searchbox']: T extends Record<S, readonly string[]> ? T[S][number] : never;
|
|
44
|
+
}['label' | 'placeholder' | 'textbox' | 'searchbox'];
|
|
45
|
+
export type SelectableLocatorKeys<T> = {
|
|
46
|
+
[S in 'combobox' | 'listbox' | 'option']: T extends Record<S, readonly string[]> ? T[S][number] : never;
|
|
47
|
+
}['combobox' | 'listbox' | 'option'];
|
|
50
48
|
export type TypedLocators<T> = {
|
|
51
49
|
[K in PageKeys<T>]: Locator;
|
|
52
50
|
};
|
|
53
|
-
export type
|
|
51
|
+
export type UniversalKeys<T> = (T extends {
|
|
52
|
+
testId: infer I;
|
|
53
|
+
} ? ResolvedTestIdKeys<I> : never) | (T extends {
|
|
54
|
+
testIds: infer I;
|
|
55
|
+
} ? ResolvedTestIdKeys<I> : never) | (T extends {
|
|
56
|
+
selector: infer S;
|
|
57
|
+
} ? ResolvedTestIdKeys<S> : never) | (T extends {
|
|
58
|
+
selectors: infer S;
|
|
59
|
+
} ? ResolvedTestIdKeys<S> : never);
|
|
60
|
+
export type SemanticKeys<T> = Exclude<PageKeys<T>, UniversalKeys<T>>;
|
|
61
|
+
export type ChainedKeys<T> = `${UniversalKeys<T>}.${PageKeys<T>}` | `${UniversalKeys<T>}.${SemanticKeys<T>}.${UniversalKeys<T>}` | `${UniversalKeys<T>}.${UniversalKeys<T>}.${PageKeys<T>}`;
|
|
54
62
|
export type TargetKey<T> = PageKeys<T> | ChainedKeys<T> | Locator;
|
|
55
|
-
export type ValidateTarget<K, T> = K extends Locator ? Locator : K extends
|
|
63
|
+
export type ValidateTarget<K, T> = K extends Locator ? Locator : K extends PageKeys<T> | ChainedKeys<T> ? K : never;
|
|
56
64
|
export declare function getOptionsArgumentIndex(methodName: AllowedMethodKeys): number;
|
|
57
|
-
/** Map type accepted by the `
|
|
65
|
+
/** Map type accepted by the `testId` property in a page config. */
|
|
58
66
|
export type TestIdMap = Record<string, string | DynamicTestIdEntry>;
|
|
59
67
|
export type SelectorMap = Record<string, string | DynamicSelectorEntry>;
|
|
60
68
|
export type PageConfig = {
|
|
61
69
|
url?: string;
|
|
70
|
+
testId?: TestIdMap;
|
|
71
|
+
selector?: SelectorMap;
|
|
62
72
|
testIds?: TestIdMap;
|
|
63
73
|
selectors?: SelectorMap;
|
|
64
|
-
|
|
74
|
+
} & {
|
|
75
|
+
[S in Exclude<StrategyType, 'testId' | 'selector'>]?: readonly string[];
|
|
65
76
|
};
|
|
77
|
+
export type KeysOfStrategy<T, S extends StrategyType> = S extends 'testId' ? T extends {
|
|
78
|
+
testId: infer I;
|
|
79
|
+
} ? ResolvedTestIdKeys<I> : T extends {
|
|
80
|
+
testIds: infer I;
|
|
81
|
+
} ? ResolvedTestIdKeys<I> : never : S extends 'selector' ? T extends {
|
|
82
|
+
selector: infer I;
|
|
83
|
+
} ? ResolvedTestIdKeys<I> : T extends {
|
|
84
|
+
selectors: infer S2;
|
|
85
|
+
} ? ResolvedTestIdKeys<S2> : never : T extends Record<S, infer A> ? A extends readonly string[] ? A[number] : never : never;
|
|
86
|
+
export type KeysOfOtherStrategies<T, S extends StrategyType> = {
|
|
87
|
+
[O in Exclude<StrategyType, S>]: KeysOfStrategy<T, O>;
|
|
88
|
+
}[Exclude<StrategyType, S>];
|
|
89
|
+
type GetTestIds<T> = T extends {
|
|
90
|
+
testId: infer I;
|
|
91
|
+
} ? (I extends Record<string, any> ? I : {}) : T extends {
|
|
92
|
+
testIds: infer I;
|
|
93
|
+
} ? (I extends Record<string, any> ? I : {}) : {};
|
|
94
|
+
type GetSelectors<T> = T extends {
|
|
95
|
+
selector: infer S;
|
|
96
|
+
} ? (S extends Record<string, any> ? S : {}) : T extends {
|
|
97
|
+
selectors: infer S;
|
|
98
|
+
} ? (S extends Record<string, any> ? S : {}) : {};
|
|
66
99
|
export type ValidatePageConfig<T> = {
|
|
67
100
|
url?: string;
|
|
101
|
+
testId?: T extends {
|
|
102
|
+
testId: infer I;
|
|
103
|
+
} ? I extends Record<string, any> ? ValidateTestIds<I, 'testId', GetSelectors<T>> & {
|
|
104
|
+
[K in keyof I]: K extends KeysOfOtherStrategies<T, 'testId'> ? {
|
|
105
|
+
[P in `Error: Duplicate key "${K & string}" is defined in multiple strategies`]: never;
|
|
106
|
+
} : any;
|
|
107
|
+
} : TestIdMap : TestIdMap;
|
|
68
108
|
testIds?: T extends {
|
|
69
109
|
testIds: infer I;
|
|
70
|
-
} ? I extends Record<string, any> ? ValidateTestIds<I, 'testId'
|
|
110
|
+
} ? I extends Record<string, any> ? ValidateTestIds<I, 'testId', GetSelectors<T>> & {
|
|
111
|
+
[K in keyof I]: K extends KeysOfOtherStrategies<T, 'testId'> ? {
|
|
112
|
+
[P in `Error: Duplicate key "${K & string}" is defined in multiple strategies`]: never;
|
|
113
|
+
} : any;
|
|
114
|
+
} : TestIdMap : TestIdMap;
|
|
115
|
+
selector?: T extends {
|
|
116
|
+
selector: infer S;
|
|
117
|
+
} ? S extends Record<string, any> ? ValidateTestIds<S, 'selector', GetTestIds<T>> & {
|
|
118
|
+
[K in keyof S]: K extends KeysOfOtherStrategies<T, 'selector'> ? {
|
|
119
|
+
[P in `Error: Duplicate key "${K & string}" is defined in multiple strategies`]: never;
|
|
120
|
+
} : any;
|
|
121
|
+
} : SelectorMap : SelectorMap;
|
|
71
122
|
selectors?: T extends {
|
|
72
|
-
testIds: infer I;
|
|
73
123
|
selectors: infer S;
|
|
74
|
-
} ? S extends Record<string, any> ? {
|
|
75
|
-
[K in keyof S]: K extends
|
|
124
|
+
} ? S extends Record<string, any> ? ValidateTestIds<S, 'selector', GetTestIds<T>> & {
|
|
125
|
+
[K in keyof S]: K extends KeysOfOtherStrategies<T, 'selector'> ? {
|
|
126
|
+
[P in `Error: Duplicate key "${K & string}" is defined in multiple strategies`]: never;
|
|
127
|
+
} : any;
|
|
76
128
|
} : SelectorMap : SelectorMap;
|
|
77
|
-
|
|
129
|
+
} & {
|
|
130
|
+
[S in Exclude<StrategyType, 'testId' | 'selector'>]?: T extends Record<S, infer A> ? A extends readonly string[] ? {
|
|
131
|
+
[I in keyof A]: A[I] extends string ? A[I] extends KeysOfOtherStrategies<T, S> ? `Error: Duplicate key "${A[I]}" is defined in multiple strategies` : A[I] : A[I];
|
|
132
|
+
} : readonly string[] : readonly string[];
|
|
78
133
|
} & {
|
|
79
134
|
[K in Exclude<keyof T, keyof PageConfig>]: never;
|
|
80
135
|
};
|
|
81
136
|
/**
|
|
82
137
|
* Creates a strongly-typed page configuration object containing URLs, testIds, and CSS selectors.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```ts
|
|
86
|
-
* import { createPageConfig } from 'pw-core/page';
|
|
87
|
-
*
|
|
88
|
-
* const config = createPageConfig({
|
|
89
|
-
* url: '/login',
|
|
90
|
-
* testIds: {
|
|
91
|
-
* username: 'username-input',
|
|
92
|
-
* password: 'password-input',
|
|
93
|
-
* submitBtn: 'login-button',
|
|
94
|
-
* },
|
|
95
|
-
* selectors: {
|
|
96
|
-
* errorAlert: '.alert-danger',
|
|
97
|
-
* }
|
|
98
|
-
* });
|
|
99
|
-
* ```
|
|
100
138
|
*/
|
|
101
139
|
export declare function createPageConfig<const T extends PageConfig>(config: [T] extends [ValidatePageConfig<T>] ? T : ValidatePageConfig<T>): T;
|
package/dist/page/config.js
CHANGED
|
@@ -1,45 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.oneArgMethodsList = exports.zeroArgMethodsList = void 0;
|
|
3
|
+
exports.strategyList = exports.rolesList = exports.oneArgMethodsList = exports.zeroArgMethodsList = void 0;
|
|
4
4
|
exports.getOptionsArgumentIndex = getOptionsArgumentIndex;
|
|
5
5
|
exports.createPageConfig = createPageConfig;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
exports.oneArgMethodsList = [
|
|
14
|
-
'fill', 'press', 'pressSequentially', 'selectOption', 'setInputFiles',
|
|
15
|
-
'getAttribute', 'dragTo'
|
|
16
|
-
];
|
|
6
|
+
const methods_1 = require("../constants/methods");
|
|
7
|
+
var methods_2 = require("../constants/methods");
|
|
8
|
+
Object.defineProperty(exports, "zeroArgMethodsList", { enumerable: true, get: function () { return methods_2.zeroArgMethodsList; } });
|
|
9
|
+
Object.defineProperty(exports, "oneArgMethodsList", { enumerable: true, get: function () { return methods_2.oneArgMethodsList; } });
|
|
10
|
+
var strategies_1 = require("../constants/strategies");
|
|
11
|
+
Object.defineProperty(exports, "rolesList", { enumerable: true, get: function () { return strategies_1.rolesList; } });
|
|
12
|
+
Object.defineProperty(exports, "strategyList", { enumerable: true, get: function () { return strategies_1.strategyList; } });
|
|
17
13
|
function getOptionsArgumentIndex(methodName) {
|
|
18
|
-
if (
|
|
14
|
+
if (methods_1.zeroArgMethodsList.includes(methodName))
|
|
19
15
|
return 0;
|
|
20
|
-
if (
|
|
16
|
+
if (methods_1.oneArgMethodsList.includes(methodName))
|
|
21
17
|
return 1;
|
|
22
18
|
return -1;
|
|
23
19
|
}
|
|
24
20
|
/**
|
|
25
21
|
* Creates a strongly-typed page configuration object containing URLs, testIds, and CSS selectors.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* import { createPageConfig } from 'pw-core/page';
|
|
30
|
-
*
|
|
31
|
-
* const config = createPageConfig({
|
|
32
|
-
* url: '/login',
|
|
33
|
-
* testIds: {
|
|
34
|
-
* username: 'username-input',
|
|
35
|
-
* password: 'password-input',
|
|
36
|
-
* submitBtn: 'login-button',
|
|
37
|
-
* },
|
|
38
|
-
* selectors: {
|
|
39
|
-
* errorAlert: '.alert-danger',
|
|
40
|
-
* }
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
22
|
*/
|
|
44
23
|
function createPageConfig(config) {
|
|
45
24
|
return config;
|
|
@@ -48,7 +48,7 @@ function capitalize(value) {
|
|
|
48
48
|
function cartesianProduct(arrays) {
|
|
49
49
|
if (arrays.length === 0)
|
|
50
50
|
return [[]];
|
|
51
|
-
return arrays.reduce((acc, arr) => acc.flatMap(combo => arr.map(val => [...combo, val])), [[]]);
|
|
51
|
+
return arrays.reduce((acc, arr) => acc.flatMap((combo) => arr.map((val) => [...combo, val])), [[]]);
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
54
|
* Extract placeholder names from a pattern like `"{status}{id}Chart"`.
|
|
@@ -88,24 +88,24 @@ function expandSingleDynamic(pattern, entry) {
|
|
|
88
88
|
}
|
|
89
89
|
// 1. No unknown keys (only placeholder names + targetKey)
|
|
90
90
|
const actualKeys = Object.keys(placeholders);
|
|
91
|
-
const unknownKeys = actualKeys.filter(k => !expectedNames.includes(k));
|
|
91
|
+
const unknownKeys = actualKeys.filter((k) => !expectedNames.includes(k));
|
|
92
92
|
if (unknownKeys.length > 0) {
|
|
93
93
|
throw new Error(`Dynamic locator "${pattern}": unknown key(s) [${unknownKeys.join(', ')}]. ` +
|
|
94
94
|
`Only '${targetKey}' and placeholder keys [${expectedNames.join(', ')}] are allowed.`);
|
|
95
95
|
}
|
|
96
96
|
// 2. All placeholders from the pattern must have a key in the entry
|
|
97
|
-
const missingKeys = expectedNames.filter(name => !(name in placeholders));
|
|
97
|
+
const missingKeys = expectedNames.filter((name) => !(name in placeholders));
|
|
98
98
|
if (missingKeys.length > 0) {
|
|
99
99
|
throw new Error(`Dynamic locator "${pattern}": missing placeholder key(s) [${missingKeys.join(', ')}]. ` +
|
|
100
100
|
`You must provide values for all placeholders defined in the pattern.`);
|
|
101
101
|
}
|
|
102
102
|
// 2.5. Placeholder values must be arrays
|
|
103
|
-
const nonArrayKeys = expectedNames.filter(name => !Array.isArray(placeholders[name]));
|
|
103
|
+
const nonArrayKeys = expectedNames.filter((name) => !Array.isArray(placeholders[name]));
|
|
104
104
|
if (nonArrayKeys.length > 0) {
|
|
105
105
|
throw new Error(`Dynamic locator "${pattern}": placeholder key(s) [${nonArrayKeys.join(', ')}] must be arrays.`);
|
|
106
106
|
}
|
|
107
107
|
// 3. All placeholder names must appear as substrings in the target pattern value
|
|
108
|
-
const missingInPattern = expectedNames.filter(name => !targetPattern.includes(name));
|
|
108
|
+
const missingInPattern = expectedNames.filter((name) => !targetPattern.includes(name));
|
|
109
109
|
if (missingInPattern.length > 0) {
|
|
110
110
|
throw new Error(`Dynamic locator "${pattern}": ${targetKey} "${targetPattern}" does not contain ` +
|
|
111
111
|
`placeholder name(s) [${missingInPattern.join(', ')}]. ` +
|
|
@@ -114,7 +114,7 @@ function expandSingleDynamic(pattern, entry) {
|
|
|
114
114
|
// ─── Expansion ───────────────────────────────────────────────────────
|
|
115
115
|
// Sort placeholder names longest-first to avoid substring replacement issues
|
|
116
116
|
const names = Object.keys(placeholders).sort((a, b) => b.length - a.length);
|
|
117
|
-
const values = names.map(name => {
|
|
117
|
+
const values = names.map((name) => {
|
|
118
118
|
const v = placeholders[name];
|
|
119
119
|
return v;
|
|
120
120
|
});
|
|
@@ -123,11 +123,22 @@ function expandSingleDynamic(pattern, entry) {
|
|
|
123
123
|
// Build the *key* — replace {name} with Capitalize(value), then uncapitalize the first char
|
|
124
124
|
let expandedKey = pattern;
|
|
125
125
|
let expandedValue = targetPattern;
|
|
126
|
+
const isPurePlaceholder = pattern === `{${names[0]}}` && names.length === 1;
|
|
126
127
|
for (let i = 0; i < names.length; i++) {
|
|
127
128
|
const name = names[i];
|
|
128
129
|
const val = combo[i];
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
let capValue = '';
|
|
131
|
+
if (isPurePlaceholder) {
|
|
132
|
+
capValue = val;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const words = val.split(/[^a-zA-Z0-9]/).filter(Boolean);
|
|
136
|
+
capValue = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
137
|
+
}
|
|
138
|
+
expandedKey = expandedKey.replace(`{${name}}`, capValue);
|
|
139
|
+
const replacement = targetKey === 'selector' ? val : val.toLowerCase();
|
|
140
|
+
expandedValue = expandedValue.replaceAll(`{${name}}`, replacement);
|
|
141
|
+
expandedValue = expandedValue.replaceAll(name, replacement);
|
|
131
142
|
}
|
|
132
143
|
// camelCase: uncapitalize the first character
|
|
133
144
|
expandedKey = expandedKey.charAt(0).toLowerCase() + expandedKey.slice(1);
|
|
@@ -143,10 +154,19 @@ function expandDynamicLocators(testIds) {
|
|
|
143
154
|
const result = {};
|
|
144
155
|
for (const [key, value] of Object.entries(testIds)) {
|
|
145
156
|
if (typeof value === 'string') {
|
|
157
|
+
if (key in result) {
|
|
158
|
+
throw new Error(`Duplicate key "${key}" defined in page object configuration.`);
|
|
159
|
+
}
|
|
146
160
|
result[key] = value;
|
|
147
161
|
}
|
|
148
162
|
else {
|
|
149
|
-
|
|
163
|
+
const expanded = expandSingleDynamic(key, value);
|
|
164
|
+
for (const k of Object.keys(expanded)) {
|
|
165
|
+
if (k in result) {
|
|
166
|
+
throw new Error(`Duplicate key "${k}" defined in page object configuration.`);
|
|
167
|
+
}
|
|
168
|
+
result[k] = expanded[k];
|
|
169
|
+
}
|
|
150
170
|
}
|
|
151
171
|
}
|
|
152
172
|
return result;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { Page, Locator } from '@playwright/test';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
export declare const locatorStrategies: Record<string, {
|
|
3
|
+
resolver: string;
|
|
4
|
+
role?: string;
|
|
5
|
+
}>;
|
|
6
|
+
export interface LocatorMetadata {
|
|
7
|
+
strategy: string;
|
|
8
|
+
value: any;
|
|
9
|
+
}
|
|
10
|
+
export declare function cleanKey(s: string): string;
|
|
11
|
+
export declare const lookupIndexCache: WeakMap<object, Map<string, LocatorMetadata>>;
|
|
12
|
+
export declare function buildLookupIndex(config: any): Map<string, LocatorMetadata>;
|
|
13
|
+
export declare function defineLocators(instance: any, context: Page | Locator, config: any): void;
|
|
14
|
+
export declare function resolveLocator(context: Page | Locator, config: any, target: any, options?: {
|
|
12
15
|
nth?: number;
|
|
13
16
|
raw?: boolean;
|
|
14
17
|
hasText?: string | RegExp;
|
|
15
|
-
}): Locator;
|
|
18
|
+
} & Record<string, any>): Locator;
|
|
16
19
|
/**
|
|
17
20
|
* Resolve a config key to a proxied Locator with step-wrapped Playwright methods.
|
|
18
21
|
*/
|
|
19
|
-
export declare function locator<
|
|
20
|
-
testIds?: Record<string, string | DynamicLocatorEntry>;
|
|
21
|
-
selectors?: Record<string, string | DynamicSelectorEntry>;
|
|
22
|
-
}>(context: Page | Locator, config: T, target: PageKeys<T> | ChainedKeys<T> | Locator, options?: Parameters<Locator['filter']>[0] & {
|
|
22
|
+
export declare function locator(context: Page | Locator, config: any, target: any, options?: Parameters<Locator['filter']>[0] & {
|
|
23
23
|
nth?: number;
|
|
24
24
|
}): Locator;
|
|
25
25
|
export declare function wrapLocatorWithProxy(loc: Locator): Locator;
|