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.
- 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 +19 -14
- 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/helpers.d.ts +1 -1
- package/dist/helpers.js +21 -9
- package/dist/page/actions/locator-actions.js +46 -2
- package/dist/page/actions/page-actions.d.ts +11 -6
- package/dist/page/actions/page-actions.js +12 -12
- package/dist/page/assertions/verify-chain.d.ts +39 -32
- package/dist/page/assertions/verify-chain.js +21 -6
- package/dist/page/assertions/verify-helpers.d.ts +9 -3
- package/dist/page/assertions/verify-helpers.js +18 -9
- package/dist/page/config.d.ts +126 -38
- package/dist/page/config.js +10 -31
- package/dist/page/index.d.ts +1 -0
- package/dist/page/index.js +1 -0
- package/dist/page/locators/dynamic-locator-resolver.d.ts +49 -0
- package/dist/page/locators/dynamic-locator-resolver.js +187 -0
- package/dist/page/locators/resolver.d.ts +15 -14
- package/dist/page/locators/resolver.js +187 -30
- package/dist/page/registry.d.ts +51 -12
- package/dist/page/registry.js +90 -18
- package/dist/page/typed-page.d.ts +33 -28
- package/dist/page/typed-page.js +89 -12
- package/dist/page/types/proxy-methods.d.ts +69 -54
- package/dist/page/types/validation.d.ts +69 -0
- package/dist/page/types/validation.js +2 -0
- package/dist/page/utils/caller-location.d.ts +5 -0
- package/dist/page/utils/caller-location.js +82 -0
- package/dist/page/utils/formatter.js +14 -4
- 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
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;
|
package/dist/page/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/page/index.js
CHANGED
|
@@ -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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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<
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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 =
|
|
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
|
-
|
|
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;
|