pw-core 1.1.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 (96) 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 +19 -14
  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/helpers.d.ts +1 -1
  66. package/dist/helpers.js +21 -9
  67. package/dist/page/actions/locator-actions.js +46 -2
  68. package/dist/page/actions/page-actions.d.ts +11 -6
  69. package/dist/page/actions/page-actions.js +12 -12
  70. package/dist/page/assertions/verify-chain.d.ts +39 -32
  71. package/dist/page/assertions/verify-chain.js +21 -6
  72. package/dist/page/assertions/verify-helpers.d.ts +9 -3
  73. package/dist/page/assertions/verify-helpers.js +18 -9
  74. package/dist/page/config.d.ts +126 -38
  75. package/dist/page/config.js +10 -31
  76. package/dist/page/index.d.ts +1 -0
  77. package/dist/page/index.js +1 -0
  78. package/dist/page/locators/dynamic-locator-resolver.d.ts +49 -0
  79. package/dist/page/locators/dynamic-locator-resolver.js +187 -0
  80. package/dist/page/locators/resolver.d.ts +15 -14
  81. package/dist/page/locators/resolver.js +187 -30
  82. package/dist/page/registry.d.ts +51 -12
  83. package/dist/page/registry.js +90 -18
  84. package/dist/page/typed-page.d.ts +33 -28
  85. package/dist/page/typed-page.js +89 -12
  86. package/dist/page/types/proxy-methods.d.ts +69 -54
  87. package/dist/page/types/validation.d.ts +69 -0
  88. package/dist/page/types/validation.js +2 -0
  89. package/dist/page/utils/caller-location.d.ts +5 -0
  90. package/dist/page/utils/caller-location.js +82 -0
  91. package/dist/page/utils/formatter.js +14 -4
  92. package/dist/types/methods.d.ts +4 -0
  93. package/dist/types/methods.js +2 -0
  94. package/dist/types/strategies.d.ts +17 -0
  95. package/dist/types/strategies.js +2 -0
  96. package/package.json +8 -1
@@ -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;
@@ -5,6 +5,7 @@ export * from './assertions/verify-helpers';
5
5
  export * from './assertions/expect';
6
6
  export * from './actions/page-actions';
7
7
  export * from './actions/locator-actions';
8
+ export * from './locators/dynamic-locator-resolver';
8
9
  export * from './locators/resolver';
9
10
  export * from './typed-page';
10
11
  export * from './registry';
@@ -21,6 +21,7 @@ __exportStar(require("./assertions/verify-helpers"), exports);
21
21
  __exportStar(require("./assertions/expect"), exports);
22
22
  __exportStar(require("./actions/page-actions"), exports);
23
23
  __exportStar(require("./actions/locator-actions"), exports);
24
+ __exportStar(require("./locators/dynamic-locator-resolver"), exports);
24
25
  __exportStar(require("./locators/resolver"), exports);
25
26
  __exportStar(require("./typed-page"), exports);
26
27
  __exportStar(require("./registry"), exports);
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Dynamic locator resolver — expands `{placeholder}` patterns in testId keys
3
+ * into all value combinations at runtime.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const testIds = {
8
+ * staticKey: 'static-test-id',
9
+ * "{status}{id}Chart": {
10
+ * id: ['line', 'bar'],
11
+ * status: ['active', 'inactive'],
12
+ * testId: "status-id-chart"
13
+ * }
14
+ * };
15
+ *
16
+ * expandDynamicLocators(testIds);
17
+ * // => {
18
+ * // staticKey: 'static-test-id',
19
+ * // activeLineChart: 'active-line-chart',
20
+ * // activeBarChart: 'active-bar-chart',
21
+ * // inactiveLineChart: 'inactive-line-chart',
22
+ * // inactiveBarChart: 'inactive-bar-chart'
23
+ * // }
24
+ * ```
25
+ */
26
+ /**
27
+ * Shape of a dynamic testId entry.
28
+ *
29
+ * `testId` — the test-id pattern with placeholder *names* as literal substrings.
30
+ * Every other property is a placeholder name mapped to its list of allowed values.
31
+ */
32
+ export type DynamicTestIdEntry = {
33
+ testId?: string;
34
+ [placeholder: string]: string | readonly string[] | undefined;
35
+ };
36
+ export type DynamicSelectorEntry = {
37
+ selector?: string;
38
+ [placeholder: string]: string | readonly string[] | undefined;
39
+ };
40
+ export type DynamicLocatorEntry = DynamicTestIdEntry | DynamicSelectorEntry;
41
+ /** Returns true when `key` contains at least one `{…}` placeholder. */
42
+ export declare function isDynamicKey(key: string): boolean;
43
+ /**
44
+ * Expands all dynamic entries in a `testIds` map into a flat `key → testId` map.
45
+ * Static (string-valued) entries pass through unchanged.
46
+ */
47
+ export declare function expandDynamicLocators(testIds: Record<string, string | DynamicLocatorEntry>): Record<string, string>;
48
+ /** Return the expanded (flat) testIds map, caching the result per config object. */
49
+ export declare function getExpandedTestIds(testIds: Record<string, string | DynamicLocatorEntry>): Record<string, string>;
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * Dynamic locator resolver — expands `{placeholder}` patterns in testId keys
4
+ * into all value combinations at runtime.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const testIds = {
9
+ * staticKey: 'static-test-id',
10
+ * "{status}{id}Chart": {
11
+ * id: ['line', 'bar'],
12
+ * status: ['active', 'inactive'],
13
+ * testId: "status-id-chart"
14
+ * }
15
+ * };
16
+ *
17
+ * expandDynamicLocators(testIds);
18
+ * // => {
19
+ * // staticKey: 'static-test-id',
20
+ * // activeLineChart: 'active-line-chart',
21
+ * // activeBarChart: 'active-bar-chart',
22
+ * // inactiveLineChart: 'inactive-line-chart',
23
+ * // inactiveBarChart: 'inactive-bar-chart'
24
+ * // }
25
+ * ```
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.isDynamicKey = isDynamicKey;
29
+ exports.expandDynamicLocators = expandDynamicLocators;
30
+ exports.getExpandedTestIds = getExpandedTestIds;
31
+ /** Returns true when `key` contains at least one `{…}` placeholder. */
32
+ function isDynamicKey(key) {
33
+ return /\{[^}]+\}/.test(key);
34
+ }
35
+ /**
36
+ * Capitalize the first character of a string.
37
+ * @internal
38
+ */
39
+ function capitalize(value) {
40
+ return value.charAt(0).toUpperCase() + value.slice(1);
41
+ }
42
+ /**
43
+ * Compute the cartesian product of an array-of-arrays.
44
+ *
45
+ * @example cartesianProduct([['a','b'],['1','2']]) // [['a','1'],['a','2'],['b','1'],['b','2']]
46
+ * @internal
47
+ */
48
+ function cartesianProduct(arrays) {
49
+ if (arrays.length === 0)
50
+ return [[]];
51
+ return arrays.reduce((acc, arr) => acc.flatMap((combo) => arr.map((val) => [...combo, val])), [[]]);
52
+ }
53
+ /**
54
+ * Extract placeholder names from a pattern like `"{status}{id}Chart"`.
55
+ * @internal
56
+ */
57
+ function extractPlaceholders(pattern) {
58
+ const names = [];
59
+ const re = /\{([^}]+)\}/g;
60
+ let m;
61
+ while ((m = re.exec(pattern)) !== null) {
62
+ names.push(m[1]);
63
+ }
64
+ return names;
65
+ }
66
+ /**
67
+ * Expand a single dynamic key pattern + entry into flat key → locator-value pairs.
68
+ * Validates the entry at runtime before expansion.
69
+ * @internal
70
+ */
71
+ function expandSingleDynamic(pattern, entry) {
72
+ const testIdPattern = entry.testId;
73
+ const selectorPattern = entry.selector;
74
+ const targetPattern = (testIdPattern !== undefined ? testIdPattern : selectorPattern);
75
+ const targetKey = testIdPattern !== undefined ? 'testId' : 'selector';
76
+ if (targetPattern === undefined) {
77
+ throw new Error(`Dynamic locator "${pattern}": entry must contain either 'testId' or 'selector' property.`);
78
+ }
79
+ const placeholders = { ...entry };
80
+ delete placeholders.testId;
81
+ delete placeholders.selector;
82
+ // ─── Validation ──────────────────────────────────────────────────────
83
+ const expectedNames = extractPlaceholders(pattern);
84
+ // 0.5. No duplicate placeholders in the pattern
85
+ const uniqueNames = Array.from(new Set(expectedNames));
86
+ if (uniqueNames.length !== expectedNames.length) {
87
+ throw new Error(`Dynamic locator "${pattern}": pattern has duplicate placeholders.`);
88
+ }
89
+ // 1. No unknown keys (only placeholder names + targetKey)
90
+ const actualKeys = Object.keys(placeholders);
91
+ const unknownKeys = actualKeys.filter((k) => !expectedNames.includes(k));
92
+ if (unknownKeys.length > 0) {
93
+ throw new Error(`Dynamic locator "${pattern}": unknown key(s) [${unknownKeys.join(', ')}]. ` +
94
+ `Only '${targetKey}' and placeholder keys [${expectedNames.join(', ')}] are allowed.`);
95
+ }
96
+ // 2. All placeholders from the pattern must have a key in the entry
97
+ const missingKeys = expectedNames.filter((name) => !(name in placeholders));
98
+ if (missingKeys.length > 0) {
99
+ throw new Error(`Dynamic locator "${pattern}": missing placeholder key(s) [${missingKeys.join(', ')}]. ` +
100
+ `You must provide values for all placeholders defined in the pattern.`);
101
+ }
102
+ // 2.5. Placeholder values must be arrays
103
+ const nonArrayKeys = expectedNames.filter((name) => !Array.isArray(placeholders[name]));
104
+ if (nonArrayKeys.length > 0) {
105
+ throw new Error(`Dynamic locator "${pattern}": placeholder key(s) [${nonArrayKeys.join(', ')}] must be arrays.`);
106
+ }
107
+ // 3. All placeholder names must appear as substrings in the target pattern value
108
+ const missingInPattern = expectedNames.filter((name) => !targetPattern.includes(name));
109
+ if (missingInPattern.length > 0) {
110
+ throw new Error(`Dynamic locator "${pattern}": ${targetKey} "${targetPattern}" does not contain ` +
111
+ `placeholder name(s) [${missingInPattern.join(', ')}]. ` +
112
+ `All placeholder names must appear in the ${targetKey} so they can be replaced at runtime.`);
113
+ }
114
+ // ─── Expansion ───────────────────────────────────────────────────────
115
+ // Sort placeholder names longest-first to avoid substring replacement issues
116
+ const names = Object.keys(placeholders).sort((a, b) => b.length - a.length);
117
+ const values = names.map((name) => {
118
+ const v = placeholders[name];
119
+ return v;
120
+ });
121
+ const result = {};
122
+ for (const combo of cartesianProduct(values)) {
123
+ // Build the *key* — replace {name} with Capitalize(value), then uncapitalize the first char
124
+ let expandedKey = pattern;
125
+ let expandedValue = targetPattern;
126
+ const isPurePlaceholder = pattern === `{${names[0]}}` && names.length === 1;
127
+ for (let i = 0; i < names.length; i++) {
128
+ const name = names[i];
129
+ const val = combo[i];
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);
142
+ }
143
+ // camelCase: uncapitalize the first character
144
+ expandedKey = expandedKey.charAt(0).toLowerCase() + expandedKey.slice(1);
145
+ result[expandedKey] = expandedValue;
146
+ }
147
+ return result;
148
+ }
149
+ /**
150
+ * Expands all dynamic entries in a `testIds` map into a flat `key → testId` map.
151
+ * Static (string-valued) entries pass through unchanged.
152
+ */
153
+ function expandDynamicLocators(testIds) {
154
+ const result = {};
155
+ for (const [key, value] of Object.entries(testIds)) {
156
+ if (typeof value === 'string') {
157
+ if (key in result) {
158
+ throw new Error(`Duplicate key "${key}" defined in page object configuration.`);
159
+ }
160
+ result[key] = value;
161
+ }
162
+ else {
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
+ }
170
+ }
171
+ }
172
+ return result;
173
+ }
174
+ /**
175
+ * Cached expansion — avoids re-expanding the same config on every action call.
176
+ * @internal
177
+ */
178
+ const expandedCache = new WeakMap();
179
+ /** Return the expanded (flat) testIds map, caching the result per config object. */
180
+ function getExpandedTestIds(testIds) {
181
+ let expanded = expandedCache.get(testIds);
182
+ if (!expanded) {
183
+ expanded = expandDynamicLocators(testIds);
184
+ expandedCache.set(testIds, expanded);
185
+ }
186
+ return expanded;
187
+ }
@@ -1,24 +1,25 @@
1
1
  import { Page, Locator } from '@playwright/test';
2
- import { ChainedKeys, PageKeys } from '../config';
3
- export declare function defineLocators<T extends {
4
- testIds?: Record<string, string>;
5
- selectors?: Record<string, string>;
6
- }>(instance: any, context: Page | Locator, config: T): void;
7
- export declare function resolveLocator<T extends {
8
- testIds?: Record<string, string>;
9
- selectors?: Record<string, string>;
10
- }>(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?: {
11
15
  nth?: number;
12
16
  raw?: boolean;
13
17
  hasText?: string | RegExp;
14
- }): Locator;
18
+ } & Record<string, any>): Locator;
15
19
  /**
16
20
  * Resolve a config key to a proxied Locator with step-wrapped Playwright methods.
17
21
  */
18
- export declare function locator<T extends {
19
- testIds?: Record<string, string>;
20
- selectors?: Record<string, string>;
21
- }>(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] & {
22
23
  nth?: number;
23
24
  }): Locator;
24
25
  export declare function wrapLocatorWithProxy(loc: Locator): Locator;
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lookupIndexCache = exports.locatorStrategies = void 0;
4
+ exports.cleanKey = cleanKey;
5
+ exports.buildLookupIndex = buildLookupIndex;
3
6
  exports.defineLocators = defineLocators;
4
7
  exports.resolveLocator = resolveLocator;
5
8
  exports.locator = locator;
@@ -7,47 +10,164 @@ exports.wrapLocatorWithProxy = wrapLocatorWithProxy;
7
10
  const test_1 = require("@playwright/test");
8
11
  const config_1 = require("../config");
9
12
  const formatter_1 = require("../utils/formatter");
10
- function defineLocators(instance, context, config) {
11
- if (config.testIds) {
12
- for (const key of Object.keys(config.testIds)) {
13
- Object.defineProperty(instance, key, {
14
- get: () => {
15
- return context.getByTestId(config.testIds[key]);
16
- },
17
- enumerable: true,
18
- configurable: true,
19
- });
13
+ const dynamic_locator_resolver_1 = require("./dynamic-locator-resolver");
14
+ const caller_location_1 = require("../utils/caller-location");
15
+ exports.locatorStrategies = {
16
+ testId: { resolver: 'getByTestId' },
17
+ selector: { resolver: 'locator' },
18
+ text: { resolver: 'getByText' },
19
+ label: { resolver: 'getByLabel' },
20
+ title: { resolver: 'getByTitle' },
21
+ placeholder: { resolver: 'getByPlaceholder' },
22
+ altText: { resolver: 'getByAltText' }
23
+ };
24
+ // Add all roles dynamically
25
+ for (const role of config_1.rolesList) {
26
+ exports.locatorStrategies[role] = { resolver: 'getByRole', role };
27
+ }
28
+ function cleanKey(s) {
29
+ let clean = s;
30
+ const suffixes = ['Btn', 'Button', 'Link', 'Input', 'Checkbox', 'Option', 'Title', 'Label', 'Value', 'Text', 'Wrapper', 'Container', 'Card', 'Item'];
31
+ for (const suffix of suffixes) {
32
+ clean = clean.replace(new RegExp(`[-_]?${suffix}$`, 'i'), '');
33
+ }
34
+ clean = clean.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
35
+ clean = clean.replace(/^\d+/, '').replace(/\d+$/, '');
36
+ return clean;
37
+ }
38
+ exports.lookupIndexCache = new WeakMap();
39
+ function buildLookupIndex(config) {
40
+ const index = new Map();
41
+ const seenInStrategies = new Map();
42
+ const addKey = (key, strategy, value) => {
43
+ const cleanedKey = cleanKey(key);
44
+ if (seenInStrategies.has(cleanedKey)) {
45
+ const existing = seenInStrategies.get(cleanedKey);
46
+ if (!existing.includes(strategy)) {
47
+ existing.push(strategy);
48
+ }
49
+ }
50
+ else {
51
+ seenInStrategies.set(cleanedKey, [strategy]);
52
+ }
53
+ index.set(cleanedKey, { strategy, value });
54
+ };
55
+ // 1. Process testId / testIds (backward compatibility)
56
+ const testIdConfig = config.testId || config.testIds;
57
+ if (testIdConfig) {
58
+ const expanded = (0, dynamic_locator_resolver_1.getExpandedTestIds)(testIdConfig);
59
+ for (const key of Object.keys(expanded)) {
60
+ addKey(key, 'testId', expanded[key]);
61
+ }
62
+ }
63
+ // 2. Process selector / selectors (backward compatibility)
64
+ const selectorConfig = config.selector || config.selectors;
65
+ if (selectorConfig) {
66
+ const expanded = (0, dynamic_locator_resolver_1.getExpandedTestIds)(selectorConfig);
67
+ for (const key of Object.keys(expanded)) {
68
+ addKey(key, 'selector', expanded[key]);
20
69
  }
21
70
  }
22
- if (config.selectors) {
23
- for (const key of Object.keys(config.selectors)) {
24
- Object.defineProperty(instance, key, {
25
- get: () => {
26
- return context.locator(config.selectors[key]);
27
- },
28
- enumerable: true,
29
- configurable: true,
30
- });
71
+ // 3. Process all other strategies
72
+ for (const strategy of config_1.strategyList) {
73
+ if (strategy === 'testId' || strategy === 'selector')
74
+ continue;
75
+ const items = config[strategy];
76
+ if (Array.isArray(items)) {
77
+ for (const item of items) {
78
+ if (typeof item === 'string') {
79
+ addKey(item, strategy, item);
80
+ }
81
+ }
31
82
  }
32
83
  }
84
+ // 4. Duplicate Check & Validation
85
+ for (const [key, strategies] of seenInStrategies.entries()) {
86
+ if (strategies.length > 1) {
87
+ throw new Error(`Duplicate locator value "${key}"\n\nFound in\n\n${strategies.join('\n\n')}\n\nLocator values must be globally unique.`);
88
+ }
89
+ }
90
+ return index;
91
+ }
92
+ function defineLocators(instance, context, config) {
93
+ let index = exports.lookupIndexCache.get(config);
94
+ if (!index) {
95
+ index = buildLookupIndex(config);
96
+ exports.lookupIndexCache.set(config, index);
97
+ }
98
+ for (const key of index.keys()) {
99
+ Object.defineProperty(instance, key, {
100
+ get: () => {
101
+ return resolveLocator(context, config, key);
102
+ },
103
+ enumerable: true,
104
+ configurable: true
105
+ });
106
+ }
33
107
  }
34
108
  function resolveLocator(context, config, target, options) {
35
109
  if (typeof target !== 'string')
36
110
  return target;
37
111
  const targetStr = target;
38
- const parts = targetStr.split('.');
112
+ let index = exports.lookupIndexCache.get(config);
113
+ if (!index) {
114
+ index = buildLookupIndex(config);
115
+ exports.lookupIndexCache.set(config, index);
116
+ }
117
+ const toRegex = (val) => {
118
+ const escaped = val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
119
+ return new RegExp(escaped.replace(/\s+/g, '\\s+'), 'i');
120
+ };
39
121
  const resolveSingle = (key, ctx) => {
40
- if (config.testIds && key in config.testIds) {
41
- return ctx.getByTestId(config.testIds[key]);
122
+ const cleaned = cleanKey(key);
123
+ const meta = index.get(cleaned);
124
+ if (!meta) {
125
+ throw new Error(`Locator key '${key}' is not defined in page object configuration.`);
126
+ }
127
+ const strategy = exports.locatorStrategies[meta.strategy];
128
+ if (!strategy) {
129
+ throw new Error(`Unknown locator strategy '${meta.strategy}' for key '${key}'.`);
42
130
  }
43
- if (config.selectors && key in config.selectors) {
44
- return ctx.locator(config.selectors[key]);
131
+ const { resolver, role } = strategy;
132
+ if (resolver === 'getByRole') {
133
+ const val = typeof meta.value === 'string' ? toRegex(meta.value) : meta.value;
134
+ const roleOptions = { name: val };
135
+ if (options) {
136
+ const allowedOptions = ['exact', 'checked', 'disabled', 'expanded', 'includeHidden', 'level', 'pressed', 'selected'];
137
+ for (const opt of allowedOptions) {
138
+ if (opt in options) {
139
+ roleOptions[opt] = options[opt];
140
+ }
141
+ }
142
+ }
143
+ return ctx[resolver](role, roleOptions);
144
+ }
145
+ else if (resolver === 'locator') {
146
+ return ctx[resolver](meta.value);
147
+ }
148
+ else {
149
+ const opt = {};
150
+ if (options && 'exact' in options) {
151
+ opt.exact = options.exact;
152
+ }
153
+ const val = typeof meta.value === 'string' ? toRegex(meta.value) : meta.value;
154
+ return ctx[resolver](val, opt);
45
155
  }
46
- throw new Error(`Locator key '${key}' is not defined in testIds or selectors.`);
47
156
  };
48
- let loc = resolveSingle(parts[0], context);
49
- for (let i = 1; i < parts.length; i++) {
50
- loc = resolveSingle(parts[i], loc);
157
+ let loc;
158
+ const cleanedTarget = cleanKey(targetStr);
159
+ if (index.has(cleanedTarget)) {
160
+ loc = resolveSingle(targetStr, context);
161
+ }
162
+ else if (targetStr.includes('.')) {
163
+ const parts = targetStr.split('.');
164
+ loc = resolveSingle(parts[0], context);
165
+ for (let i = 1; i < parts.length; i++) {
166
+ loc = resolveSingle(parts[i], loc);
167
+ }
168
+ }
169
+ else {
170
+ loc = resolveSingle(targetStr, context);
51
171
  }
52
172
  let resolved = loc;
53
173
  if (options?.hasText !== undefined) {
@@ -67,7 +187,9 @@ function resolveLocator(context, config, target, options) {
67
187
  function locator(context, config, target, options) {
68
188
  const resolved = resolveLocator(context, config, target, { raw: true });
69
189
  const filtered = options ? resolved.filter(options) : resolved;
70
- const loc = (options && typeof options === 'object' && 'nth' in options && options.nth !== undefined) ? filtered.nth(options.nth) : filtered;
190
+ const loc = options && typeof options === 'object' && 'nth' in options && options.nth !== undefined
191
+ ? filtered.nth(options.nth)
192
+ : filtered;
71
193
  return wrapLocatorWithProxy(loc);
72
194
  }
73
195
  function wrapLocatorWithProxy(loc) {
@@ -78,7 +200,42 @@ function wrapLocatorWithProxy(loc) {
78
200
  if (typeof val === 'function' && actionMethods.includes(propKey)) {
79
201
  return (...args) => {
80
202
  const stepName = (0, formatter_1.formatStepDescription)(propKey, targetLoc, args);
81
- return test_1.test.step(stepName, () => val.apply(targetLoc, args));
203
+ if (propKey === 'fill') {
204
+ const options = args[1];
205
+ let shouldMask = false;
206
+ if (options && typeof options === 'object' && options.mask !== undefined) {
207
+ shouldMask = options.mask === true;
208
+ }
209
+ else {
210
+ const targetStr = (0, formatter_1.formatTarget)(targetLoc);
211
+ const targetStrLower = targetStr.toLowerCase();
212
+ shouldMask = targetStrLower.includes('pass') || targetStrLower.includes('pw');
213
+ }
214
+ if (shouldMask) {
215
+ return test_1.test.step(stepName, async () => {
216
+ await targetLoc.focus();
217
+ await targetLoc.evaluate((el, val) => {
218
+ const inputEl = el;
219
+ const prototype = el.tagName === 'TEXTAREA'
220
+ ? window.HTMLTextAreaElement.prototype
221
+ : window.HTMLInputElement.prototype;
222
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
223
+ if (descriptor && descriptor.set) {
224
+ descriptor.set.call(inputEl, val);
225
+ }
226
+ else {
227
+ inputEl.value = val;
228
+ }
229
+ inputEl.dispatchEvent(new Event('input', { bubbles: true }));
230
+ inputEl.dispatchEvent(new Event('change', { bubbles: true }));
231
+ }, args[0]);
232
+ }, { box: true, location: (0, caller_location_1.getCallerLocation)() });
233
+ }
234
+ }
235
+ return test_1.test.step(stepName, () => val.apply(targetLoc, args), {
236
+ box: true,
237
+ location: (0, caller_location_1.getCallerLocation)()
238
+ });
82
239
  };
83
240
  }
84
241
  return val;