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.
Files changed (86) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +777 -0
  4. package/dist/codegen/config/context-weights.d.ts +8 -0
  5. package/dist/codegen/config/context-weights.js +11 -0
  6. package/dist/codegen/config/ignored-attributes.d.ts +1 -0
  7. package/dist/codegen/config/ignored-attributes.js +4 -0
  8. package/dist/codegen/config/ignored-classes.d.ts +1 -0
  9. package/dist/codegen/config/ignored-classes.js +144 -0
  10. package/dist/codegen/config/ignored-values.d.ts +1 -0
  11. package/dist/codegen/config/ignored-values.js +4 -0
  12. package/dist/codegen/config/index.d.ts +29 -0
  13. package/dist/codegen/config/index.js +17 -0
  14. package/dist/codegen/config/strategy-order.d.ts +1 -0
  15. package/dist/codegen/config/strategy-order.js +18 -0
  16. package/dist/codegen/config/weights.d.ts +15 -0
  17. package/dist/codegen/config/weights.js +18 -0
  18. package/dist/codegen/floating-panel/client.d.ts +1 -0
  19. package/dist/codegen/floating-panel/client.js +248 -0
  20. package/dist/codegen/floating-panel/index.d.ts +4 -0
  21. package/dist/codegen/floating-panel/index.js +20 -0
  22. package/dist/codegen/floating-panel/manager.d.ts +19 -0
  23. package/dist/codegen/floating-panel/manager.js +90 -0
  24. package/dist/codegen/floating-panel/template.d.ts +2 -0
  25. package/dist/codegen/floating-panel/template.js +58 -0
  26. package/dist/codegen/floating-panel/types.d.ts +7 -0
  27. package/dist/codegen/floating-panel/types.js +2 -0
  28. package/dist/codegen/generator/action.validator.d.ts +5 -0
  29. package/dist/codegen/generator/action.validator.js +27 -0
  30. package/dist/codegen/generator/index.d.ts +13 -0
  31. package/dist/codegen/generator/index.js +724 -0
  32. package/dist/codegen/generator/locator.restricted.d.ts +36 -0
  33. package/dist/codegen/generator/locator.restricted.js +127 -0
  34. package/dist/codegen/generator/locator.validator.d.ts +8 -0
  35. package/dist/codegen/generator/locator.validator.js +38 -0
  36. package/dist/codegen/hover-tracker/client.d.ts +1 -0
  37. package/dist/codegen/hover-tracker/client.js +75 -0
  38. package/dist/codegen/hover-tracker/index.d.ts +3 -0
  39. package/dist/codegen/hover-tracker/index.js +19 -0
  40. package/dist/codegen/hover-tracker/manager.d.ts +11 -0
  41. package/dist/codegen/hover-tracker/manager.js +28 -0
  42. package/dist/codegen/hover-tracker/types.d.ts +3 -0
  43. package/dist/codegen/hover-tracker/types.js +2 -0
  44. package/dist/codegen/index.d.ts +35 -0
  45. package/dist/codegen/index.js +1147 -0
  46. package/dist/codegen/key-utils.d.ts +23 -0
  47. package/dist/codegen/key-utils.js +68 -0
  48. package/dist/codegen/scorer/base-score.d.ts +1 -0
  49. package/dist/codegen/scorer/base-score.js +7 -0
  50. package/dist/codegen/scorer/context-score.d.ts +2 -0
  51. package/dist/codegen/scorer/context-score.js +27 -0
  52. package/dist/codegen/scorer/index.d.ts +5 -0
  53. package/dist/codegen/scorer/index.js +23 -0
  54. package/dist/codegen/scorer/locator.ranking.d.ts +15 -0
  55. package/dist/codegen/scorer/locator.ranking.js +83 -0
  56. package/dist/codegen/scorer/semantic-score.d.ts +1 -0
  57. package/dist/codegen/scorer/semantic-score.js +47 -0
  58. package/dist/codegen/types.d.ts +35 -0
  59. package/dist/codegen/types.js +2 -0
  60. package/dist/component/table.js +14 -10
  61. package/dist/constants/methods.d.ts +2 -0
  62. package/dist/constants/methods.js +33 -0
  63. package/dist/constants/strategies.d.ts +2 -0
  64. package/dist/constants/strategies.js +47 -0
  65. package/dist/page/actions/locator-actions.js +15 -4
  66. package/dist/page/assertions/verify-chain.d.ts +39 -32
  67. package/dist/page/assertions/verify-chain.js +18 -4
  68. package/dist/page/assertions/verify-helpers.js +12 -3
  69. package/dist/page/config.d.ts +109 -71
  70. package/dist/page/config.js +10 -31
  71. package/dist/page/locators/dynamic-locator-resolver.js +29 -9
  72. package/dist/page/locators/resolver.d.ts +15 -15
  73. package/dist/page/locators/resolver.js +151 -32
  74. package/dist/page/registry.d.ts +43 -6
  75. package/dist/page/registry.js +90 -18
  76. package/dist/page/typed-page.d.ts +33 -29
  77. package/dist/page/typed-page.js +40 -11
  78. package/dist/page/types/proxy-methods.d.ts +69 -54
  79. package/dist/page/types/validation.d.ts +48 -1
  80. package/dist/page/utils/caller-location.js +13 -9
  81. package/dist/page/utils/formatter.js +6 -3
  82. package/dist/types/methods.d.ts +4 -0
  83. package/dist/types/methods.js +2 -0
  84. package/dist/types/strategies.d.ts +17 -0
  85. package/dist/types/strategies.js +2 -0
  86. package/package.json +8 -1
@@ -1,5 +1,5 @@
1
1
  import { Locator, expect as playwrightExpect } from '@playwright/test';
2
- import { ChainedKeys, PageKeys } from '../config';
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?] ? [Args[0], (Exclude<Args[1], undefined> & {
15
- nth?: number;
16
- hasText?: string | RegExp;
17
- message?: string;
18
- })?] : Args extends [any?] ? Exclude<Args[0], undefined> extends object ? [(Exclude<Args[0], undefined> & {
19
- nth?: number;
20
- hasText?: string | RegExp;
21
- message?: string;
22
- })?] : [Exclude<Args[0], undefined>, options?: {
23
- nth?: number;
24
- hasText?: string | RegExp;
25
- message?: string;
26
- }] : [options?: {
27
- nth?: number;
28
- hasText?: string | RegExp;
29
- message?: string;
30
- }];
31
- type DynamicallyModifiedMatchers<T> = {
32
- [K in keyof PlaywrightLocatorMatchers]: K extends 'not' ? Omit<VerifyMatchers<T>, 'not'> : PlaywrightLocatorMatchers[K] extends (...args: infer Args) => any ? (...args: ModifyMatcherArgs<Args>) => Promise<void> : PlaywrightLocatorMatchers[K];
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> = (target: PageKeys<T> | ChainedKeys<T> | Locator, options?: VerifyOptions) => VerifyMatchers<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> | ChainedKeys<T> | Locator, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeHidden']>[0] & {
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> | ChainedKeys<T> | Locator, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeEnabled']>[0] & {
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> | ChainedKeys<T> | Locator, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeDisabled']>[0] & {
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> | ChainedKeys<T> | Locator, message?: string): ReturnType<typeof playwrightExpect<Locator>>;
62
- locator(target: PageKeys<T> | ChainedKeys<T> | Locator, options?: Parameters<Locator['filter']>[0] & {
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 extends {
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> | ChainedKeys<T> | Locator, verifyOptions: VerifyOptions | undefined, isSoft: boolean): VerifyMatchers<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(['then', 'catch', 'finally', 'bind', 'call', 'apply', 'toString', 'valueOf', 'toLocaleString']);
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 = (options && 'nth' in options) ? options.nth : defaultNth;
49
- const hasText = (options && 'hasText' in options) ? options.hasText : defaultHasText;
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, { nth, hasText, raw: isHaveCount });
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, { nth: options?.nth, hasText: options?.hasText });
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, { nth: options?.nth, hasText: options?.hasText });
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, { nth: options?.nth, hasText: options?.hasText });
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
  }
@@ -1,101 +1,139 @@
1
- import { Locator, Page } from '@playwright/test';
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
- export declare const zeroArgMethodsList: readonly ["click", "dblclick", "hover", "focus", "blur", "check", "uncheck", "clear", "waitFor", "isChecked", "isDisabled", "isVisible", "textContent", "innerText", "allInnerTexts", "allTextContents", "count", "scrollIntoViewIfNeeded", "boundingBox"];
6
- export declare const oneArgMethodsList: readonly ["fill", "press", "pressSequentially", "selectOption", "setInputFiles", "getAttribute", "dragTo"];
7
- export type AllowedZeroArgMethods = typeof zeroArgMethodsList[number];
8
- export type AllowedOneArgMethods = typeof oneArgMethodsList[number];
9
- export type AllowedMethodKeys = AllowedZeroArgMethods | AllowedOneArgMethods;
10
- import { ValidateDynamicEntryProperties, HasDuplicatePlaceholders } from './types/validation.js';
11
- /**
12
- * Replaces every `{Name}` placeholder in `Pattern` with `Capitalize<Value>`
13
- * where `Value` is drawn from the corresponding array in `Entry`.
14
- * Template-literal unions distribute automatically, producing the full cartesian product.
15
- */
16
- type ReplacePattern<Pattern extends string, Entry> = Pattern extends `${infer Before}{${infer Name}}${infer After}` ? Name extends keyof Entry ? Entry[Name] extends readonly string[] ? `${Before}${Capitalize<Entry[Name][number]>}${ReplacePattern<After, Entry> & string}` : never : never : Pattern;
17
- /**
18
- * Expands a dynamic key pattern into the union of all concrete (camelCased) keys.
19
- *
20
- * @example
21
- * ```ts
22
- * type Keys = ExpandDynamicKey<
23
- * "{status}{id}Chart",
24
- * { status: readonly ['active', 'inactive']; id: readonly ['line', 'bar']; testId: "status-id-chart" }
25
- * >;
26
- * // => "activeLineChart" | "activeBarChart" | "inactiveLineChart" | "inactiveBarChart"
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
- testIds?: infer I;
47
- } ? I extends Record<string, any> ? ResolvedTestIdKeys<I> : never : never) | (T extends {
48
- selectors?: infer S;
49
- } ? S extends Record<string, any> ? ResolvedTestIdKeys<S> : never : never);
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 ChainedKeys<T> = `${PageKeys<T>}.${PageKeys<T>}`;
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 (PageKeys<T> | ChainedKeys<T>) ? K : never;
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 `testIds` property in a page config. */
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
- Class?: new (page: Page, config?: any) => any;
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'> : TestIdMap : TestIdMap;
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 keyof I ? `Duplicate key: ${K & string} already exists in testIds` : ValidateTestIds<S, 'selector'>[K & keyof S & string];
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
- Class?: new (page: Page, config?: any) => any;
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;
@@ -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
- exports.zeroArgMethodsList = [
7
- 'click', 'dblclick', 'hover', 'focus', 'blur', 'check', 'uncheck', 'clear',
8
- 'waitFor', 'isChecked', 'isDisabled',
9
- 'isVisible', 'textContent', 'innerText',
10
- 'allInnerTexts', 'allTextContents', 'count',
11
- 'scrollIntoViewIfNeeded', 'boundingBox'
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 (exports.zeroArgMethodsList.includes(methodName))
14
+ if (methods_1.zeroArgMethodsList.includes(methodName))
19
15
  return 0;
20
- if (exports.oneArgMethodsList.includes(methodName))
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
- expandedKey = expandedKey.replace(`{${name}}`, capitalize(val));
130
- expandedValue = expandedValue.replaceAll(name, targetKey === 'selector' ? val : val.toLowerCase());
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
- Object.assign(result, expandSingleDynamic(key, value));
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
- import { ChainedKeys, PageKeys, DynamicSelectorEntry } from '../config';
3
- import type { DynamicLocatorEntry } from './dynamic-locator-resolver';
4
- export declare function defineLocators<T extends {
5
- testIds?: Record<string, string | DynamicLocatorEntry>;
6
- selectors?: Record<string, string | DynamicSelectorEntry>;
7
- }>(instance: any, context: Page | Locator, config: T): void;
8
- export declare function resolveLocator<T extends {
9
- testIds?: Record<string, string | DynamicLocatorEntry>;
10
- selectors?: Record<string, string | DynamicSelectorEntry>;
11
- }>(context: Page | Locator, config: T, target: PageKeys<T> | ChainedKeys<T> | Locator, options?: {
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<T extends {
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;