artes 1.7.24 → 1.7.26
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/package.json
CHANGED
|
@@ -1,230 +1,265 @@
|
|
|
1
|
-
const { context } = require("../../hooks/context");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
let elements = {};
|
|
5
|
-
|
|
6
|
-
function addElements(newElements) {
|
|
7
|
-
elements = { ...elements, ...newElements };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// async function locatorExistenceChecker(locator){
|
|
11
|
-
// const locatorCount = await locator.count();
|
|
12
|
-
// console.log(locator, locatorCount)
|
|
13
|
-
// return locatorCount ==0 ? false : true;
|
|
14
|
-
// }
|
|
15
|
-
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
1
|
+
const { context } = require("../../hooks/context");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
let elements = {};
|
|
5
|
+
|
|
6
|
+
function addElements(newElements) {
|
|
7
|
+
elements = { ...elements, ...newElements };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// async function locatorExistenceChecker(locator){
|
|
11
|
+
// const locatorCount = await locator.count();
|
|
12
|
+
// console.log(locator, locatorCount)
|
|
13
|
+
// return locatorCount ==0 ? false : true;
|
|
14
|
+
// }
|
|
15
|
+
|
|
16
|
+
function isPlaywrightCallSyntax(str) {
|
|
17
|
+
return typeof str === "string" && /^[a-zA-Z]+\(.*\)\s*;?\s*$/s.test(str.trim());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parsePlaywrightCall(str) {
|
|
21
|
+
const match = str.trim().match(/^([a-zA-Z]+)\((.*)\)\s*;?\s*$/s);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
|
|
24
|
+
const [, method, argsStr] = match;
|
|
25
|
+
|
|
26
|
+
// Safely evaluate the argument list as a JS array literal
|
|
27
|
+
// e.g. "'button', { name: 'Decline' }" -> ['button', { name: 'Decline' }]
|
|
28
|
+
let args;
|
|
29
|
+
try {
|
|
30
|
+
args = Function(`"use strict"; return [${argsStr}];`)();
|
|
31
|
+
} catch (err) {
|
|
32
|
+
throw new Error(`Failed to parse locator arguments in "${str}": ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { method, args };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function selectorSeparator(element) {
|
|
39
|
+
if (typeof element !== "string") return element;
|
|
40
|
+
|
|
41
|
+
const selector = element?.split("=");
|
|
42
|
+
const validTypes = [
|
|
43
|
+
"xpath",
|
|
44
|
+
"name",
|
|
45
|
+
"placeholder",
|
|
46
|
+
"text",
|
|
47
|
+
"label",
|
|
48
|
+
"role",
|
|
49
|
+
"alt",
|
|
50
|
+
"title",
|
|
51
|
+
"testid",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
if (selector && validTypes.includes(selector[0]?.trim())) {
|
|
55
|
+
return [
|
|
56
|
+
selector[0].trim(),
|
|
57
|
+
selector[1] !== undefined ? selector[1].trim() : "",
|
|
58
|
+
];
|
|
59
|
+
} else {
|
|
60
|
+
return selector.join("=");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getSelector(element) {
|
|
65
|
+
element = resolveVariable(element);
|
|
66
|
+
|
|
67
|
+
const selector =
|
|
68
|
+
elements?.[element]?.selector || elements?.[element] || element;
|
|
69
|
+
return resolveVariable(selectorSeparator(selector));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getElement(element) {
|
|
73
|
+
if (!context.page) {
|
|
74
|
+
throw new Error("Page context is not initialized.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const rawValue = resolveVariable(
|
|
78
|
+
elements?.[element]?.selector || elements?.[element] || element
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// NEW: handle raw Playwright method-call syntax, e.g. getByRole('button', { name: 'Decline' })
|
|
82
|
+
if (isPlaywrightCallSyntax(rawValue)) {
|
|
83
|
+
const parsed = parsePlaywrightCall(rawValue);
|
|
84
|
+
if (!parsed || typeof context.page[parsed.method] !== "function") {
|
|
85
|
+
throw new Error(`Unsupported or invalid Playwright locator call: "${rawValue}"`);
|
|
86
|
+
}
|
|
87
|
+
return context.page[parsed.method](...parsed.args);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const selector = getSelector(element);
|
|
91
|
+
const waitTime = elements[element]?.waitTime * 1000 || 0;
|
|
92
|
+
|
|
93
|
+
let locator;
|
|
94
|
+
switch (selector[0]) {
|
|
95
|
+
case "xpath":
|
|
96
|
+
locator = context.page.locator(`xpath=${selector[1]}`, { exact: true });
|
|
97
|
+
break;
|
|
98
|
+
case "name":
|
|
99
|
+
locator = context.page.locator(`[name="${selector[1]}"]`, {
|
|
100
|
+
exact: true,
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
case "placeholder":
|
|
104
|
+
locator = context.page.getByPlaceholder(selector[1], { exact: true });
|
|
105
|
+
break;
|
|
106
|
+
case "text":
|
|
107
|
+
locator = context.page.getByText(selector[1], { exact: true });
|
|
108
|
+
break;
|
|
109
|
+
case "label":
|
|
110
|
+
locator = context.page.getByLabel(selector[1], { exact: true });
|
|
111
|
+
break;
|
|
112
|
+
case "role":
|
|
113
|
+
locator = context.page.getByRole(selector[1], { exact: true });
|
|
114
|
+
break;
|
|
115
|
+
case "alt":
|
|
116
|
+
locator = context.page.getByAltText(selector[1], { exact: true });
|
|
117
|
+
break;
|
|
118
|
+
case "title":
|
|
119
|
+
locator = context.page.getByTitle(selector[1], { exact: true });
|
|
120
|
+
break;
|
|
121
|
+
case "testid":
|
|
122
|
+
locator = context.page.getByTestId(selector[1], { exact: true });
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
locator = context.page.locator(selector, { exact: true });
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return locator;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeCrossplatformPath(inputPath) {
|
|
133
|
+
return path.normalize(inputPath.replace(/\\/g, "/"));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pathToCamelCase(path) {
|
|
137
|
+
const cleaned = path.replace(/\[(\d+)\]/g, "_$1");
|
|
138
|
+
const parts = cleaned.split(".");
|
|
139
|
+
return parts
|
|
140
|
+
.map((part, index) => {
|
|
141
|
+
if (index === 0) return part;
|
|
142
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
143
|
+
})
|
|
144
|
+
.join("");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function extractVarsFromResponse(responseBody, vars, customVarNames) {
|
|
148
|
+
function getValueByPath(obj, path) {
|
|
149
|
+
if (typeof obj === "string") return obj;
|
|
150
|
+
|
|
151
|
+
const keys = path.split(".").flatMap((key) => {
|
|
152
|
+
const arrayMatch = key.match(/^([^\[]+)\[(\d+)\]$/);
|
|
153
|
+
if (arrayMatch) {
|
|
154
|
+
return [arrayMatch[1], parseInt(arrayMatch[2])];
|
|
155
|
+
}
|
|
156
|
+
return [key];
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
let current = obj;
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
if (current == null) return undefined;
|
|
162
|
+
|
|
163
|
+
if (typeof key === "number") {
|
|
164
|
+
if (!Array.isArray(current)) return undefined;
|
|
165
|
+
current = current[key];
|
|
166
|
+
} else if (typeof current === "object" && key in current) {
|
|
167
|
+
current = current[key];
|
|
168
|
+
} else {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return current;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
const varPaths = vars.split(",").map((v) => v.trim());
|
|
178
|
+
let customNames = [];
|
|
179
|
+
|
|
180
|
+
if (!customVarNames) {
|
|
181
|
+
customNames = varPaths.map(pathToCamelCase);
|
|
182
|
+
} else if (typeof customVarNames === "string") {
|
|
183
|
+
customNames = customVarNames.split(",").map((n) => n.trim());
|
|
184
|
+
} else if (Array.isArray(customVarNames)) {
|
|
185
|
+
customNames = customVarNames;
|
|
186
|
+
} else {
|
|
187
|
+
throw new Error("customVarNames must be a string or an array");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (customNames.length !== varPaths.length) {
|
|
191
|
+
customNames = varPaths.map(pathToCamelCase);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
varPaths.forEach((path, index) => {
|
|
195
|
+
const value = getValueByPath(responseBody, path);
|
|
196
|
+
if (value !== undefined) {
|
|
197
|
+
saveVar(value, customNames[index], path);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function saveVar(value, customName, path) {
|
|
203
|
+
if (!customName) {
|
|
204
|
+
const flatKey = path
|
|
205
|
+
.split(".")
|
|
206
|
+
.map((part, i) =>
|
|
207
|
+
i === 0 ? part : part[0].toUpperCase() + part.slice(1),
|
|
208
|
+
)
|
|
209
|
+
.join("");
|
|
210
|
+
|
|
211
|
+
context.vars[flatKey] = value;
|
|
212
|
+
} else {
|
|
213
|
+
context.vars[customName] = value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveVariable(template) {
|
|
218
|
+
if (typeof template === "string") {
|
|
219
|
+
return template.replace(/{{\s*(\w+)\s*}}/g, (_, varName) => {
|
|
220
|
+
let value = context.vars[varName];
|
|
221
|
+
|
|
222
|
+
if (value !== undefined) {
|
|
223
|
+
if (typeof value !== "string") {
|
|
224
|
+
try {
|
|
225
|
+
value = JSON.stringify(value);
|
|
226
|
+
} catch {
|
|
227
|
+
value = String(value);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return value
|
|
232
|
+
.replace(/\n/g, "\\n")
|
|
233
|
+
.replace(/\r/g, "\\r")
|
|
234
|
+
.replace(/\t/g, "\\t");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return `{{${varName}}}`;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (Array.isArray(template)) {
|
|
242
|
+
return template.map((item) => resolveVariable(item));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (template && typeof template === "object") {
|
|
246
|
+
const result = {};
|
|
247
|
+
for (const key in template) {
|
|
248
|
+
result[key] = resolveVariable(template[key]);
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return template;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
getElement,
|
|
258
|
+
addElements,
|
|
259
|
+
getSelector,
|
|
260
|
+
extractVarsFromResponse,
|
|
261
|
+
pathToCamelCase,
|
|
262
|
+
normalizeCrossplatformPath,
|
|
263
|
+
saveVar,
|
|
264
|
+
resolveVariable,
|
|
265
|
+
};
|
|
@@ -1364,3 +1364,27 @@ Then(
|
|
|
1364
1364
|
},
|
|
1365
1365
|
);
|
|
1366
1366
|
|
|
1367
|
+
Then(
|
|
1368
|
+
"User expects that response has {string} field that contains {string} value",
|
|
1369
|
+
async (field, value) => {
|
|
1370
|
+
extractVarsFromResponse(context.response["Response Body"], field);
|
|
1371
|
+
const key = pathToCamelCase(field);
|
|
1372
|
+
expect(String(context.vars[key])).toContain(resolveVariable(value));
|
|
1373
|
+
},
|
|
1374
|
+
);
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
Then('User expects that response has {string} header with {string} value', async (field, value) => {
|
|
1378
|
+
extractVarsFromResponse(context.response["Response Headers"], field);
|
|
1379
|
+
const key = pathToCamelCase(field);
|
|
1380
|
+
expect(String(context.vars[key])).toBe(resolveVariable(value));
|
|
1381
|
+
})
|
|
1382
|
+
|
|
1383
|
+
Then(
|
|
1384
|
+
"User expects that response does not have {string} field with {string} value",
|
|
1385
|
+
async (field, value) => {
|
|
1386
|
+
extractVarsFromResponse(context.response["Response Body"], field);
|
|
1387
|
+
const key = pathToCamelCase(field);
|
|
1388
|
+
expect(String(context.vars[key])).not.toBe(resolveVariable(value));
|
|
1389
|
+
},
|
|
1390
|
+
);
|
|
@@ -1,76 +1,208 @@
|
|
|
1
|
-
const { When } = require("../helper/imports/commons");
|
|
2
|
-
const { frame } = require("../helper/stepFunctions/exporter");
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
const { When } = require("../helper/imports/commons");
|
|
2
|
+
const { frame } = require("../helper/stepFunctions/exporter");
|
|
3
|
+
const { saveVar, element} = require("artes");
|
|
4
|
+
|
|
5
|
+
// User takes a screenshot of a specific selector
|
|
6
|
+
When("User takes a screenshot of {string}", async function (selector) {
|
|
7
|
+
await frame.screenshot(selector);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// User gets the content frame of a specific selector
|
|
11
|
+
When("User gets the content frame of {string}", async function (selector) {
|
|
12
|
+
await frame.contentFrame(selector);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// User gets the frame locator of a specific selector
|
|
16
|
+
When("User gets the frame locator of {string}", async function (selector) {
|
|
17
|
+
await frame.frameLocator(selector);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// User gets the nth element of a specific selector
|
|
21
|
+
When(
|
|
22
|
+
"User gets the {int} th element of {string}",
|
|
23
|
+
async function (index, selector) {
|
|
24
|
+
await frame.nth(selector, index);
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// User gets the first element of a specific selector
|
|
29
|
+
When("User gets the first element of {string}", async function (selector) {
|
|
30
|
+
await frame.first(selector);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// User gets the last element of a specific selector
|
|
34
|
+
When("User gets the last element of {string}", async function (selector) {
|
|
35
|
+
await frame.last(selector);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// User filters elements of a specific selector
|
|
39
|
+
When(
|
|
40
|
+
"User filters elements of {string} with filter {string}",
|
|
41
|
+
async function (selector, filter) {
|
|
42
|
+
await frame.filter(selector, filter);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// User counts the number of elements of a specific selector
|
|
47
|
+
When("User counts the elements of {string}", async function (selector) {
|
|
48
|
+
await frame.count(selector);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// User gets an element by its alt text
|
|
52
|
+
When("User gets the element with alt text {string}", async function (text) {
|
|
53
|
+
await frame.getByAltText(text);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// User gets an element by its label
|
|
57
|
+
When("User gets the element with label {string}", async function (label) {
|
|
58
|
+
await frame.getByLabel(label);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// User gets an element by its placeholder
|
|
62
|
+
When(
|
|
63
|
+
"User gets the element with placeholder {string}",
|
|
64
|
+
async function (placeholder) {
|
|
65
|
+
await frame.getByPlaceholder(placeholder);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// User gets an element by its role
|
|
70
|
+
When("User gets the element with role {string}", async function (role) {
|
|
71
|
+
await frame.getByRole(role);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// User gets an element by its testId
|
|
75
|
+
When("User gets the element with testId {string}", async function (testId) {
|
|
76
|
+
await frame.getByTestId(testId);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
When("User saves the value of {string} as {string}", async function (selector, varName) {
|
|
83
|
+
|
|
84
|
+
async function extractElementValue(locator) {
|
|
85
|
+
await locator.waitFor({ state: "visible" });
|
|
86
|
+
|
|
87
|
+
const tagName = await locator.evaluate((el) => el.tagName.toLowerCase());
|
|
88
|
+
const normalize = (str) => str?.replace(/\s+/g, " ").trim();
|
|
89
|
+
|
|
90
|
+
if (["input", "textarea"].includes(tagName)) {
|
|
91
|
+
return normalize(await locator.inputValue());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (tagName === "select") {
|
|
95
|
+
return normalize(
|
|
96
|
+
await locator.evaluate((el) => el.options[el.selectedIndex]?.text)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (tagName === "img") {
|
|
101
|
+
return normalize(await locator.getAttribute("alt"));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Try several strategies in order, use the first non-empty result
|
|
105
|
+
const strategies = [
|
|
106
|
+
() => locator.innerText(),
|
|
107
|
+
() => locator.textContent(),
|
|
108
|
+
() => locator.getAttribute("value"),
|
|
109
|
+
() => locator.getAttribute("aria-label"),
|
|
110
|
+
() => locator.getAttribute("title"),
|
|
111
|
+
() => locator.getAttribute("placeholder"),
|
|
112
|
+
() => locator.getAttribute("alt"),
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
for (const strategy of strategies) {
|
|
116
|
+
try {
|
|
117
|
+
const result = normalize(await strategy());
|
|
118
|
+
if (result) return result;
|
|
119
|
+
} catch {
|
|
120
|
+
// strategy not applicable to this element, try next
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const locator = element(selector);
|
|
129
|
+
const value = await extractElementValue(locator);
|
|
130
|
+
console.log( typeof locator)
|
|
131
|
+
|
|
132
|
+
saveVar(value, varName);
|
|
133
|
+
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
When(
|
|
138
|
+
"User saves the value of {int} th of {string} as {string}",
|
|
139
|
+
async function (ordinal, selector, varName) {
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async function extractElementValue(locator) {
|
|
143
|
+
await locator.waitFor({ state: "visible" });
|
|
144
|
+
|
|
145
|
+
const tagName = await locator.evaluate((el) => el.tagName.toLowerCase());
|
|
146
|
+
const normalize = (str) => str?.replace(/\s+/g, " ").trim();
|
|
147
|
+
|
|
148
|
+
if (["input", "textarea"].includes(tagName)) {
|
|
149
|
+
return normalize(await locator.inputValue());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (tagName === "select") {
|
|
153
|
+
return normalize(
|
|
154
|
+
await locator.evaluate((el) => el.options[el.selectedIndex]?.text)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (tagName === "img") {
|
|
159
|
+
return normalize(await locator.getAttribute("alt"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const strategies = [
|
|
163
|
+
() => locator.innerText(),
|
|
164
|
+
() => locator.textContent(),
|
|
165
|
+
() => locator.getAttribute("value"),
|
|
166
|
+
() => locator.getAttribute("aria-label"),
|
|
167
|
+
() => locator.getAttribute("title"),
|
|
168
|
+
() => locator.getAttribute("placeholder"),
|
|
169
|
+
() => locator.getAttribute("alt"),
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
for (const strategy of strategies) {
|
|
173
|
+
try {
|
|
174
|
+
const result = normalize(await strategy());
|
|
175
|
+
if (result) return result;
|
|
176
|
+
} catch {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function ordinalToIndex(ordinal) {
|
|
185
|
+
const num = parseInt(ordinal, 10);
|
|
186
|
+
if (isNaN(num) || num < 1) {
|
|
187
|
+
throw new Error(`Invalid ordinal "${ordinal}" — must be like "1st", "2nd", "3rd", "4th"...`);
|
|
188
|
+
}
|
|
189
|
+
return num - 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
const index = ordinalToIndex(ordinal);
|
|
194
|
+
const baseLocator = element(selector);
|
|
195
|
+
|
|
196
|
+
const count = await baseLocator.count();
|
|
197
|
+
if (index >= count) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Requested ${ordinal} match for "${selector}", but only ${count} element(s) found.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const locator = baseLocator.nth(index);
|
|
204
|
+
const value = await extractElementValue(locator);
|
|
205
|
+
|
|
206
|
+
saveVar(value, varName);
|
|
207
|
+
}
|
|
208
|
+
);
|