@devassure/tpp-engine 0.0.0-stage → 1.8.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/DEV_README.md +19 -0
- package/README.md +6 -3
- 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,1568 @@
|
|
|
1
|
+
const { remote } = require('webdriverio');
|
|
2
|
+
const { Logger, LOG_PREFIX } = require("../utils/logger");
|
|
3
|
+
const { getCapabilities, getConnectionDetails } = require("./web-driver-utils");
|
|
4
|
+
const { setDeviceCloudTestStatus } = require('./device-cloud/device-cloud-service');
|
|
5
|
+
const { TppElement } = require('../models/tpp-mobile-element');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { getScreenshotPath, getScreenshotLog } = require('../utils/screenshot-utils');
|
|
8
|
+
const { FileUtils } = require('../library/file/file-utils');
|
|
9
|
+
const { sleep } = require('../utils/common-utils');
|
|
10
|
+
const { MobileRecorderControl } = require('./recorder-utils/mobile-recorder');
|
|
11
|
+
const { getCloudCommand } = require('./device-cloud/commands/device-cloud-commands');
|
|
12
|
+
|
|
13
|
+
class MobileActions {
|
|
14
|
+
|
|
15
|
+
constructor(config, retry, totalRetries, projectInfo, timeouts) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.retry = retry;
|
|
18
|
+
this.totalRetries = totalRetries;
|
|
19
|
+
this.projectInfo = projectInfo;
|
|
20
|
+
this.timeouts = timeouts;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
newChild(name) {
|
|
24
|
+
const childActions = new MobileActions(this.config, this.retry, this.totalRetries, this.projectInfo);
|
|
25
|
+
childActions.driver = this.driver;
|
|
26
|
+
childActions.app = this.app;
|
|
27
|
+
childActions.caller = this.caller + " :: " + name;
|
|
28
|
+
childActions.blacklistedLogs = this.blacklistedLogs;
|
|
29
|
+
return childActions;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setCaller(caller) {
|
|
33
|
+
this.caller = caller;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setBlacklistedLogs(blacklistedLogs) {
|
|
37
|
+
this.blacklistedLogs = blacklistedLogs;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async connect(app, testLabel) {
|
|
41
|
+
let devicePrettyLog = `App: ${app.name}, Device: ${this.config.device?.name || this.config.device?.id || this.config.cloudDevice?.name}`;
|
|
42
|
+
this.log(undefined, `[MOBILE_OS] ${this.config.os}`);
|
|
43
|
+
this.log(undefined, `[MOBILE_APP] ${app.name}`);
|
|
44
|
+
this.log(undefined, `[MOBILE_DEVICE_NAME] ${this.config.device?.name || this.config.cloudDevice?.name}`);
|
|
45
|
+
if(this.config.device?.id) {
|
|
46
|
+
this.log(undefined, `[MOBILE_DEVICE_ID] ${this.config.device.id}`);
|
|
47
|
+
}
|
|
48
|
+
if(this.config.deviceCloud?.type) {
|
|
49
|
+
this.log(undefined, `[MOBILE_CLOUD] ${this.config.deviceCloud.type}`);
|
|
50
|
+
devicePrettyLog = `${devicePrettyLog}, Cloud: ${this.config.deviceCloud.type}`;
|
|
51
|
+
}
|
|
52
|
+
this.log(undefined, devicePrettyLog);
|
|
53
|
+
const logLabel = `Connect to device and install App`;
|
|
54
|
+
this.startLog(logLabel);
|
|
55
|
+
this.app = app;
|
|
56
|
+
try {
|
|
57
|
+
//TODO read timeouts from this.timeouts
|
|
58
|
+
const timeouts = {
|
|
59
|
+
inactiveTimeout: this.timeouts?.inactivityTimeout || 2 * 60, //2 minutes
|
|
60
|
+
}
|
|
61
|
+
const capabilities = getCapabilities(this.config, app, {projectInfo: this.projectInfo, label: testLabel}, timeouts);
|
|
62
|
+
const connection = getConnectionDetails(this.config);
|
|
63
|
+
this.driver = await remote({
|
|
64
|
+
...connection,
|
|
65
|
+
logLevel: 'trace',
|
|
66
|
+
capabilities: capabilities,
|
|
67
|
+
connectionRetryTimeout: 4 * 60 * 1000, // 4 minutes
|
|
68
|
+
});
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
70
|
+
await this.open_app(true);
|
|
71
|
+
} catch (ex) {
|
|
72
|
+
this.failureLog(logLabel);
|
|
73
|
+
if(ex.message.includes("adb: device unauthorized")) {
|
|
74
|
+
throw {message: `Permission issue in running test on device ${this.config.device?.name} , id - ${this.config.device?.id}. Please Check 'Always allow' and tap 'OK' in 'Allow USB Debugging' popup that appears in the device.`}
|
|
75
|
+
}
|
|
76
|
+
if(ex.message.includes("writing to settings requires:android.permission.WRITE_SECURE_SETTINGS")) {
|
|
77
|
+
throw {message: `Permission issue in running test on device ${this.config.device?.name} , id - ${this.config.device?.id}. Please turn ON 'Disable permission monitoring' under Developer Settings -> Apps Section. You might want to restart your phone for this settings to take effect.`}
|
|
78
|
+
}
|
|
79
|
+
if(ex.message.includes('Failed to create session')) {
|
|
80
|
+
throw {message: ex.message.replace('\n', ' ')}
|
|
81
|
+
}
|
|
82
|
+
throw ex;
|
|
83
|
+
}
|
|
84
|
+
this.endLog(logLabel);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async startMobileRecorder() {
|
|
88
|
+
try {
|
|
89
|
+
await (new MobileRecorderControl(this)).openMobileRecorder();
|
|
90
|
+
} catch(error) {
|
|
91
|
+
this.log(undefined, `[RECORDER-ERROR] ${error?.message || 'Unable to read the device state!'}`);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async get_device_details() {
|
|
97
|
+
const logLabel = `Get device details`;
|
|
98
|
+
this.startLog(logLabel);
|
|
99
|
+
|
|
100
|
+
const { os = "UNKNOWN", devices = [], cloudDevices = [], deviceCloud = null } = this.config || {};
|
|
101
|
+
const device = devices.length > 0 ? devices[0] : {};
|
|
102
|
+
const cloudDevice = cloudDevices.length > 0 ? cloudDevices[0] : {};
|
|
103
|
+
|
|
104
|
+
const details = {
|
|
105
|
+
os,
|
|
106
|
+
emulator: device.emulator ?? false,
|
|
107
|
+
id: device.id ?? cloudDevice.id ?? "UNKNOWN",
|
|
108
|
+
name: device.name ?? cloudDevice.name ?? "UNKNOWN"
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (cloudDevices.length > 0) {
|
|
112
|
+
if (os.toLowerCase() === "android" && cloudDevice.androidVersion) {
|
|
113
|
+
details.androidVersion = cloudDevice.androidVersion;
|
|
114
|
+
} else if (os.toLowerCase() === "ios" && cloudDevice.iosVersion) {
|
|
115
|
+
details.iosVersion = cloudDevice.iosVersion;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
if(os.toLowerCase() === "android"){
|
|
120
|
+
details.sdkVersion = device.sdk_version;
|
|
121
|
+
}
|
|
122
|
+
else if (os.toLowerCase() === "ios"){
|
|
123
|
+
details.iosVersion = this.config.device?.os_version;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (deviceCloud) {
|
|
128
|
+
details.deviceCloudType = deviceCloud.type ?? "UNKNOWN";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.endLog(logLabel);
|
|
132
|
+
return details;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async shutdown(status) {
|
|
137
|
+
if(status && this.config?.deviceCloud) {
|
|
138
|
+
await setDeviceCloudTestStatus(this.config, this.driver, status.pass, status.message);
|
|
139
|
+
}
|
|
140
|
+
this.startLog('Close mobile session');
|
|
141
|
+
try {
|
|
142
|
+
let appId = this.config?.os === 'ios' ? this.app?.ios?.bundleId : this.app?.android?.package;
|
|
143
|
+
if(appId) {
|
|
144
|
+
let idParam = this.config?.os === 'ios' ? {bundleId: appId} : {appId: appId};
|
|
145
|
+
const cloudCommand = getCloudCommand(this.config, this.driver);
|
|
146
|
+
if(cloudCommand?.closeApp) {
|
|
147
|
+
await cloudCommand.closeApp();
|
|
148
|
+
} else {
|
|
149
|
+
await this.driver.execute('mobile: terminateApp', idParam);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
await this.driver?.deleteSession();
|
|
154
|
+
}
|
|
155
|
+
this.endLog('Close mobile session');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getSessionDetails() {
|
|
159
|
+
return {
|
|
160
|
+
host: '127.0.0.1',
|
|
161
|
+
port: this.config.appium.port,
|
|
162
|
+
sessionId: this.driver.sessionId,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async unlock_device() {
|
|
167
|
+
const logLabel = `Unlock device`;
|
|
168
|
+
this.startLog(logLabel);
|
|
169
|
+
try {
|
|
170
|
+
const deviceOs = this.config?.os || 'android'
|
|
171
|
+
|
|
172
|
+
if (deviceOs === 'android') {
|
|
173
|
+
const isLocked = await this.driver.isLocked();
|
|
174
|
+
if (isLocked) {
|
|
175
|
+
await this.driver.unlock();
|
|
176
|
+
} else {
|
|
177
|
+
this.log(logLabel, 'Android device is already unlocked.');
|
|
178
|
+
}
|
|
179
|
+
} else if (deviceOs === 'ios') {
|
|
180
|
+
await this.driver.execute('mobile: pressButton', { name: 'home' });
|
|
181
|
+
} else {
|
|
182
|
+
this.failureLog(logLabel);
|
|
183
|
+
throw {message: `Unsupported platform: ${deviceOs}`};
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
this.failureLog(logLabel);
|
|
187
|
+
throw {message: `Error unlocking device: ${error?.message || error}`};
|
|
188
|
+
}
|
|
189
|
+
this.endLog(logLabel);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async lock_device() {
|
|
193
|
+
const logLabel = `Lock device`;
|
|
194
|
+
this.startLog(logLabel);
|
|
195
|
+
try {
|
|
196
|
+
const isLocked = await this.driver.isLocked();
|
|
197
|
+
if (!isLocked) {
|
|
198
|
+
await this.driver.lock();
|
|
199
|
+
} else {
|
|
200
|
+
this.log(logLabel, 'Device is already locked.');
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.failureLog(logLabel);
|
|
204
|
+
throw {message: `Error locking device: ${error?.message || error}`};
|
|
205
|
+
}
|
|
206
|
+
this.endLog(logLabel);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async hide_keyboard() {
|
|
210
|
+
const logLabel = `Hide keyboard`;
|
|
211
|
+
this.startLog(logLabel);
|
|
212
|
+
try {
|
|
213
|
+
await this.driver.hideKeyboard();
|
|
214
|
+
} catch (error) {
|
|
215
|
+
this.failureLog(logLabel);
|
|
216
|
+
throw {message: `Error hiding keyboard: ${error?.message || error}`};
|
|
217
|
+
}
|
|
218
|
+
this.endLog(logLabel);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async is_keyboard_open() {
|
|
222
|
+
const logLabel = `Is keyboard open`;
|
|
223
|
+
this.startLog(logLabel);
|
|
224
|
+
let isOpen = false;
|
|
225
|
+
try {
|
|
226
|
+
const deviceOs = this.config?.os || 'android';
|
|
227
|
+
if (deviceOs === 'android') {
|
|
228
|
+
isOpen = await this.driver.isKeyboardShown();
|
|
229
|
+
} else if (deviceOs === 'ios') {
|
|
230
|
+
const keyboardElement = await this.driver.$('//XCUIElementTypeKeyboard');
|
|
231
|
+
isOpen = await keyboardElement.isDisplayed().catch(() => false);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
this.failureLog(logLabel);
|
|
235
|
+
throw {message: `Error checking keyboard state: ${error?.message || error}`};
|
|
236
|
+
}
|
|
237
|
+
this.endLog(logLabel);
|
|
238
|
+
return isOpen;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async press_back_button() {
|
|
242
|
+
const logLabel = `Press back button`;
|
|
243
|
+
this.startLog(logLabel);
|
|
244
|
+
try {
|
|
245
|
+
await this.driver.back();
|
|
246
|
+
} catch (error) {
|
|
247
|
+
this.failureLog(logLabel);
|
|
248
|
+
throw {message: `Error pressing back button: ${error?.message || error}`};
|
|
249
|
+
}
|
|
250
|
+
this.endLog(logLabel);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async switch_to_landscape() {
|
|
254
|
+
const logLabel = `Switch to landscape orientation`;
|
|
255
|
+
this.startLog(logLabel);
|
|
256
|
+
try {
|
|
257
|
+
await this.driver.setOrientation('LANDSCAPE');
|
|
258
|
+
} catch (error) {
|
|
259
|
+
this.failureLog(logLabel);
|
|
260
|
+
throw {message: `Error switching to landscape orientation: ${error?.message || error}`};
|
|
261
|
+
}
|
|
262
|
+
this.endLog(logLabel);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async switch_to_portrait() {
|
|
266
|
+
const logLabel = `Switch to portrait orientation`;
|
|
267
|
+
this.startLog(logLabel);
|
|
268
|
+
try {
|
|
269
|
+
await this.driver.setOrientation('PORTRAIT');
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.failureLog(logLabel);
|
|
272
|
+
throw {message: `Error switching to portrait orientation: ${error?.message || error}`};
|
|
273
|
+
}
|
|
274
|
+
this.endLog(logLabel);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async get_device_orientation() {
|
|
278
|
+
const logLabel = `Get device orientation`;
|
|
279
|
+
this.startLog(logLabel);
|
|
280
|
+
let orientation;
|
|
281
|
+
try {
|
|
282
|
+
orientation = await this.driver.getOrientation();
|
|
283
|
+
} catch (error) {
|
|
284
|
+
this.failureLog(logLabel);
|
|
285
|
+
throw {message: `Error getting orientation: ${error?.message || error}`};
|
|
286
|
+
}
|
|
287
|
+
this.endLog(logLabel);
|
|
288
|
+
return orientation;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async get_device_resolution() {
|
|
292
|
+
const logLabel = `Get device resolution`;
|
|
293
|
+
this.startLog(logLabel);
|
|
294
|
+
let resolution = null;
|
|
295
|
+
try {
|
|
296
|
+
const {width, height} = await this.driver.getWindowSize();
|
|
297
|
+
resolution = {width, height};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
this.failureLog(logLabel);
|
|
300
|
+
throw {message: `Error getting device resolution: ${error?.message || error}`};
|
|
301
|
+
}
|
|
302
|
+
this.endLog(logLabel);
|
|
303
|
+
return resolution;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async shake_device() {
|
|
307
|
+
const logLabel = `Shake device`;
|
|
308
|
+
this.startLog(logLabel);
|
|
309
|
+
|
|
310
|
+
if(this.config?.os === 'android') {
|
|
311
|
+
this.failureLog(logLabel);
|
|
312
|
+
throw {message: `Shaking device is not supported on Android`};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
await this.driver.shake();
|
|
317
|
+
} catch (error) {
|
|
318
|
+
this.failureLog(logLabel);
|
|
319
|
+
throw {message: `Error shaking device: ${error?.message || error}`};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.endLog(logLabel);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async open_app(skipEmptyId) {
|
|
326
|
+
const logLabel = `Open app`;
|
|
327
|
+
this.startLog(logLabel);
|
|
328
|
+
|
|
329
|
+
const cloudCommand = getCloudCommand(this.config, this.driver);
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
if(cloudCommand?.openApp) {
|
|
333
|
+
await cloudCommand.openApp();
|
|
334
|
+
} else {
|
|
335
|
+
const deviceOs = this.config?.os || 'android'
|
|
336
|
+
let appId = deviceOs === 'ios' ? this.app?.ios?.bundleId : this.app?.android?.package;
|
|
337
|
+
if(!appId) {
|
|
338
|
+
if(!skipEmptyId) {
|
|
339
|
+
throw {message: `Mobile App ${deviceOs === 'ios' ? 'bundleId' : 'packageName'} is empty`};
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
if (deviceOs === 'ios') {
|
|
343
|
+
await this.driver.execute('mobile: launchApp', { bundleId: appId });
|
|
344
|
+
} else {
|
|
345
|
+
await this.driver.execute('mobile: activateApp', { appId: appId });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
this.failureLog(logLabel);
|
|
351
|
+
throw {message: `Error opening app: ${error?.message || error}`};
|
|
352
|
+
}
|
|
353
|
+
this.endLog(logLabel);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async minimise_app(timeInSeconds) {
|
|
357
|
+
const logLabel = `Minimise app`;
|
|
358
|
+
this.startLog(logLabel);
|
|
359
|
+
|
|
360
|
+
const cloudCommand = getCloudCommand(this.config, this.driver);
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
if(cloudCommand?.minimiseApp) {
|
|
364
|
+
await cloudCommand.minimiseApp(timeInSeconds);
|
|
365
|
+
} else {
|
|
366
|
+
await this.driver.execute('mobile: backgroundApp', { seconds: timeInSeconds });
|
|
367
|
+
}
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.failureLog(logLabel);
|
|
370
|
+
throw {message: `Error minimising app: ${error?.message || error}`};
|
|
371
|
+
}
|
|
372
|
+
this.endLog(logLabel);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async close_app() {
|
|
376
|
+
const logLabel = `Close app`;
|
|
377
|
+
this.startLog(logLabel);
|
|
378
|
+
const cloudCommand = getCloudCommand(this.config, this.driver);
|
|
379
|
+
try {
|
|
380
|
+
if(cloudCommand?.closeApp) {
|
|
381
|
+
await cloudCommand.closeApp();
|
|
382
|
+
} else {
|
|
383
|
+
let appId = this.config?.os === 'ios' ? this.app?.ios?.bundleId : this.app?.android?.package;
|
|
384
|
+
if(appId) {
|
|
385
|
+
let idParam = this.config?.os === 'ios' ? {bundleId: appId} : {appId: appId};
|
|
386
|
+
await this.driver.execute('mobile: terminateApp', idParam);
|
|
387
|
+
} else {
|
|
388
|
+
this.failureLog(logLabel);
|
|
389
|
+
throw {message: `Mobile App ${this.config?.os === 'ios' ? 'bundleId' : 'appId'} is empty`};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
this.failureLog(logLabel);
|
|
394
|
+
throw {message: `Error closing app: ${error?.message || error}`};
|
|
395
|
+
}
|
|
396
|
+
this.endLog(logLabel);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async open_app_id(appId) {
|
|
400
|
+
const logLabel = `Open app with id ${appId}`;
|
|
401
|
+
this.startLog(logLabel);
|
|
402
|
+
try {
|
|
403
|
+
const deviceOs = this.config?.os || 'android'
|
|
404
|
+
if (deviceOs === 'ios') {
|
|
405
|
+
await this.driver.execute('mobile: launchApp', { bundleId: appId });
|
|
406
|
+
} else {
|
|
407
|
+
await this.driver.execute('mobile: activateApp', { appId: appId });
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
this.failureLog(logLabel);
|
|
411
|
+
throw {message: `Error opening app ${appId}: ${error?.message || error}`};
|
|
412
|
+
}
|
|
413
|
+
this.endLog(logLabel);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async close_app_id(appId) {
|
|
417
|
+
const logLabel = `Close app with id ${appId}`;
|
|
418
|
+
this.startLog(logLabel);
|
|
419
|
+
try {
|
|
420
|
+
let idParam = this.config?.os === 'ios' ? {bundleId: appId} : {appId: appId};
|
|
421
|
+
const cloudCommand = getCloudCommand(this.config, this.driver);
|
|
422
|
+
if(cloudCommand?.closeApp) {
|
|
423
|
+
await cloudCommand.closeApp();
|
|
424
|
+
} else {
|
|
425
|
+
await this.driver.execute('mobile: terminateApp', idParam);
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
this.failureLog(logLabel);
|
|
429
|
+
throw {message: `Error closing app ${appId}: ${error?.message || error}`};
|
|
430
|
+
}
|
|
431
|
+
this.endLog(logLabel);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async switch_to_webview() {
|
|
435
|
+
const logLabel = `Switch to Web View`;
|
|
436
|
+
this.startLog(logLabel);
|
|
437
|
+
|
|
438
|
+
let contexts = await this.driver.getContexts();
|
|
439
|
+
contexts = contexts?.filter(context => {
|
|
440
|
+
if(typeof context === 'string') {
|
|
441
|
+
return context.startsWith('WEBVIEW_') && !context.startsWith('WEBVIEW_com.google');
|
|
442
|
+
}
|
|
443
|
+
return context?.id?.includes('WEBVIEW_')
|
|
444
|
+
});
|
|
445
|
+
let targetContext = contexts?.find(context => {
|
|
446
|
+
let appId = this.config?.os === 'ios' ? this.app?.ios?.bundleId : this.app?.android?.package;
|
|
447
|
+
if(!appId) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
if(typeof context === 'string') {
|
|
451
|
+
return context.includes(appId);
|
|
452
|
+
}
|
|
453
|
+
if(context?.bundleId) {
|
|
454
|
+
return context?.bundleId.includes(appId);
|
|
455
|
+
}
|
|
456
|
+
return context?.id?.includes(appId);
|
|
457
|
+
})
|
|
458
|
+
|| contexts[0];
|
|
459
|
+
if(!targetContext) {
|
|
460
|
+
this.failureLog(logLabel);
|
|
461
|
+
throw {messae: 'WebView is not available!'};
|
|
462
|
+
}
|
|
463
|
+
const targetId = typeof targetContext === 'string' ? targetContext : targetContext?.id;
|
|
464
|
+
await this.driver.switchContext(targetId);
|
|
465
|
+
this.endLog(logLabel);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async switch_to_native_app () {
|
|
469
|
+
const logLabel = `Switch to Native App`;
|
|
470
|
+
this.startLog(logLabel);
|
|
471
|
+
|
|
472
|
+
let contexts = await this.driver.getContexts();
|
|
473
|
+
let targetContext = contexts?.find(context => {
|
|
474
|
+
if(typeof context === 'string') {
|
|
475
|
+
return context === 'NATIVE_APP';
|
|
476
|
+
}
|
|
477
|
+
return context?.id?.includes('NATIVE_APP');
|
|
478
|
+
});
|
|
479
|
+
if(!targetContext) {
|
|
480
|
+
this.failureLog(logLabel);
|
|
481
|
+
throw {messae: 'Native App Context is not available!'};
|
|
482
|
+
}
|
|
483
|
+
const targetId = typeof targetContext === 'string' ? targetContext : targetContext?.id;
|
|
484
|
+
await this.driver.switchContext(targetId);
|
|
485
|
+
this.endLog(logLabel);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async waitForElementExists(uiElement, userTimeout) {
|
|
489
|
+
let timeout = userTimeout || ((this.timeouts?.actionTimeout || 30) * 1000);
|
|
490
|
+
await uiElement.waitForExist({
|
|
491
|
+
timeout: timeout,
|
|
492
|
+
timeoutMsg: `Element not present within ${timeout} ms`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async waitForElementVisiblity(uiElement, userTimeout) {
|
|
497
|
+
let timeout = userTimeout || ((this.timeouts?.actionTimeout || 30) * 1000);
|
|
498
|
+
await uiElement.waitForDisplayed({
|
|
499
|
+
timeout: timeout,
|
|
500
|
+
timeoutMsg: `Element not visible within ${timeout} ms`,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async waitForElementActionable(uiElement, userTimeout) {
|
|
505
|
+
await this.waitForElementVisiblity(uiElement);
|
|
506
|
+
let timeout = userTimeout || ((this.timeouts?.actionTimeout || 30) * 1000);
|
|
507
|
+
|
|
508
|
+
await uiElement.waitForEnabled({
|
|
509
|
+
timeout: timeout,
|
|
510
|
+
timeoutMsg: `Element not enabled within ${timeout} ms`,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
//TODO add scroll if necessary
|
|
514
|
+
|
|
515
|
+
//TODO check if there is option to check if element is clickable / interactable
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async getElementCenter(uiElement) {
|
|
519
|
+
const {x, y} = await uiElement.getLocation();
|
|
520
|
+
const {width, height} = await uiElement.getSize();
|
|
521
|
+
return {
|
|
522
|
+
x: Math.round(x + (width / 2)),
|
|
523
|
+
y: Math.round(y + (height / 2)),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async getElementOffset(uiElement, xOffset, yOffset) {
|
|
528
|
+
const {x, y} = await uiElement.getLocation();
|
|
529
|
+
return {
|
|
530
|
+
x: Math.round(x + xOffset),
|
|
531
|
+
y: Math.round(y + yOffset),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async get_element_dimensions(element) {
|
|
536
|
+
const logLabel = `Get dimensions of - ${element.label}`;
|
|
537
|
+
this.startLog(logLabel);
|
|
538
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
539
|
+
let dimensions = null;
|
|
540
|
+
let uiElement = null;
|
|
541
|
+
try {
|
|
542
|
+
uiElement = await tppElement.locator();
|
|
543
|
+
await this.waitForElementExists(uiElement);
|
|
544
|
+
} catch (e) {
|
|
545
|
+
await this.handleElementException(e, tppElement, logLabel);
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const {width, height} = await uiElement.getSize();
|
|
549
|
+
const {x, y} = await uiElement.getLocation();
|
|
550
|
+
dimensions = {width, height, x, y};
|
|
551
|
+
} catch (e) {
|
|
552
|
+
this.failureLog(logLabel);
|
|
553
|
+
throw {message: `Error getting dimensions of ${element.label}: ${e?.message || e}`};
|
|
554
|
+
}
|
|
555
|
+
this.endLog(logLabel);
|
|
556
|
+
return dimensions;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async scroll_element_into_view(parentElement, element) {
|
|
560
|
+
const logLabel = `Scroll ${parentElement.label} until ${element.label} is in view`;
|
|
561
|
+
this.startLog(logLabel);
|
|
562
|
+
|
|
563
|
+
const tppParentElement = await this.tpp_element_or_backup(parentElement, logLabel, 1);
|
|
564
|
+
try {
|
|
565
|
+
let uiElement = await tppParentElement.locator();
|
|
566
|
+
await this.waitForElementExists(uiElement);
|
|
567
|
+
} catch (e) {
|
|
568
|
+
await this.handleElementException(e, tppParentElement, logLabel);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
let timeout = (this.timeouts?.actionTimeout || 30) * 1000;
|
|
572
|
+
try {
|
|
573
|
+
await this.driver.waitUntil(
|
|
574
|
+
async () => {
|
|
575
|
+
const isPresent = await this.wait_for_single_element(element, 1);
|
|
576
|
+
if (isPresent) {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
this.vertical_scroll_element(parentElement);
|
|
581
|
+
|
|
582
|
+
return false;
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
timeout: timeout,
|
|
586
|
+
timeoutMsg: `Element ${element.label} was not found within the ${timeout} ms`,
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
} catch (error) {
|
|
590
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1, 1);
|
|
591
|
+
this.handleElementException(error, tppElement, logLabel, true);
|
|
592
|
+
}
|
|
593
|
+
this.endLog(logLabel);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async horizontal_scroll_element_into_view(parentElement, element) {
|
|
597
|
+
const logLabel = `Scroll horizontally ${parentElement.label} until ${element.label} is in view`;
|
|
598
|
+
this.startLog(logLabel);
|
|
599
|
+
|
|
600
|
+
const tppParentElement = await this.tpp_element_or_backup(parentElement, logLabel, 1);
|
|
601
|
+
try {
|
|
602
|
+
let uiElement = await tppParentElement.locator();
|
|
603
|
+
await this.waitForElementExists(uiElement);
|
|
604
|
+
} catch (e) {
|
|
605
|
+
await this.handleElementException(e, tppParentElement, logLabel);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
let timeout = (this.timeouts?.actionTimeout || 30) * 1000;
|
|
609
|
+
try {
|
|
610
|
+
await this.driver.waitUntil(
|
|
611
|
+
async () => {
|
|
612
|
+
const isPresent = await this.wait_for_single_element(element, 1);
|
|
613
|
+
if (isPresent) {
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
this.horizontal_scroll_element(parentElement);
|
|
618
|
+
|
|
619
|
+
return false;
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
timeout: timeout,
|
|
623
|
+
timeoutMsg: `Element ${element.label} was not found within the ${timeout} ms`,
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
} catch (error) {
|
|
627
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1, 1);
|
|
628
|
+
this.handleElementException(error, tppElement, logLabel, true);
|
|
629
|
+
}
|
|
630
|
+
this.endLog(logLabel);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async click_on_element(element) {
|
|
634
|
+
const logLabel = `Click on element - ${element.label}`;
|
|
635
|
+
this.startLog(logLabel);
|
|
636
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
637
|
+
try {
|
|
638
|
+
let uiElement = await tppElement.locator();
|
|
639
|
+
await this.waitForElementActionable(uiElement);
|
|
640
|
+
await uiElement.click();
|
|
641
|
+
} catch (e) {
|
|
642
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
643
|
+
}
|
|
644
|
+
this.endLog(logLabel);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async tap_on_element(element) {
|
|
648
|
+
const logLabel = `Tap on element - ${element.label}`;
|
|
649
|
+
this.startLog(logLabel);
|
|
650
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
651
|
+
try {
|
|
652
|
+
const uiElement = await tppElement.locator();
|
|
653
|
+
await this.waitForElementActionable(uiElement);
|
|
654
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
655
|
+
await this.driver.performActions([{
|
|
656
|
+
type: "pointer",
|
|
657
|
+
id: "finger1",
|
|
658
|
+
parameters: { pointerType: "touch" },
|
|
659
|
+
actions:[
|
|
660
|
+
{type: "pointerMove", duration:0, x: x, y: y},
|
|
661
|
+
{type: "pointerDown", button: 0},
|
|
662
|
+
{type: "pointerUp", button: 0},
|
|
663
|
+
]
|
|
664
|
+
}]);
|
|
665
|
+
} catch (e) {
|
|
666
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
667
|
+
}
|
|
668
|
+
this.endLog(logLabel);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
//function for tap at given point x,y from top of screen
|
|
672
|
+
async tap_at_point(x, y) {
|
|
673
|
+
const logLabel = `Tap at point (${x}, ${y})`;
|
|
674
|
+
this.startLog(logLabel);
|
|
675
|
+
try {
|
|
676
|
+
await this.driver.performActions([{
|
|
677
|
+
type: "pointer",
|
|
678
|
+
id: "finger1",
|
|
679
|
+
parameters: { pointerType: "touch" },
|
|
680
|
+
actions:[
|
|
681
|
+
{type: "pointerMove", duration:0, x: x, y: y},
|
|
682
|
+
{type: "pointerDown", button: 0},
|
|
683
|
+
{type: "pointerUp", button: 0},
|
|
684
|
+
]
|
|
685
|
+
}]);
|
|
686
|
+
} catch (e) {
|
|
687
|
+
this.failureLog(logLabel);
|
|
688
|
+
throw {message: `Error tapping at point (${x}, ${y}): ${e?.message || e}`};
|
|
689
|
+
}
|
|
690
|
+
this.endLog(logLabel);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
//function for tap on element at given offset x,y from top
|
|
694
|
+
async tap_at_point_on_element(xOffset, yOffset, element) {
|
|
695
|
+
const logLabel = `Tap at point (${xOffset}, ${yOffset}) on element - ${element.label}`;
|
|
696
|
+
this.startLog(logLabel);
|
|
697
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
698
|
+
try {
|
|
699
|
+
const uiElement = await tppElement.locator();
|
|
700
|
+
await this.waitForElementActionable(uiElement);
|
|
701
|
+
const {x, y} = await this.getElementOffset(uiElement, xOffset, yOffset);
|
|
702
|
+
await this.driver.performActions([{
|
|
703
|
+
type: "pointer",
|
|
704
|
+
id: "finger1",
|
|
705
|
+
parameters: { pointerType: "touch" },
|
|
706
|
+
actions:[
|
|
707
|
+
{type: "pointerMove", duration:0, x: x, y: y},
|
|
708
|
+
{type: "pointerDown", button: 0},
|
|
709
|
+
{type: "pointerUp", button: 0},
|
|
710
|
+
]
|
|
711
|
+
}]);
|
|
712
|
+
} catch (e) {
|
|
713
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
714
|
+
}
|
|
715
|
+
this.endLog(logLabel);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async double_tap_on_element(element) {
|
|
719
|
+
const logLabel = `Double tap on element - ${element.label}`;
|
|
720
|
+
this.startLog(logLabel);
|
|
721
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
722
|
+
try {
|
|
723
|
+
const uiElement = await tppElement.locator();
|
|
724
|
+
await this.waitForElementActionable(uiElement);
|
|
725
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
726
|
+
await this.driver.performActions([{
|
|
727
|
+
type: "pointer",
|
|
728
|
+
id: "finger1",
|
|
729
|
+
parameters: { pointerType: "touch" },
|
|
730
|
+
actions:[
|
|
731
|
+
{type: "pointerMove", duration:0, x: x, y: y},
|
|
732
|
+
{type: "pointerDown", button: 0},
|
|
733
|
+
{type: "pointerUp", button: 0},
|
|
734
|
+
{type: "pause", duration: 50},
|
|
735
|
+
{type: "pointerMove", duration:0, x: x, y: y},
|
|
736
|
+
{type: "pointerDown", button: 0},
|
|
737
|
+
{type: "pointerUp", button: 0},
|
|
738
|
+
]
|
|
739
|
+
}]);
|
|
740
|
+
} catch (e) {
|
|
741
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
742
|
+
}
|
|
743
|
+
this.endLog(logLabel);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async tap_on_element_and_hold(element, holdDuration) {
|
|
747
|
+
const logLabel = `Tap on element - ${element.label} and hold for ${holdDuration} ms`;
|
|
748
|
+
this.startLog(logLabel);
|
|
749
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
750
|
+
try {
|
|
751
|
+
const uiElement = await tppElement.locator();
|
|
752
|
+
await this.waitForElementActionable(uiElement);
|
|
753
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
754
|
+
await this.driver.performActions([{
|
|
755
|
+
type: "pointer",
|
|
756
|
+
id: "finger1",
|
|
757
|
+
parameters: { pointerType: "touch" },
|
|
758
|
+
actions:[
|
|
759
|
+
{type: "pointerMove", duration:0, x: x, y: y},
|
|
760
|
+
{type: "pointerDown", button: 0},
|
|
761
|
+
{type: "pause", duration: holdDuration},
|
|
762
|
+
{type: "pointerUp", button: 0},
|
|
763
|
+
]
|
|
764
|
+
}]);
|
|
765
|
+
} catch (e) {
|
|
766
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
767
|
+
}
|
|
768
|
+
this.endLog(logLabel);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async swipeAction(element, dx, dy, logLabel) {
|
|
772
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
773
|
+
try {
|
|
774
|
+
const uiElement = await tppElement.locator();
|
|
775
|
+
await this.waitForElementActionable(uiElement);
|
|
776
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
777
|
+
await this.driver.performActions([{
|
|
778
|
+
type: "pointer",
|
|
779
|
+
id: "finger1",
|
|
780
|
+
parameters: { pointerType: "touch" },
|
|
781
|
+
actions:[
|
|
782
|
+
{ type: "pointerMove", duration: 0, x: x, y: y },
|
|
783
|
+
{ type: "pointerDown", button: 0 },
|
|
784
|
+
{type: "pause", duration: 100},
|
|
785
|
+
{ type: "pointerMove", duration: 200, x: x + dx, y: y + dy },
|
|
786
|
+
{type: "pause", duration: 100},
|
|
787
|
+
{ type: "pointerUp", button: 0 },
|
|
788
|
+
]
|
|
789
|
+
}]);
|
|
790
|
+
} catch (e) {
|
|
791
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
async vertical_scroll_element(element) {
|
|
796
|
+
const logLabel = `Scroll vertically on element - ${element.label}`;
|
|
797
|
+
this.startLog(logLabel);
|
|
798
|
+
await this.swipeAction(element, 0, -800, logLabel);
|
|
799
|
+
this.endLog(logLabel);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async horizontal_scroll_element(element) {
|
|
803
|
+
const logLabel = `Scroll horizontally on element - ${element.label}`;
|
|
804
|
+
this.startLog(logLabel);
|
|
805
|
+
await this.swipeAction(element, -500, 0, logLabel);
|
|
806
|
+
this.endLog(logLabel);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async swipe_up_on_element(element) {
|
|
810
|
+
const logLabel = `Swipe up on element - ${element.label}`;
|
|
811
|
+
this.startLog(logLabel);
|
|
812
|
+
await this.swipeAction(element, 0, -800, logLabel);
|
|
813
|
+
this.endLog(logLabel);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async swipe_down_on_element(element) {
|
|
817
|
+
const logLabel = `Swipe down on element - ${element.label}`;
|
|
818
|
+
this.startLog(logLabel);
|
|
819
|
+
await this.swipeAction(element, 0, 800, logLabel);
|
|
820
|
+
this.endLog(logLabel);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async swipe_left_on_element(element) {
|
|
824
|
+
const logLabel = `Swipe left on element - ${element.label}`;
|
|
825
|
+
this.startLog(logLabel);
|
|
826
|
+
await this.swipeAction(element, -500, 0, logLabel);
|
|
827
|
+
this.endLog(logLabel);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
async swipe_right_on_element(element) {
|
|
831
|
+
const logLabel = `Swip right on element - ${element.label}`;
|
|
832
|
+
this.startLog(logLabel);
|
|
833
|
+
await this.swipeAction(element, 500, 0, logLabel);
|
|
834
|
+
this.endLog(logLabel);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
async swipe_and_hold(element, xDistance, yDistance, holdDuration) {
|
|
838
|
+
const logLabel = `Swipe on element - ${element.label} for distance x = ${xDistance}, y = ${yDistance} and hold for ${holdDuration} ms`;
|
|
839
|
+
this.startLog(logLabel);
|
|
840
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
841
|
+
try {
|
|
842
|
+
const uiElement = await tppElement.locator();
|
|
843
|
+
await this.waitForElementActionable(uiElement);
|
|
844
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
845
|
+
await this.driver.performActions([{
|
|
846
|
+
type: "pointer",
|
|
847
|
+
id: "finger1",
|
|
848
|
+
parameters: { pointerType: "touch" },
|
|
849
|
+
actions:[
|
|
850
|
+
{ type: "pointerMove", duration: 0, x: x, y: y },
|
|
851
|
+
{ type: "pointerDown", button: 0 },
|
|
852
|
+
{type: "pause", duration: 100},
|
|
853
|
+
{ type: "pointerMove", duration: 100, x: x + xDistance, y: y + yDistance },
|
|
854
|
+
{type: "pause", duration: holdDuration},
|
|
855
|
+
{ type: "pointerUp", button: 0 },
|
|
856
|
+
]
|
|
857
|
+
}]);
|
|
858
|
+
} catch (e) {
|
|
859
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
860
|
+
}
|
|
861
|
+
this.endLog(logLabel);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
async pinch_in_on_element(element) {
|
|
865
|
+
const logLabel = `Pinch IN on element - ${element.label}`;
|
|
866
|
+
this.startLog(logLabel);
|
|
867
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
868
|
+
try {
|
|
869
|
+
let uiElement = await tppElement.locator();
|
|
870
|
+
await this.waitForElementActionable(uiElement);
|
|
871
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
872
|
+
|
|
873
|
+
// TODO find dynamic size rather than using 100
|
|
874
|
+
const startDistance = 100 * 2;
|
|
875
|
+
const endDistance = 100 / 2;
|
|
876
|
+
|
|
877
|
+
await this.performPinchAction({
|
|
878
|
+
xProps: {x: x, xStartDistance: startDistance, xEndDistace: endDistance},
|
|
879
|
+
yProps: {y: y, yStartDistance: startDistance, yEndDistace: endDistance},
|
|
880
|
+
});
|
|
881
|
+
} catch (e) {
|
|
882
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
883
|
+
}
|
|
884
|
+
this.endLog(logLabel);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
async pinch_out_on_element(element) {
|
|
888
|
+
const logLabel = `Pinch OUT on element - ${element.label}`;
|
|
889
|
+
this.startLog(logLabel);
|
|
890
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
891
|
+
try {
|
|
892
|
+
let uiElement = await tppElement.locator();
|
|
893
|
+
await this.waitForElementActionable(uiElement);
|
|
894
|
+
const {x, y} = await this.getElementCenter(uiElement);
|
|
895
|
+
|
|
896
|
+
// TODO find dynamic size rather than using 100
|
|
897
|
+
const startDistance = 100 / 2;
|
|
898
|
+
const endDistance = 100 * 2;
|
|
899
|
+
|
|
900
|
+
await this.performPinchAction({
|
|
901
|
+
xProps: {x: x, xStartDistance: startDistance, xEndDistace: endDistance},
|
|
902
|
+
yProps: {y: y, yStartDistance: startDistance, yEndDistace: endDistance},
|
|
903
|
+
});
|
|
904
|
+
} catch (e) {
|
|
905
|
+
await this.handleClickException(e, tppElement, logLabel);
|
|
906
|
+
}
|
|
907
|
+
this.endLog(logLabel);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
async performPinchAction({xProps: {x, xStartDistance, xEndDistace}, yProps: {y, yStartDistance, yEndDistace}}) {
|
|
911
|
+
try {
|
|
912
|
+
await this.driver.performActions([
|
|
913
|
+
{
|
|
914
|
+
type: "pointer",
|
|
915
|
+
id: "finger1",
|
|
916
|
+
parameters: { pointerType: "touch" },
|
|
917
|
+
actions: [
|
|
918
|
+
{ type: "pointerMove", duration: 0, x: x - xStartDistance, y: y - yStartDistance },
|
|
919
|
+
{ type: "pointerDown", button: 0 },
|
|
920
|
+
{ type: "pointerMove", duration: 0, x: x - xEndDistace, y: y - yEndDistace },
|
|
921
|
+
{ type: "pointerUp", button: 0 },
|
|
922
|
+
]
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
type: "pointer",
|
|
926
|
+
id: "finger2",
|
|
927
|
+
parameters: { pointerType: "touch" },
|
|
928
|
+
actions: [
|
|
929
|
+
{ type: "pointerMove", duration: 0, x: x + xStartDistance, y: y + yStartDistance },
|
|
930
|
+
{ type: "pointerDown", button: 0 },
|
|
931
|
+
{ type: "pointerMove", duration: 0, x: x + xEndDistace, y: y + yEndDistace },
|
|
932
|
+
{ type: "pointerUp", button: 0 },
|
|
933
|
+
]
|
|
934
|
+
},
|
|
935
|
+
]);
|
|
936
|
+
} catch (ex) {
|
|
937
|
+
if(ex.message?.includes("make sure your input actions chain is valid")) {
|
|
938
|
+
//Ignoring as above actions is working. TODO check if subsequent steps are fine
|
|
939
|
+
} else {
|
|
940
|
+
throw ex;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
async input_value(value, element) {
|
|
946
|
+
const logLabel = `Input - ${value}, on element ${element.label}`;
|
|
947
|
+
this.startLog(logLabel);
|
|
948
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
949
|
+
try {
|
|
950
|
+
let uiElement = await tppElement.locator();
|
|
951
|
+
try {
|
|
952
|
+
await uiElement.setValue(value);
|
|
953
|
+
} catch (er) {
|
|
954
|
+
this.log(logLabel, `Element ${tppElement.label()} is unstable, retrying...`);
|
|
955
|
+
if(!uiElement.elementId) {
|
|
956
|
+
uiElement = await tppElement.locator();
|
|
957
|
+
await uiElement.setValue(value);
|
|
958
|
+
} else {
|
|
959
|
+
throw er;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
} catch (e) {
|
|
963
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
964
|
+
}
|
|
965
|
+
this.endLog(logLabel);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
async send_keys(value, element) {
|
|
969
|
+
const logLabel = `Send keys - ${value}, to element ${element.label}`;
|
|
970
|
+
this.startLog(logLabel);
|
|
971
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
972
|
+
try {
|
|
973
|
+
let uiElement = await tppElement.locator();
|
|
974
|
+
const eleId = uiElement.elementId;
|
|
975
|
+
this.driver.elementSendKeys(eleId, value);
|
|
976
|
+
} catch (e) {
|
|
977
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
978
|
+
}
|
|
979
|
+
this.endLog(logLabel);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
async typeText(text) {
|
|
983
|
+
if (this.config?.os === 'ios') {
|
|
984
|
+
const actions = text.split('').flatMap(char => ([
|
|
985
|
+
{ type: 'keyDown', value: char },
|
|
986
|
+
{ type: 'keyUp', value: char }
|
|
987
|
+
]));
|
|
988
|
+
|
|
989
|
+
await this.driver.performActions([{
|
|
990
|
+
type: 'key',
|
|
991
|
+
id: 'keyboard',
|
|
992
|
+
actions: actions
|
|
993
|
+
}]);
|
|
994
|
+
} else {
|
|
995
|
+
// Escape spaces with %s for Android shell command
|
|
996
|
+
const escapedText = text.replace(/ /g, '%s');
|
|
997
|
+
|
|
998
|
+
await this.driver.execute('mobile: shell', {
|
|
999
|
+
command: 'input',
|
|
1000
|
+
args: ['text', escapedText]
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async type_text(text) {
|
|
1006
|
+
const logLabel = `Type text - ${text}`;
|
|
1007
|
+
try {
|
|
1008
|
+
this.typeText(text);
|
|
1009
|
+
} catch(err) {
|
|
1010
|
+
this.failureLog(logLabel);
|
|
1011
|
+
throw {message: `Error typing text on device keyboard - ${err?.message || err}`}
|
|
1012
|
+
}
|
|
1013
|
+
this.endLog(logLabel);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
//type_text_with_delay
|
|
1017
|
+
async type_text_with_delay(text, delay) {
|
|
1018
|
+
const logLabel = `Type text - ${text}, with delay of ${delay} ms between keys`;
|
|
1019
|
+
this.startLog(logLabel);
|
|
1020
|
+
|
|
1021
|
+
if(!text?.trim()) {
|
|
1022
|
+
this.log(logLabel, 'Text is empty, skipping typing');
|
|
1023
|
+
this.endLog(logLabel);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (!delay || isNaN(delay) || delay < 0) {
|
|
1028
|
+
this.failureLog(logLabel);
|
|
1029
|
+
throw {message: `Invalid delay value: ${delay}`};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
try {
|
|
1033
|
+
for (const char of text) {
|
|
1034
|
+
await this.typeText(char);
|
|
1035
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
1036
|
+
}
|
|
1037
|
+
} catch(err) {
|
|
1038
|
+
this.failureLog(logLabel);
|
|
1039
|
+
throw {message: `Error typing text on device keyboard - ${err?.message || err}`}
|
|
1040
|
+
}
|
|
1041
|
+
this.endLog(logLabel);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
async clear_text(element) {
|
|
1045
|
+
const logLabel = `Clear text on element ${element.label}`;
|
|
1046
|
+
this.startLog(logLabel);
|
|
1047
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1048
|
+
try {
|
|
1049
|
+
let uiElement = await tppElement.locator();
|
|
1050
|
+
await uiElement.clearValue();
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1053
|
+
}
|
|
1054
|
+
this.endLog(logLabel);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
async wait_for_element_visible(element) {
|
|
1058
|
+
const logLabel = `Wait for - ${element.label} to be visible`;
|
|
1059
|
+
this.startLog(logLabel);
|
|
1060
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1061
|
+
try {
|
|
1062
|
+
let uiElement = await tppElement.locator();
|
|
1063
|
+
await this.waitForElementVisiblity(uiElement);
|
|
1064
|
+
} catch (e) {
|
|
1065
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1066
|
+
}
|
|
1067
|
+
this.endLog(logLabel);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
async wait_for_element_visible_ms(element, timeout) {
|
|
1071
|
+
const logLabel = `Wait for - ${element.label} to be visible within ${timeout} ms`;
|
|
1072
|
+
this.startLog(logLabel);
|
|
1073
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1, (timeout / 1000) || 20000);
|
|
1074
|
+
try {
|
|
1075
|
+
let uiElement = await tppElement.locator();
|
|
1076
|
+
await this.waitForElementVisiblity(uiElement, timeout);
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1079
|
+
}
|
|
1080
|
+
this.endLog(logLabel);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
async wait_for_element(element) {
|
|
1084
|
+
const logLabel = `Wait for - ${element.label} to be present`;
|
|
1085
|
+
this.startLog(logLabel);
|
|
1086
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1087
|
+
try {
|
|
1088
|
+
let uiElement = await tppElement.locator();
|
|
1089
|
+
await this.waitForElementExists(uiElement);
|
|
1090
|
+
} catch (e) {
|
|
1091
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1092
|
+
}
|
|
1093
|
+
this.endLog(logLabel);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async wait_for_element_ms(element, timeout) {
|
|
1097
|
+
const logLabel = `Wait for - ${element.label} to be present within ${timeout} ms`;
|
|
1098
|
+
this.startLog(logLabel);
|
|
1099
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1, (timeout / 1000) || 20000);
|
|
1100
|
+
try {
|
|
1101
|
+
let uiElement = await tppElement.locator();
|
|
1102
|
+
await this.waitForElementExists(uiElement, timeout);
|
|
1103
|
+
} catch (e) {
|
|
1104
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1105
|
+
}
|
|
1106
|
+
this.endLog(logLabel);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async wait_for_element_to_disappear(element) {
|
|
1110
|
+
const logLabel = `Wait for elment - ${element.label} to disappear`;
|
|
1111
|
+
this.startLog(logLabel);
|
|
1112
|
+
const timeout = (this.timeouts?.actionTimeout || 30) * 1000;
|
|
1113
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1114
|
+
try {
|
|
1115
|
+
await this.driver.waitUntil(
|
|
1116
|
+
async () => (await tppElement.getCount()) === 0,
|
|
1117
|
+
{timeout: timeout},
|
|
1118
|
+
);
|
|
1119
|
+
} catch (_ex) {
|
|
1120
|
+
this.failureLog(logLabel);
|
|
1121
|
+
throw {message: `Element is still visible after ${timeout} ms`};
|
|
1122
|
+
}
|
|
1123
|
+
this.endLog(logLabel);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
async wait_for_element_to_disappear_ms(element, duration) {
|
|
1127
|
+
const logLabel = `Wait for elment - ${element.label} to disappear within ${duration} ms`;
|
|
1128
|
+
this.startLog(logLabel);
|
|
1129
|
+
const timeout = duration || ((this.timeouts?.actionTimeout || 30) * 1000);
|
|
1130
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1131
|
+
try {
|
|
1132
|
+
await this.driver.waitUntil(
|
|
1133
|
+
async () => (await tppElement.getCount()) === 0,
|
|
1134
|
+
{timeout: timeout},
|
|
1135
|
+
);
|
|
1136
|
+
} catch (_ex) {
|
|
1137
|
+
this.failureLog(logLabel);
|
|
1138
|
+
throw {message: `Element is still visible after ${timeout} ms`};
|
|
1139
|
+
}
|
|
1140
|
+
this.endLog(logLabel);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
async verify_element_count(element, count) {
|
|
1144
|
+
const logLabel = `Verify if count of - ${element.label} == ${count}`;
|
|
1145
|
+
this.startLog(logLabel);
|
|
1146
|
+
count = Number(count);
|
|
1147
|
+
const timeout = (this.timeouts?.actionTimeout || 30) * 1000;
|
|
1148
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, count);
|
|
1149
|
+
try {
|
|
1150
|
+
await this.driver.waitUntil(
|
|
1151
|
+
async () => (await tppElement.getCount()) === count,
|
|
1152
|
+
{timeout: timeout},
|
|
1153
|
+
);
|
|
1154
|
+
} catch (e) {
|
|
1155
|
+
let currentCount = await tppElement.getCount();
|
|
1156
|
+
throw {message: `Count of element is not as expeced: found ${currentCount} vs expected ${count}`}
|
|
1157
|
+
}
|
|
1158
|
+
this.endLog(logLabel);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
async verify_element_text(element, text) {
|
|
1162
|
+
const logLabel = `Verify if text of - ${element.label} == ${text.replaceAll("\n", " ")}`;
|
|
1163
|
+
this.startLog(logLabel);
|
|
1164
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1165
|
+
const timeout = (this.timeouts?.actionTimeout || 30) * 1000;
|
|
1166
|
+
try {
|
|
1167
|
+
await this.driver.waitUntil(
|
|
1168
|
+
async () => (await (await tppElement.locator()).getText()) === text,
|
|
1169
|
+
{timeout: timeout},
|
|
1170
|
+
);
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
await this.waitForElementExists(await tppElement.locator(), 5000);
|
|
1173
|
+
let uiElement = await tppElement.locator();
|
|
1174
|
+
let currentText = await uiElement.getText();
|
|
1175
|
+
throw {message: `Text of element is not as expeced: found ${currentText} vs expected ${text}`}
|
|
1176
|
+
}
|
|
1177
|
+
this.endLog(logLabel);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
async verify_if_element_present(element) {
|
|
1181
|
+
const logLabel = `Verify if element - ${element.label} is present`;
|
|
1182
|
+
this.startLog(logLabel);
|
|
1183
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1184
|
+
try {
|
|
1185
|
+
let uiElement = await tppElement.locator();
|
|
1186
|
+
await this.waitForElementExists(uiElement);
|
|
1187
|
+
} catch (e) {
|
|
1188
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1189
|
+
}
|
|
1190
|
+
this.endLog(logLabel);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
async verify_if_element_visible(element) {
|
|
1194
|
+
const logLabel = `Verify if element - ${element.label} is visible`;
|
|
1195
|
+
this.startLog(logLabel);
|
|
1196
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1197
|
+
try {
|
|
1198
|
+
let uiElement = await tppElement.locator();
|
|
1199
|
+
await this.waitForElementVisiblity(uiElement);
|
|
1200
|
+
} catch (e) {
|
|
1201
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1202
|
+
}
|
|
1203
|
+
this.endLog(logLabel);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
isStaleElementException(e) {
|
|
1207
|
+
return e?.name && e.name === 'stale element reference error';
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// If expected count is 1, wait when multiple elements are matching
|
|
1211
|
+
async tpp_element_or_backup(element, logLabel, expectedCount, timeOutSeconds) {
|
|
1212
|
+
let i = 0;
|
|
1213
|
+
let expected = element.index != null ? 2 : expectedCount;
|
|
1214
|
+
while (i < 4) {
|
|
1215
|
+
try {
|
|
1216
|
+
return await this.getTppElementOrBackup(element, logLabel, expected, timeOutSeconds);
|
|
1217
|
+
} catch (e) {
|
|
1218
|
+
i++;
|
|
1219
|
+
if (i < 4 && this.isStaleElementException(e)) {
|
|
1220
|
+
this.log(logLabel, `Stale Element Reference. Retrying... #${i}`);
|
|
1221
|
+
} else {
|
|
1222
|
+
throw e;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async is_element_visible(element, timeout) {
|
|
1229
|
+
const logLabel = `Is element - ${element.label} visible within ${timeout} ms`;
|
|
1230
|
+
this.startLog(logLabel);
|
|
1231
|
+
if(timeout === -1) timeout = (this.customConfig?.timeouts?.actionTimeout || 30) * 1000;
|
|
1232
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1233
|
+
try {
|
|
1234
|
+
let uiElement = await tppElement.locator();
|
|
1235
|
+
await this.waitForElementVisiblity(uiElement, timeout);
|
|
1236
|
+
} catch (ex) {
|
|
1237
|
+
this.log(logLabel, `Returning false! Exception - ${ex}`);
|
|
1238
|
+
this.endLog(logLabel);
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
this.endLog(logLabel);
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
async is_element_present(element, timeout) {
|
|
1246
|
+
const logLabel = `Is element - ${element.label} present within ${timeout} ms`;
|
|
1247
|
+
this.startLog(logLabel);
|
|
1248
|
+
if(timeout === -1) timeout = (this.customConfig?.timeouts?.actionTimeout || 30) * 1000;
|
|
1249
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1250
|
+
try {
|
|
1251
|
+
let uiElement = await tppElement.locator();
|
|
1252
|
+
await this.waitForElementExists(uiElement, timeout);
|
|
1253
|
+
} catch (ex) {
|
|
1254
|
+
this.log(logLabel, `Returning false! Exception - ${ex}`);
|
|
1255
|
+
this.endLog(logLabel);
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
this.endLog(logLabel);
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
async is_nth_element_present(element, n, timeout) {
|
|
1263
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1264
|
+
let exception = undefined;
|
|
1265
|
+
const startTime = Date.now();
|
|
1266
|
+
while (Date.now() - startTime < (timeout || 10000)) {
|
|
1267
|
+
exception = undefined; //Ignore the exception other than the last iteration
|
|
1268
|
+
try {
|
|
1269
|
+
const elements = await this.driver.$$(tppElement.getSelector());
|
|
1270
|
+
if (elements.length > n) {
|
|
1271
|
+
return true;
|
|
1272
|
+
}
|
|
1273
|
+
} catch (ex) {
|
|
1274
|
+
exception = ex;
|
|
1275
|
+
}
|
|
1276
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if(exception) {
|
|
1280
|
+
this.log(logLabel, `Returning false! Exception - ${e}`);
|
|
1281
|
+
}
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
async is_element_enabled(element, timeout) {
|
|
1286
|
+
const logLabel = `Is element - ${element.label} enabled within ${timeout} ms`;
|
|
1287
|
+
this.startLog(logLabel);
|
|
1288
|
+
if(timeout === 0) timeout = 100;
|
|
1289
|
+
if(timeout === -1) timeout = (this.customConfig?.timeouts?.actionTimeout || 30) * 1000;
|
|
1290
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1291
|
+
try {
|
|
1292
|
+
let uiElement = await tppElement.locator();
|
|
1293
|
+
await this.waitForElementActionable(uiElement, timeout);
|
|
1294
|
+
} catch (ex) {
|
|
1295
|
+
this.log(logLabel, `Returning false! Exception - ${ex}`);
|
|
1296
|
+
this.endLog(logLabel);
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
1299
|
+
this.endLog(logLabel);
|
|
1300
|
+
return true;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
async is_element_checked(element, timeout) {
|
|
1304
|
+
const logLabel = `Is element - ${element.label} checked within ${timeout} ms`;
|
|
1305
|
+
this.startLog(logLabel);
|
|
1306
|
+
if(timeout === 0) timeout = 100;
|
|
1307
|
+
if(timeout === -1) timeout = (this.customConfig?.timeouts?.actionTimeout || 30) * 1000;
|
|
1308
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1309
|
+
let uiElement;
|
|
1310
|
+
try {
|
|
1311
|
+
uiElement = await tppElement.locator();
|
|
1312
|
+
await this.waitForElementVisiblity(uiElement, timeout);
|
|
1313
|
+
} catch (ex) {
|
|
1314
|
+
this.log(logLabel, `Element not visible - ${ex}`);
|
|
1315
|
+
this.endLog(logLabel);
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
try {
|
|
1319
|
+
await this.driver.waitUntil(
|
|
1320
|
+
async () => {
|
|
1321
|
+
const isChecked = await uiElement.getAttribute('checked');
|
|
1322
|
+
return isChecked === 'true';
|
|
1323
|
+
},
|
|
1324
|
+
{
|
|
1325
|
+
timeout: timeout, // Timeout in milliseconds
|
|
1326
|
+
timeoutMsg: `Element was not checked within ${timeout} ms`,
|
|
1327
|
+
}
|
|
1328
|
+
);
|
|
1329
|
+
} catch (ex) {
|
|
1330
|
+
this.log(logLabel, `Element not checked - ${ex}`);
|
|
1331
|
+
this.endLog(logLabel);
|
|
1332
|
+
return false;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
this.endLog(logLabel);
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
//Unused
|
|
1340
|
+
async waitForElementDisappear(element, userTimeout) {
|
|
1341
|
+
let timeout = userTimeout || ((this.timeouts?.actionTimeout || 30) * 1000);
|
|
1342
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1343
|
+
const startTime = Date.now();
|
|
1344
|
+
let size;
|
|
1345
|
+
while (Date.now() - startTime < timeout) {
|
|
1346
|
+
exception = undefined; //Ignore the exception other than the last iteration
|
|
1347
|
+
try {
|
|
1348
|
+
const elements = await this.driver.$$(tppElement.getSelector());
|
|
1349
|
+
size = elements?.length;
|
|
1350
|
+
if (size && size <= 0) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
} catch (ex) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1357
|
+
}
|
|
1358
|
+
throw {message: `Element is still visible after ${timeout} ms. Count = ${size}`};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
async get_element_text(element) {
|
|
1362
|
+
const logLabel = `Get text of - ${element.label}`;
|
|
1363
|
+
this.startLog(logLabel);
|
|
1364
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1365
|
+
let uiElement = await tppElement.locator();
|
|
1366
|
+
let text = await uiElement.getText();
|
|
1367
|
+
this.endLog(logLabel);
|
|
1368
|
+
return text;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
async get_element_count(element) {
|
|
1372
|
+
const logLabel = `Get count of - ${element.label}`;
|
|
1373
|
+
this.startLog(logLabel);
|
|
1374
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 0);
|
|
1375
|
+
const count = await tppElement.getCount();
|
|
1376
|
+
this.endLog(logLabel);
|
|
1377
|
+
return count;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
async get_element_attribute(attribute, element) {
|
|
1381
|
+
const logLabel = `Get attribute ${attribute} from element ${element.label}`;
|
|
1382
|
+
this.startLog(logLabel);
|
|
1383
|
+
const tppElement = await this.tpp_element_or_backup(element, logLabel, 1);
|
|
1384
|
+
let uiElement = await tppElement.locator();
|
|
1385
|
+
try {
|
|
1386
|
+
let attributeValue = await uiElement.getAttribute(attribute);
|
|
1387
|
+
this.endLog(logLabel);
|
|
1388
|
+
return attributeValue;
|
|
1389
|
+
} catch (e) {
|
|
1390
|
+
if(e.message.includes(" attribute is unknown ")) {
|
|
1391
|
+
this.failureLog(logLabel);
|
|
1392
|
+
throw {message: `Attribute ${attribute} is not present in element ${element.label}`};
|
|
1393
|
+
}
|
|
1394
|
+
await this.handleElementException(e, tppElement, logLabel, true);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
get_nth_element(element, n) {
|
|
1399
|
+
const logLabel = `Get nth element of ${element.label}. n = ${n}`;
|
|
1400
|
+
n = Number(n);
|
|
1401
|
+
this.startLog(logLabel);
|
|
1402
|
+
if(n < 0) {
|
|
1403
|
+
throw {
|
|
1404
|
+
message: `index n = ${n} is invalid. should be >= 0`,
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
this.endLog(logLabel);
|
|
1408
|
+
return {...element, index: n};
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
async wait_for_single_element(element, timeOutSeconds) {
|
|
1412
|
+
const tppElement = new TppElement(element, this.driver);
|
|
1413
|
+
let count = await tppElement.getCount();
|
|
1414
|
+
let i = 0;
|
|
1415
|
+
while(count != 1 && i < timeOutSeconds) {
|
|
1416
|
+
await sleep(1000);
|
|
1417
|
+
count = await tppElement.getCount();
|
|
1418
|
+
i++;
|
|
1419
|
+
}
|
|
1420
|
+
return count === 1;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
async take_screenshot(label) {
|
|
1424
|
+
const logLabel = `Take Screenshot with label ${label}`;
|
|
1425
|
+
this.startLog(logLabel);
|
|
1426
|
+
await this.addScreenshotWithLogs(label);
|
|
1427
|
+
this.endLog(logLabel);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
async addScreenshotWithLogs(imageName) {
|
|
1431
|
+
const filePath = getScreenshotPath(imageName, this.caller, this.retry);
|
|
1432
|
+
try {
|
|
1433
|
+
await this.addScreenshot(filePath);
|
|
1434
|
+
this.action_log(getScreenshotLog(imageName, filePath, this.caller));
|
|
1435
|
+
} catch (ex) {
|
|
1436
|
+
this.exceptionLog(`Error capturing screenshot - ${ex.message || 'screenshot failed'}.`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
async addScreenshot(filePath) {
|
|
1441
|
+
const screenshotBase64 = await this.driver.takeScreenshot();
|
|
1442
|
+
new FileUtils().createParentIfNotPresent(filePath);
|
|
1443
|
+
fs.writeFileSync(filePath, screenshotBase64, 'base64');
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
async getSpecialExceptionMessage(tppElement, ignoreCount) {
|
|
1447
|
+
const eleCount = await tppElement.getCount();
|
|
1448
|
+
if(eleCount <= 0) {
|
|
1449
|
+
return undefined;
|
|
1450
|
+
}
|
|
1451
|
+
if(!ignoreCount && eleCount > 1) {
|
|
1452
|
+
return {message: "More than one element is matching in the DOM"};
|
|
1453
|
+
}
|
|
1454
|
+
//TODO handle other mobile element exceptions
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
async throwElementNotPresent(element, label, expectSingle) {
|
|
1458
|
+
this.failureLog(label);
|
|
1459
|
+
const tppElement = new TppElement(element, this.driver)
|
|
1460
|
+
const specilExcpetion = await this.getSpecialExceptionMessage(tppElement, !expectSingle);
|
|
1461
|
+
let ex = specilExcpetion || {message: `Unable to find element : ${element.label} : ${tppElement.getSelector()}${new Logger().getIndexLog(tppElement)}`}
|
|
1462
|
+
throw ex;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async handleElementException(ex, tppElement, label, expectSingle) {
|
|
1466
|
+
if(ex.source === 'tpp') {
|
|
1467
|
+
this.failureLog(label);
|
|
1468
|
+
throw ex;
|
|
1469
|
+
}
|
|
1470
|
+
const regex = /^elements_\d+\.(\w+) is not a function(?:.*)?$/;
|
|
1471
|
+
const match = ex.message.match(regex);
|
|
1472
|
+
if(match){
|
|
1473
|
+
const missingElement = match[1];
|
|
1474
|
+
this.failureLog(`Element : ${missingElement} not defined in Page file`);
|
|
1475
|
+
throw {message : `Element : ${missingElement} not defined in Page file`};
|
|
1476
|
+
}
|
|
1477
|
+
if(expectSingle && await tppElement.getCount() > 1) {
|
|
1478
|
+
throw {message: "More than one element is matching in the DOM"};
|
|
1479
|
+
}
|
|
1480
|
+
this.action_log("Element exception: ", ex);
|
|
1481
|
+
this.failureLog(label);
|
|
1482
|
+
// const exceptionLines = (ex?.message || ex.toString()).split('\n');
|
|
1483
|
+
// const message = exceptionLines[0].replace(ansiRegex(), '');
|
|
1484
|
+
throw ex;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
async handleClickException(e, tppElement, logLabel, ignoreCount) {
|
|
1488
|
+
let ex = e;
|
|
1489
|
+
console.log("Click exception: ", ex);
|
|
1490
|
+
const specilExcpetion = await this.getSpecialExceptionMessage(tppElement, ignoreCount);
|
|
1491
|
+
if(specilExcpetion) ex = specilExcpetion;
|
|
1492
|
+
await this.handleElementException(ex, tppElement, logLabel);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
async getTppElementOrBackup(element, logLabel, expectedCount, timeOutSeconds) {
|
|
1496
|
+
if (expectedCount === 0) {
|
|
1497
|
+
return new TppElement(element, this.driver);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const checkFn = async (ele) => {
|
|
1501
|
+
if (expectedCount === 1) {
|
|
1502
|
+
return await this.wait_for_single_element(ele, timeOutSeconds || (element.backupLocators?.length ? 10 : this.timeouts?.actionTimeout || 30));
|
|
1503
|
+
}
|
|
1504
|
+
return await this.is_nth_element_present(ele, 0);
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
if (await checkFn(element)) {
|
|
1508
|
+
return new TppElement(element, this.driver);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (!element.backupLocators?.length) {
|
|
1512
|
+
await this.throwElementNotPresent(element, logLabel, expectedCount === 1);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
let i = 0;
|
|
1516
|
+
let tppElement = new TppElement(element, this.driver);
|
|
1517
|
+
while (i < element.backupLocators.length) {
|
|
1518
|
+
new Logger(this.caller, this.blacklistedLogs).elementBackupRetryLog(tppElement, i, logLabel);
|
|
1519
|
+
const backup = element.backupLocators[i];
|
|
1520
|
+
tppElement = new TppElement(
|
|
1521
|
+
{ ...element, ...backup },
|
|
1522
|
+
this.driver,
|
|
1523
|
+
);
|
|
1524
|
+
|
|
1525
|
+
if (await checkFn(tppElement.element)) {
|
|
1526
|
+
new Logger(this.caller, this.blacklistedLogs).logBackupLocatorUsed(tppElement);
|
|
1527
|
+
return tppElement;
|
|
1528
|
+
}
|
|
1529
|
+
i++;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
return new TppElement(element, this.driver);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
log(stepName, msg) {
|
|
1536
|
+
new Logger(this.caller, this.blacklistedLogs).log(stepName, msg);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
startLog(stepName) {
|
|
1540
|
+
new Logger(this.caller, this.blacklistedLogs).startLog(stepName);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
endLog(stepName) {
|
|
1544
|
+
new Logger(this.caller, this.blacklistedLogs).endLog(stepName);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
failureLog(stepName) {
|
|
1548
|
+
new Logger(this.caller, this.blacklistedLogs).failureLog(stepName);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
action_log(log) {
|
|
1552
|
+
new Logger(this.caller, this.blacklistedLogs).action_log(log);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
exceptionLog(message) {
|
|
1556
|
+
if(typeof message === 'string'){
|
|
1557
|
+
const errorLines = message.split('\n');
|
|
1558
|
+
for(let i = 0;i < errorLines.length; i++){
|
|
1559
|
+
this.action_log(`${LOG_PREFIX}${this.caller} :: [EXCEPTION] ${errorLines[i]}`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
else {
|
|
1563
|
+
this.action_log(`${LOG_PREFIX}${this.caller} :: [EXCEPTION] ${message}`);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
exports.MobileActions = MobileActions;
|