@devassure/tpp-engine 0.0.0-stage → 1.8.0
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 +18 -2
- package/index.js +121 -0
- package/package.json +30 -4
- package/src/accessibility/combined-a11y-report.js +208 -0
- package/src/accessibility/tpp-a11y.js +81 -0
- package/src/actions.js +3201 -0
- package/src/api/api-auth.js +18 -0
- package/src/api/api-service.js +289 -0
- package/src/app-config.json +7 -0
- package/src/autoheal-actions.js +309 -0
- package/src/console-log/console-log-manager.js +39 -0
- package/src/console-log/console-log-utils.js +201 -0
- package/src/database/mysql/mysql-service.js +23 -0
- package/src/database/neo4j/neo4j-service.js +32 -0
- package/src/database/oracle/oracle-service.js +48 -0
- package/src/database/pg/pg-service.js +29 -0
- package/src/downloads/downloads-manager.js +86 -0
- package/src/downloads/downloads-utils.js +113 -0
- package/src/globalsetup.js +68 -0
- package/src/globalteardown.js +57 -0
- package/src/hooks/each-hook.js +45 -0
- package/src/hooks/hook-utils.js +21 -0
- package/src/library/authenticator/authenticator-utils.js +43 -0
- package/src/library/csv/csv-utils.js +297 -0
- package/src/library/date/date-utils.js +168 -0
- package/src/library/emailTesting/TokenConfig.js +46 -0
- package/src/library/emailTesting/emailTesting-utils.js +527 -0
- package/src/library/excel/excel-utils.js +922 -0
- package/src/library/file/file-action-utils.js +129 -0
- package/src/library/file/file-utils.js +127 -0
- package/src/library/json/json-utils.js +104 -0
- package/src/library/math/math-utils.js +283 -0
- package/src/library/softAssert/softAssert-utils.js +154 -0
- package/src/library/table/table-utils.js +92 -0
- package/src/library/text/text-utils.js +45 -0
- package/src/library/tpp-library.js +81 -0
- package/src/library/web/slider-utils.js +66 -0
- package/src/mobile/appium/appium-manager.js +158 -0
- package/src/mobile/device-cloud/browserstack-service.js +44 -0
- package/src/mobile/device-cloud/commands/browserstack-commands.js +20 -0
- package/src/mobile/device-cloud/commands/device-cloud-commands.js +17 -0
- package/src/mobile/device-cloud/device-cloud-service.js +29 -0
- package/src/mobile/device-cloud/pcloudy-service.js +65 -0
- package/src/mobile/mobile-actions.js +1568 -0
- package/src/mobile/recorder-utils/css-selector-generator.js +2 -0
- package/src/mobile/recorder-utils/finder.js +752 -0
- package/src/mobile/recorder-utils/mobile-recorder.js +266 -0
- package/src/mobile/web-driver-utils.js +110 -0
- package/src/models/tpp-element.js +58 -0
- package/src/models/tpp-mobile-element.js +50 -0
- package/src/network-log/network-log-manager.js +96 -0
- package/src/network-log/network-log-utils.js +361 -0
- package/src/types/elements.types.js +24 -0
- package/src/utils/ai-utils.js +48 -0
- package/src/utils/assert-utils.js +50 -0
- package/src/utils/autoheal-utils.js +9 -0
- package/src/utils/common-utils.js +120 -0
- package/src/utils/cookie-utils.js +70 -0
- package/src/utils/json-utils.js +10 -0
- package/src/utils/log-utils.js +10 -0
- package/src/utils/logger.js +76 -0
- package/src/utils/screenshot-utils.js +42 -0
- package/src/utils/token-utils.js +18 -0
- package/src/utils/vectordb-utils.js +125 -0
- package/src/visual-test/tpp-visual-test.js +122 -0
- package/src/web/web-executor-agent.js +149 -0
- package/src/web/web-executor-data-fetch.js +353 -0
- package/test-excel-utils.js +570 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function extractAuthHeaders(tppAuth) {
|
|
2
|
+
if(!tppAuth) return {};
|
|
3
|
+
switch(tppAuth.authType) {
|
|
4
|
+
case 'Basic Auth': {
|
|
5
|
+
if(!tppAuth.userName || tppAuth.password == null) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
const encodedCreds = btoa(`${tppAuth.userName}:${tppAuth.password}`);
|
|
9
|
+
return {Authorization: `Basic ${encodedCreds}`};
|
|
10
|
+
}
|
|
11
|
+
case 'Bearer Token': {
|
|
12
|
+
return {Authorization: `Bearer ${tppAuth.bearerToken}`}
|
|
13
|
+
}
|
|
14
|
+
default : return {};
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.extractAuthHeaders = extractAuthHeaders;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const { compare } = require("../utils/assert-utils");
|
|
2
|
+
const { extractValue } = require("../utils/json-utils");
|
|
3
|
+
const { extractAuthHeaders } = require("./api-auth");
|
|
4
|
+
const packageJSON = require('../../package.json');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const { FileUtils } = require("../library/file/file-utils");
|
|
7
|
+
|
|
8
|
+
class ApiService {
|
|
9
|
+
|
|
10
|
+
constructor(request) {
|
|
11
|
+
this.request = request;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async makeApiCall(method, apiUrl, options) {
|
|
15
|
+
let response;
|
|
16
|
+
switch(method) {
|
|
17
|
+
case 'GET': {
|
|
18
|
+
response = await this.request.get(apiUrl, options);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
case 'POST': {
|
|
22
|
+
response = await this.request.post(apiUrl, options);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
case 'PUT': {
|
|
26
|
+
response = await this.request.put(apiUrl, options);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
case 'PATCH': {
|
|
30
|
+
response = await this.request.patch(apiUrl, options);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 'DELETE': {
|
|
34
|
+
response = await this.request.delete(apiUrl, options);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this.parseResponse(response)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async apiCall(apiRequest) {
|
|
42
|
+
const apiPath = !apiRequest.path.trim()
|
|
43
|
+
? ''
|
|
44
|
+
: apiRequest.path.startsWith("/") ? apiRequest.path : "/" + apiRequest.path;
|
|
45
|
+
const fullPath = apiRequest.baseUrl + apiPath.trim();
|
|
46
|
+
const options = this.getApiOptions(apiRequest);
|
|
47
|
+
return await this.makeApiCall(apiRequest.method, fullPath, options)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getApiFullPath(apiRequest) {
|
|
51
|
+
const apiPath = !apiRequest.path.trim()
|
|
52
|
+
? ''
|
|
53
|
+
: apiRequest.path.startsWith("/") ? apiRequest.path : "/" + apiRequest.path;
|
|
54
|
+
return apiRequest.baseUrl + apiPath.trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async apiCallwithJson(apiRequest,data) {
|
|
58
|
+
const fullPath = this.getApiFullPath(apiRequest);
|
|
59
|
+
let options;
|
|
60
|
+
options = this.getApiOptions(apiRequest);
|
|
61
|
+
if(data) { // overwrite options
|
|
62
|
+
options = this.getApiOptionsJSON(options, data);
|
|
63
|
+
}
|
|
64
|
+
return await this.makeApiCall(apiRequest.method, fullPath, options)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getApiOptionsJSON(options, data) {
|
|
68
|
+
if (data.body) {
|
|
69
|
+
options.data = data.body;
|
|
70
|
+
}
|
|
71
|
+
if (data.headers || options.headers) {
|
|
72
|
+
options.headers = {
|
|
73
|
+
...options.headers || {},
|
|
74
|
+
...data.headers || {},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (data.params || options.params) {
|
|
78
|
+
options.params = {
|
|
79
|
+
...options.params || {},
|
|
80
|
+
...data.params || {},
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return options
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
validateStatus(apiResponse, comparator, expectedStatus) {
|
|
87
|
+
if(!compare(apiResponse.status(), comparator, expectedStatus)) {
|
|
88
|
+
throw {
|
|
89
|
+
message: `Response status is not as expected. actual: ${apiResponse.status()} ---- comparator: ${comparator} ---- expected: ${expectedStatus}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async validateText(apiResponse, comparator, expected) {
|
|
95
|
+
const responseText = await apiResponse.text();
|
|
96
|
+
if(!compare(responseText, comparator, expected)) {
|
|
97
|
+
throw {
|
|
98
|
+
message: `Response text is not as expected. actual: ${responseText} ---- comparator: ${comparator} ---- expected: ${expected}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async validateJson(apiResponse, jsonKey, comparator, expected) {
|
|
104
|
+
const responseJson = await apiResponse.json();
|
|
105
|
+
const value = extractValue(responseJson, jsonKey);
|
|
106
|
+
if(!compare(value, comparator, expected)) {
|
|
107
|
+
throw {
|
|
108
|
+
message: `Response text is not as expected. actual: ${value} ---- comparator: ${comparator} ---- expected: ${expected}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getResponseUrl(apiResponse) {
|
|
114
|
+
return apiResponse.url()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getResponseStaus(apiResponse) {
|
|
118
|
+
return apiResponse.status()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async getResponseBodyJson(apiResponse) {
|
|
122
|
+
return await apiResponse.json();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getResponseBodyText(apiResponse) {
|
|
126
|
+
return await apiResponse.text();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getResponseHeaders(apiResponse) {
|
|
130
|
+
return apiResponse.headers();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getResponseHeaderValue(apiResponse, key) {
|
|
134
|
+
const headers = apiResponse.headers();
|
|
135
|
+
return headers[key] || '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getApiOptions(apiRequest) {
|
|
139
|
+
const queryParams = apiRequest.request?.queryParams;
|
|
140
|
+
const params = this.extractQueryParams(queryParams);
|
|
141
|
+
const tppBody = apiRequest.request?.body;
|
|
142
|
+
const body = this.extractRequestBody(tppBody);
|
|
143
|
+
const tppHeaders = apiRequest.request?.headers;
|
|
144
|
+
const tppAuth = apiRequest.request?.auth;
|
|
145
|
+
const headers = this.extractHeaders(tppHeaders, tppAuth, body, apiRequest.baseUrl);
|
|
146
|
+
return {
|
|
147
|
+
...params,
|
|
148
|
+
...headers,
|
|
149
|
+
...body,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
extractRequestBody(tppBody) {
|
|
154
|
+
if(!tppBody || tppBody.type === 'none') return {};
|
|
155
|
+
|
|
156
|
+
if(tppBody.type === 'text') return {data: tppBody.data || ""};
|
|
157
|
+
|
|
158
|
+
if(tppBody.type === 'json') {
|
|
159
|
+
if(tppBody.data == null) { return {data: {}}};
|
|
160
|
+
if(typeof tppBody.data === 'string') {
|
|
161
|
+
if(!tppBody.data.trim()) { return {data: {}}};
|
|
162
|
+
try {
|
|
163
|
+
return {data: JSON.parse(tppBody.data || '{}')};
|
|
164
|
+
} catch (ex) {
|
|
165
|
+
throw { message: `Request body is not a valid JSON - ${ex}`};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return {data: tppBody.data || {}}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if(tppBody.type === 'form') {
|
|
172
|
+
if(!tppBody?.data?.length) return {};
|
|
173
|
+
let hasFiles = false;
|
|
174
|
+
const params = {};
|
|
175
|
+
tppBody.data.filter(param => !!param.name?.trim())
|
|
176
|
+
.forEach(param => {
|
|
177
|
+
if(param.type !== 'file') {
|
|
178
|
+
params[param.name] = param.value;
|
|
179
|
+
} else {
|
|
180
|
+
hasFiles = true;
|
|
181
|
+
const filePath = new FileUtils().getFilePath(param.value)
|
|
182
|
+
if(!fs.existsSync(filePath)) {
|
|
183
|
+
throw {message: 'File not found - ' + param.value};
|
|
184
|
+
}
|
|
185
|
+
params[param.name] = fs.createReadStream(filePath);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if(Object.keys(params).length <= 0) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return hasFiles ? {multipart: params} : {form: params};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if(tppBody.type === 'graphql') {
|
|
196
|
+
if(tppBody.data == null) { return {data: {query: '', variables: {}}}};
|
|
197
|
+
const query = tppBody.data.query || '';
|
|
198
|
+
let variables = {};
|
|
199
|
+
try {
|
|
200
|
+
if(tppBody.data.variables) variables = JSON.parse(tppBody.data.variables);
|
|
201
|
+
} catch (_ex) {};
|
|
202
|
+
return {data: {query, variables}}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
extractQueryParams(tppParams) {
|
|
209
|
+
if(!tppParams || !tppParams.length) return {};
|
|
210
|
+
const params = {};
|
|
211
|
+
tppParams.forEach(param => {
|
|
212
|
+
if(param?.name?.trim()) {
|
|
213
|
+
params[param.name] = param.value;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
if(Object.keys(params).length <= 0) {
|
|
217
|
+
return {};
|
|
218
|
+
}
|
|
219
|
+
return {params: params};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
extractHeaders(tppHeaders, tppAuth, body, baseUrl) {
|
|
223
|
+
const content = typeof body === "object" ? JSON.stringify(body.data) : body.data;
|
|
224
|
+
const contentLen = !!content ? Buffer.byteLength(content, 'utf-8') : 0;
|
|
225
|
+
const headers = {
|
|
226
|
+
'Content-Length': contentLen.toString(),
|
|
227
|
+
'Host': new URL(baseUrl).hostname || 'localhost',
|
|
228
|
+
'User-Agent': `DevAssure/${packageJSON?.version || '1.0'}`,
|
|
229
|
+
};
|
|
230
|
+
tppHeaders?.forEach(header => {
|
|
231
|
+
if(!!header?.name?.trim()) {
|
|
232
|
+
headers[header.name] = header.value;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
const authHeaders = extractAuthHeaders(tppAuth) || {};
|
|
236
|
+
const data = {...headers, ...authHeaders};
|
|
237
|
+
if(Object.keys(data).length <= 0) {
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
return {headers: data};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
parseResponse(response) {
|
|
244
|
+
return response;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async getResponseLog(response) {
|
|
248
|
+
return {
|
|
249
|
+
url: this.getResponseUrl(response),
|
|
250
|
+
status: this.getResponseStaus(response),
|
|
251
|
+
headers: this.getResponseHeaders(response),
|
|
252
|
+
body: await this.getResponseBodyText(response),
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getRequestLog(apiRequest) {
|
|
257
|
+
const apiOptions = this.getApiOptions(apiRequest);
|
|
258
|
+
const paramData = apiOptions.params && Object.keys(apiOptions.params).length
|
|
259
|
+
? {params: apiOptions.params}
|
|
260
|
+
: {};
|
|
261
|
+
const headerData = apiOptions.headers && Object.keys(apiOptions.headers).length
|
|
262
|
+
? {headers: apiOptions.headers}
|
|
263
|
+
: {};
|
|
264
|
+
const bodyData = apiOptions.data || apiOptions.form
|
|
265
|
+
? {body: apiOptions.data || apiOptions.form}
|
|
266
|
+
: {};
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
method: apiRequest.method,
|
|
270
|
+
url: this.getApiFullPath(apiRequest),
|
|
271
|
+
...headerData,
|
|
272
|
+
...paramData,
|
|
273
|
+
...bodyData,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getApiDetailsPretty(apiRequest, response) {
|
|
278
|
+
return `---------------------------------API DETAILS---------------------------------
|
|
279
|
+
----------------REQUEST----------------
|
|
280
|
+
${JSON.stringify(this.getRequestLog(apiRequest), undefined, 4)}
|
|
281
|
+
----------------RESPONSE----------------
|
|
282
|
+
${JSON.stringify(await this.getResponseLog(response), undefined, 4)}
|
|
283
|
+
_______________________________________________________________________________`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
exports.ApiService = ApiService;
|
|
289
|
+
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
const { FileUtils } = require("./library/file/file-utils");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
class AutohealActions {
|
|
5
|
+
|
|
6
|
+
constructor(actions) {
|
|
7
|
+
this.actions = actions;
|
|
8
|
+
this.page = actions.page;
|
|
9
|
+
this.fromAi = actions.fromAi;
|
|
10
|
+
this.customConfig = actions.customConfig;
|
|
11
|
+
this.autohealLog = actions.autohealLog;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async initPreferences() {
|
|
15
|
+
if (this.locatorPreferences) return this.locatorPreferences;
|
|
16
|
+
const projPath = this.customConfig?.projPath || "";
|
|
17
|
+
const locatorPreferenceFile = path.join(projPath, "project-preferences", "locator.preference.tspp");
|
|
18
|
+
this.locatorPreferences = await new FileUtils().readJSONContent(locatorPreferenceFile);
|
|
19
|
+
return this.locatorPreferences;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async click_on_element(x, y, stepName, element) {
|
|
23
|
+
const logLabel = `[Auto-heal] Clicking at (${x}, ${y}).`;
|
|
24
|
+
this.actions.action_log(logLabel);
|
|
25
|
+
try {
|
|
26
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
27
|
+
await page.mouse.click(x, y);
|
|
28
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
this.printLog(stepName, `[Auto-heal] Exception while clicking: ${e}`, element);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
printLog(stepName, log, element) {
|
|
35
|
+
let nestedStep = stepName + ` :: ${this.autohealLog(element)}`;
|
|
36
|
+
this.actions.log(nestedStep, log);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async double_click_on_element(x, y, stepName, element) {
|
|
40
|
+
const logLabel = `[Auto-heal] Double clicking at (${x}, ${y}).`;
|
|
41
|
+
this.actions.action_log(logLabel);
|
|
42
|
+
try {
|
|
43
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
44
|
+
await page.mouse.move(x, y);
|
|
45
|
+
await page.mouse.dblclick(x, y);
|
|
46
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
this.printLog(stepName, `[Auto-heal] Exception while double clicking: ${e}`, element);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async right_click_on_element(x, y, stepName, element) {
|
|
53
|
+
const logLabel = `[Auto-heal] Right clicking at (${x}, ${y}).`;
|
|
54
|
+
this.actions.action_log(logLabel);
|
|
55
|
+
try {
|
|
56
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
57
|
+
await page.mouse.move(x, y);
|
|
58
|
+
await page.mouse.click(x, y, { button: 'right' });
|
|
59
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
this.printLog(stepName,`[Auto-heal] Exception while right clicking: ${e}`, element);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async input_value(value, x, y, stepName, element) {
|
|
66
|
+
const logLabel = `[Auto-heal] Typing "${value}" at (${x}, ${y}).`;
|
|
67
|
+
this.actions.action_log(logLabel);
|
|
68
|
+
try {
|
|
69
|
+
const targetSelector = `${Date.now()}`;
|
|
70
|
+
// find the elements at x,y and check if any one of them is of type input/textarea or has roles combobox/textbox or has contentEditableSetto true
|
|
71
|
+
const inputElement = await this.page.evaluate(([x, y, targetSelector]) => {
|
|
72
|
+
const inputElement = document.elementsFromPoint(x, y);
|
|
73
|
+
for(let i=0; i<inputElement.length; i++) {
|
|
74
|
+
const tagName = inputElement[i].tagName.toLowerCase();
|
|
75
|
+
const type = inputElement[i].getAttribute('type');
|
|
76
|
+
const role = inputElement[i].getAttribute('role');
|
|
77
|
+
const nonTextTypes = ['checkbox', 'radio', 'button', 'submit', 'reset', 'file', 'image', 'color', 'range'];
|
|
78
|
+
const contentEditable = inputElement[i].getAttribute('contentEditable');
|
|
79
|
+
if((tagName === 'input' && !nonTextTypes.includes(type)) || tagName === 'textarea' || (role && role.toLowerCase() === 'combobox') || (role && role.toLowerCase() === 'textbox') || (contentEditable && contentEditable.toLowerCase() === 'true')) {
|
|
80
|
+
inputElement[i].setAttribute('tpp-data-target', targetSelector);
|
|
81
|
+
return inputElement[i];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, [x, y, targetSelector]);
|
|
85
|
+
if(!inputElement) {
|
|
86
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
87
|
+
await page.mouse.click(x, y);
|
|
88
|
+
await this.page.waitForTimeout(100);
|
|
89
|
+
const fallbackElement = await this.page.evaluate(([x, y, targetSelector]) => {
|
|
90
|
+
const inputElement = document.activeElement;
|
|
91
|
+
const tagName = inputElement.tagName.toLowerCase();
|
|
92
|
+
const type = inputElement.getAttribute('type');
|
|
93
|
+
const role = inputElement.getAttribute('role');
|
|
94
|
+
const nonTextTypes = ['checkbox', 'radio', 'button', 'submit', 'reset', 'file', 'image', 'color', 'range'];
|
|
95
|
+
const contentEditable = inputElement.getAttribute('contentEditable');
|
|
96
|
+
if((tagName === 'input' && !nonTextTypes.includes(type)) || tagName === 'textarea' || (role && role.toLowerCase() === 'combobox') || (role && role.toLowerCase() === 'textbox') || (contentEditable && contentEditable.toLowerCase() === 'true')) {
|
|
97
|
+
inputElement.setAttribute('tpp-data-target', targetSelector);
|
|
98
|
+
return inputElement;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
, [x, y, targetSelector]);
|
|
102
|
+
if (!fallbackElement) {
|
|
103
|
+
this.printLog(stepName, `[Auto-heal] No suitable element found to type into.`, element);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const locator = await this.page.locator(`[tpp-data-target="${targetSelector}"]`);
|
|
108
|
+
await locator.fill(value);
|
|
109
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
this.printLog(stepName,`[Auto-heal] Exception while typing: ${e}`, element);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async clear_text(x, y, stepName, element) {
|
|
116
|
+
const logLabel = `[Auto-heal] Clearing text at (${x}, ${y}).`;
|
|
117
|
+
this.actions.action_log(logLabel);
|
|
118
|
+
try {
|
|
119
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
120
|
+
await page.mouse.click(x, y);
|
|
121
|
+
const selectAllKey = process.platform === 'darwin' ? 'Meta' : 'Control';
|
|
122
|
+
await page.keyboard.down(selectAllKey);
|
|
123
|
+
await page.keyboard.press('A');
|
|
124
|
+
await page.keyboard.up(selectAllKey);
|
|
125
|
+
await page.keyboard.press('Delete');
|
|
126
|
+
await page.keyboard.press('Backspace');
|
|
127
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
this.printLog(stepName, `[Auto-heal] Exception while clearing text: ${e}`, element);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async press_keys_on_element(keys, x, y, stepName, element) {
|
|
134
|
+
const logLabel = `[Auto-heal] Pressing keys "${keys}" at (${x}, ${y}).`;
|
|
135
|
+
this.actions.action_log(logLabel);
|
|
136
|
+
try {
|
|
137
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
138
|
+
await page.mouse.click(x, y);
|
|
139
|
+
await page.keyboard.press(keys);
|
|
140
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
this.printLog(stepName, `[Auto-heal] Exception while pressing keys: ${e}`, element);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async select_value(value, x, y, stepName, element) {
|
|
147
|
+
const logLabel = `[Auto-heal] Selecting value "${value}" at (${x}, ${y}).`;
|
|
148
|
+
this.actions.action_log(logLabel);
|
|
149
|
+
try {
|
|
150
|
+
const options = this.fromAi ? {delay: 50} : {};
|
|
151
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
152
|
+
await page.mouse.move(x, y);
|
|
153
|
+
await page.selectOption({x, y}, `${value}`, {...options});
|
|
154
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
this.printLog(stepName, `[Auto-heal] Exception while selecting value: ${e}`, element);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async set_element_attribute(key, value, x, y, stepName, element) {
|
|
161
|
+
const logLabel = `[Auto-heal] Setting attribute "${key}" to "${value}" at (${x}, ${y}).`;
|
|
162
|
+
this.actions.action_log(logLabel);
|
|
163
|
+
try {
|
|
164
|
+
await this.page.evaluate(([key, value, x, y]) => {
|
|
165
|
+
const element = document.elementFromPoint(x, y);
|
|
166
|
+
if (element) {
|
|
167
|
+
element.setAttribute(key, value);
|
|
168
|
+
}
|
|
169
|
+
}, [key, value, x, y]);
|
|
170
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
this.printLog(stepName, `[Auto-heal] Exception while setting attribute: ${e}`, element);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async get_element_attribute(key, x, y, stepName, element) {
|
|
177
|
+
const logLabel = `[Auto-heal] Getting attribute "${key}" at (${x}, ${y}).`;
|
|
178
|
+
this.actions.action_log(logLabel);
|
|
179
|
+
let val = null;
|
|
180
|
+
try {
|
|
181
|
+
val = await this.page.evaluate(([key, x, y]) => {
|
|
182
|
+
const element = document.elementFromPoint(x, y);
|
|
183
|
+
return element ? element.getAttribute(key) : null;
|
|
184
|
+
}, [key, x, y]);
|
|
185
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
this.printLog(stepName, `[Auto-heal] Exception while getting attribute: ${e}`, element);
|
|
188
|
+
}
|
|
189
|
+
return val;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async is_element_attribute_present(key, x, y, stepName, element) {
|
|
193
|
+
const logLabel = `[Auto-heal] Checking if attribute "${key}" is present at (${x}, ${y}).`;
|
|
194
|
+
this.actions.action_log(logLabel);
|
|
195
|
+
let present = false;
|
|
196
|
+
try {
|
|
197
|
+
present = await this.page.evaluate(([key, x, y]) => {
|
|
198
|
+
const element = document.elementFromPoint(x, y);
|
|
199
|
+
return element ? element.hasAttribute(key) : false;
|
|
200
|
+
}, [key, x, y]);
|
|
201
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
this.printLog(stepName, `[Auto-heal] Exception while checking attribute: ${e}`, element);
|
|
204
|
+
}
|
|
205
|
+
return present;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async set_input_files(files, x, y, stepName, element) {
|
|
209
|
+
const logLabel = `[Auto-heal] Setting input files at (${x}, ${y}).`;
|
|
210
|
+
this.actions.action_log(logLabel);
|
|
211
|
+
try {
|
|
212
|
+
const fileUtils = new FileUtils();
|
|
213
|
+
const filePaths = files.map(file => fileUtils.getFilePath(file));
|
|
214
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
215
|
+
await page.mouse.move(x, y);
|
|
216
|
+
await this.page.setInputFiles({x, y}, filePaths);
|
|
217
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
this.printLog(stepName, `[Auto-heal] Exception while setting input files: ${e}`, element);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async hover_on_element(x, y, stepName, element) {
|
|
224
|
+
const logLabel = `[Auto-heal] Hovering at (${x}, ${y}).`;
|
|
225
|
+
this.actions.action_log(logLabel);
|
|
226
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
227
|
+
await page.mouse.move(x, y);
|
|
228
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async get_input_element_value(x, y, stepName, element) {
|
|
232
|
+
const logLabel = `[Auto-heal] Getting input value at (${x}, ${y}).`;
|
|
233
|
+
this.actions.action_log(logLabel);
|
|
234
|
+
let value = null;
|
|
235
|
+
try {
|
|
236
|
+
value = await this.page.evaluate(([x, y]) => {
|
|
237
|
+
const element = document.elementFromPoint(x, y);
|
|
238
|
+
return element ? element.value : null;
|
|
239
|
+
}, [x, y]);
|
|
240
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
241
|
+
} catch (e) {
|
|
242
|
+
this.printLog(stepName, `[Auto-heal] Exception while getting input value: ${e}`, element);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async get_element_text(x, y, stepName, element) {
|
|
248
|
+
const logLabel = `[Auto-heal] Getting text at (${x}, ${y}).`;
|
|
249
|
+
this.actions.action_log(logLabel);
|
|
250
|
+
let text = null;
|
|
251
|
+
try {
|
|
252
|
+
text = await this.page.evaluate(([x, y]) => {
|
|
253
|
+
const element = document.elementFromPoint(x, y);
|
|
254
|
+
return element ? element.innerText : null;
|
|
255
|
+
}, [x, y]);
|
|
256
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
this.printLog(stepName, `[Auto-heal] Exception while getting text: ${e}`, element);
|
|
259
|
+
}
|
|
260
|
+
return text;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async get_element_dimensions(x, y, stepName, element) {
|
|
264
|
+
const logLabel = `[Auto-heal] Getting dimensions at (${x}, ${y}).`;
|
|
265
|
+
this.actions.action_log(logLabel);
|
|
266
|
+
let dimensions = null;
|
|
267
|
+
try {
|
|
268
|
+
dimensions = await this.page.evaluate(([x, y]) => {
|
|
269
|
+
const element = document.elementFromPoint(x, y);
|
|
270
|
+
if (element) {
|
|
271
|
+
const rect = element.getBoundingClientRect();
|
|
272
|
+
return {
|
|
273
|
+
width: rect.width,
|
|
274
|
+
height: rect.height,
|
|
275
|
+
x: rect.x,
|
|
276
|
+
y: rect.y,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}, [x, y]);
|
|
281
|
+
await this.printFallbackLocator(x, y, stepName, element);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
this.printLog(stepName, `[Auto-heal] Exception while getting dimensions: ${e}`, element);
|
|
284
|
+
}
|
|
285
|
+
return dimensions;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async printFallbackLocator(x, y, stepName, element) {
|
|
289
|
+
let pref = await this.initPreferences();
|
|
290
|
+
let page = this.page.page && this.page.page() ? this.page.page() : this.page;
|
|
291
|
+
let ele = await page.evaluate(([x, y, pref]) => {
|
|
292
|
+
const element = document.elementFromPoint(x, y);
|
|
293
|
+
const eventElement = window.getTppEventElement ? window.getTppEventElement(element, 'click', { locatorPreference: pref }) : null;
|
|
294
|
+
return eventElement;
|
|
295
|
+
}, [x, y, pref]);
|
|
296
|
+
if (ele && ele.target && ele.target !== element.locator) {
|
|
297
|
+
ele.locator = ele.target
|
|
298
|
+
await this.actions.setFallbackLocators(element, ele);
|
|
299
|
+
ele.elementLabel = element.label;
|
|
300
|
+
let fallbackLog = {
|
|
301
|
+
element: element.label,
|
|
302
|
+
fallback: ele,
|
|
303
|
+
}
|
|
304
|
+
this.printLog(stepName, `[autoheal-locator-used] ${JSON.stringify(fallbackLog)}`, element);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
exports.AutohealActions = AutohealActions;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class ConsoleLogManager {
|
|
2
|
+
|
|
3
|
+
constructor(consoleConfig, page) {
|
|
4
|
+
this.enabled = !!consoleConfig?.enabled;
|
|
5
|
+
if(!consoleConfig?.enabled || !page) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
this.consoleLogs = [];
|
|
10
|
+
this.allowedTypes = consoleConfig.allowedTypes;
|
|
11
|
+
this.startLogTracing(page);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
startLogTracing(page) {
|
|
15
|
+
if(!this.enabled) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const handleLog = (msg) => {
|
|
20
|
+
if(this.allowedTypes && !this.allowedTypes.includes(msg.type()?.toLowerCase())) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.consoleLogs.push({
|
|
24
|
+
type: msg.type(),
|
|
25
|
+
text: msg.text(),
|
|
26
|
+
sourceUrl: msg.location()?.url,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
page.on('console', handleLog);
|
|
31
|
+
|
|
32
|
+
page.context().on('page', async (newPage) => {
|
|
33
|
+
newPage.on('console', handleLog);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
exports.ConsoleLogManager = ConsoleLogManager;
|