@cuppet/core 1.0.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/LICENSE +201 -0
- package/README.md +183 -0
- package/features/app/appiumManager.js +58 -0
- package/features/app/browserManager.js +54 -0
- package/features/app/hooks.js +95 -0
- package/features/app/stepDefinitions/accessibilitySteps.js +17 -0
- package/features/app/stepDefinitions/apiSteps.js +52 -0
- package/features/app/stepDefinitions/appiumSteps.js +15 -0
- package/features/app/stepDefinitions/generalSteps.js +98 -0
- package/features/app/stepDefinitions/helperSteps.js +79 -0
- package/features/app/stepDefinitions/ifVisibleSteps.js +58 -0
- package/features/app/stepDefinitions/iframeSteps.js +61 -0
- package/features/app/stepDefinitions/lighthouseSteps.js +17 -0
- package/features/app/stepDefinitions/pageElements.js +208 -0
- package/features/app/stepDefinitions/pageElementsConfig.js +42 -0
- package/features/app/stepDefinitions/pageElementsJson.js +49 -0
- package/features/app/stepDefinitions/visualRegressionSteps.js +26 -0
- package/features/app/world.js +15 -0
- package/index.js +43 -0
- package/package.json +57 -0
- package/src/accessibilityTesting.js +44 -0
- package/src/apiFunctions.js +290 -0
- package/src/appiumTesting.js +79 -0
- package/src/dataStorage.js +290 -0
- package/src/elementInteraction.js +1003 -0
- package/src/helperFunctions.js +183 -0
- package/src/lighthouse.js +23 -0
- package/src/mainFunctions.js +288 -0
- package/src/visualRegression.js +67 -0
- package/stepDefinitions.js +17 -0
package/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Core Cuppet Framework Components
|
|
2
|
+
// This package provides the core testing framework functionality
|
|
3
|
+
|
|
4
|
+
// Export main function modules
|
|
5
|
+
const elementInteraction = require('./src/elementInteraction');
|
|
6
|
+
const dataStorage = require('./src/dataStorage');
|
|
7
|
+
const mainFunctions = require('./src/mainFunctions');
|
|
8
|
+
const helperFunctions = require('./src/helperFunctions');
|
|
9
|
+
const apiFunctions = require('./src/apiFunctions');
|
|
10
|
+
const appiumTesting = require('./src/appiumTesting');
|
|
11
|
+
const accessibilityTesting = require('./src/accessibilityTesting');
|
|
12
|
+
const lighthouse = require('./src/lighthouse');
|
|
13
|
+
const visualRegression = require('./src/visualRegression');
|
|
14
|
+
|
|
15
|
+
// Export managers
|
|
16
|
+
const BrowserManager = require('./features/app/browserManager');
|
|
17
|
+
const AppiumManager = require('./features/app/appiumManager');
|
|
18
|
+
|
|
19
|
+
// Export step definitions
|
|
20
|
+
const stepDefinitions = require('./stepDefinitions');
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
// Core functions
|
|
24
|
+
elementInteraction,
|
|
25
|
+
dataStorage,
|
|
26
|
+
mainFunctions,
|
|
27
|
+
helperFunctions,
|
|
28
|
+
apiFunctions,
|
|
29
|
+
appiumTesting,
|
|
30
|
+
accessibilityTesting,
|
|
31
|
+
lighthouse,
|
|
32
|
+
visualRegression,
|
|
33
|
+
|
|
34
|
+
// Managers
|
|
35
|
+
BrowserManager,
|
|
36
|
+
AppiumManager,
|
|
37
|
+
|
|
38
|
+
// Step definitions
|
|
39
|
+
stepDefinitions,
|
|
40
|
+
|
|
41
|
+
// Version info
|
|
42
|
+
version: require('./package.json').version
|
|
43
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cuppet/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core testing framework components for Cuppet - BDD framework based on Cucumber and Puppeteer",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/",
|
|
8
|
+
"features/",
|
|
9
|
+
"index.js",
|
|
10
|
+
"stepDefinitions.js"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"testing",
|
|
14
|
+
"bdd",
|
|
15
|
+
"cucumber",
|
|
16
|
+
"puppeteer",
|
|
17
|
+
"appium",
|
|
18
|
+
"automation",
|
|
19
|
+
"e2e"
|
|
20
|
+
],
|
|
21
|
+
"author": "Miroslav Rusev",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@cucumber/cucumber": "^11.0.0",
|
|
25
|
+
"puppeteer": "^24.0.1",
|
|
26
|
+
"config": "^3.3.9"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@supercharge/strings": "^2.0.0",
|
|
30
|
+
"axios": "^1.8.2",
|
|
31
|
+
"backstopjs": "^6.3.23",
|
|
32
|
+
"chai": "^4.3.7",
|
|
33
|
+
"lighthouse": "^12.1.0",
|
|
34
|
+
"mime": "^3.0.0",
|
|
35
|
+
"moment": "^2.30.1",
|
|
36
|
+
"pa11y": "^8.0.0",
|
|
37
|
+
"pa11y-reporter-html": "^2.0.0",
|
|
38
|
+
"xml2js": "^0.6.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@wdio/globals": "^9.14.0",
|
|
42
|
+
"appium": "^2.18.0",
|
|
43
|
+
"appium-uiautomator2-driver": "^4.2.3",
|
|
44
|
+
"webdriverio": "9.12.7"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/MiroslavRusev/cuppet-core.git"
|
|
52
|
+
},
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/MiroslavRusev/cuppet-core/issues"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/MiroslavRusev/cuppet-core#readme"
|
|
57
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module accessibilityTesting
|
|
3
|
+
* @typedef {import('puppeteer').Page} Page
|
|
4
|
+
* @typedef {import('puppeteer').Browser} Browser
|
|
5
|
+
*/
|
|
6
|
+
const config = require('config');
|
|
7
|
+
const storage = require('./dataStorage');
|
|
8
|
+
const helper = require('./helperFunctions');
|
|
9
|
+
const pa11y = require('pa11y');
|
|
10
|
+
const htmlReporter = require('pa11y-reporter-html');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
/**
|
|
14
|
+
* Method to validate if certain path meets the criteria from the config.
|
|
15
|
+
* Please use the config json files to set options.
|
|
16
|
+
* You can find more info at - https://github.com/pa11y/pa11y#configuration
|
|
17
|
+
* @param {Browser} browser - puppeteer browser object
|
|
18
|
+
* @param {Page} page - puppeteer page object
|
|
19
|
+
* @param scenarioName - the current scenario name
|
|
20
|
+
* @param path - the path of the page which accessibility will be tested
|
|
21
|
+
* @throws Error
|
|
22
|
+
* @returns {Promise<void>}
|
|
23
|
+
*/
|
|
24
|
+
validatePageAccessibility: async function (browser, page, scenarioName, path) {
|
|
25
|
+
const pa11yConfig = config.get('pa11yConfig');
|
|
26
|
+
const configOptions = {
|
|
27
|
+
...pa11yConfig,
|
|
28
|
+
browser: browser,
|
|
29
|
+
page: page,
|
|
30
|
+
};
|
|
31
|
+
if (!path.startsWith('http')) {
|
|
32
|
+
throw new Error('Only absolute paths are allowed!');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const results = await pa11y(path, configOptions);
|
|
36
|
+
const fileName = await helper.prepareFileNameFromUrl(path);
|
|
37
|
+
// make the URL ready for filepath usage
|
|
38
|
+
if (results.issues) {
|
|
39
|
+
const html = await htmlReporter.results(results);
|
|
40
|
+
await storage.createHtmlReport('Pa11y-' + scenarioName.slice(0, -1) + fileName, html);
|
|
41
|
+
throw new Error(`${path} page has accessibility issues. HTML report has been generated!`);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const config = require('config');
|
|
3
|
+
const storage = require('./dataStorage');
|
|
4
|
+
const xml2js = require('xml2js');
|
|
5
|
+
const assert = require('chai').assert;
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
/** @type {object} */
|
|
9
|
+
response: null,
|
|
10
|
+
/** @type {object} */
|
|
11
|
+
request: null,
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Prepare path for API test usage
|
|
15
|
+
* @param url - It can be absolute/relative path or even placeholder for saved variable
|
|
16
|
+
* @returns {Promise<*>} - Returns a working path
|
|
17
|
+
*/
|
|
18
|
+
prepareUrl: async function (url) {
|
|
19
|
+
const path = await storage.checkForMultipleVariables(url);
|
|
20
|
+
if (!path.startsWith('http') && config.has('api.baseApiUrl')) {
|
|
21
|
+
return config.get('api.baseApiUrl') + path;
|
|
22
|
+
}
|
|
23
|
+
return path;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Function used to generate the needed headers for each request
|
|
28
|
+
* @async
|
|
29
|
+
* @function setHeaders
|
|
30
|
+
* @param headers
|
|
31
|
+
* @param {boolean} defaultHeadersFlag
|
|
32
|
+
* @returns {Promise<Object>} - Returns an object with the headers
|
|
33
|
+
*/
|
|
34
|
+
setHeaders: async function (headers = {}, defaultHeadersFlag = false) {
|
|
35
|
+
if (!defaultHeadersFlag) {
|
|
36
|
+
return headers;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let defaultHeaders = {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
};
|
|
43
|
+
if (config.has('api.x-api-key')) {
|
|
44
|
+
defaultHeaders['X-Api-Key'] = config.get('api.x-api-key');
|
|
45
|
+
}
|
|
46
|
+
if (config.has('api.Authorization')) {
|
|
47
|
+
defaultHeaders['X-Api-Key'] = config.get('api.Authorization');
|
|
48
|
+
}
|
|
49
|
+
if (headers && defaultHeaders) {
|
|
50
|
+
defaultHeaders = {
|
|
51
|
+
...defaultHeaders,
|
|
52
|
+
...headers,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return defaultHeaders;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Prepare and set the basic auth (if needed).
|
|
60
|
+
* This method supports if the API and the website have different basic auth.
|
|
61
|
+
* @async
|
|
62
|
+
* @function setHeaders
|
|
63
|
+
* @returns {Promise<{Object}>}
|
|
64
|
+
*/
|
|
65
|
+
setBasicAuth: async function () {
|
|
66
|
+
let basicAuth = {};
|
|
67
|
+
if (config.has('api.authUser')) {
|
|
68
|
+
basicAuth = {
|
|
69
|
+
username: config.get('api.authUser'),
|
|
70
|
+
password: config.get('api.authPass'),
|
|
71
|
+
};
|
|
72
|
+
} else if (config.has('basicAuth.authUser')) {
|
|
73
|
+
basicAuth = {
|
|
74
|
+
username: config.get('basicAuth.authUser'),
|
|
75
|
+
password: config.get('basicAuth.authPass'),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return basicAuth;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Sends an HTTP request using axios.
|
|
83
|
+
*
|
|
84
|
+
* @async
|
|
85
|
+
* @function sendRequest
|
|
86
|
+
* @param {string} method - The HTTP method to use for the request.
|
|
87
|
+
* @param {string} [url="/"] - The URL to send the request to. Defaults to "/".
|
|
88
|
+
* @param {Object} [headers={}] - An object containing HTTP headers to include with the request. Defaults to an empty object.
|
|
89
|
+
* @param {Object} [data={}] - An object containing data to send in the body of the request. Defaults to an empty object.
|
|
90
|
+
* @returns {Promise<Object>} Returns a Promise that resolves to the response from the server.
|
|
91
|
+
* @throws {Error} Throws an error if the request fails.
|
|
92
|
+
*/
|
|
93
|
+
sendRequest: async function (method, url = '/', headers = {}, data = {}) {
|
|
94
|
+
const apiUrl = await this.prepareUrl(url);
|
|
95
|
+
const requestHeaders = await this.setHeaders(headers, true);
|
|
96
|
+
const auth = await this.setBasicAuth();
|
|
97
|
+
if (this.request) {
|
|
98
|
+
data = this.request;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
this.response = await axios.request({
|
|
102
|
+
url: apiUrl,
|
|
103
|
+
method: method.toLowerCase(),
|
|
104
|
+
...(Object.keys(auth).length && { auth }),
|
|
105
|
+
// The data is conditionally added to the request, because it's not used with GET requests and creates conflict.
|
|
106
|
+
// The following checks if data object is not empty, returns data object if not empty or skip if empty.
|
|
107
|
+
...(Object.keys(data).length && { data }),
|
|
108
|
+
headers: requestHeaders,
|
|
109
|
+
});
|
|
110
|
+
return this.response;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
throw new Error(`Request failed with: ${error}`);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Replace placeholders of type %var% and prepare request body
|
|
118
|
+
* @async
|
|
119
|
+
* @function prepareRequestBody
|
|
120
|
+
* @param body - the request body needs to be passed in string format
|
|
121
|
+
* @returns {Promise<Object>} - returns the request body object
|
|
122
|
+
*/
|
|
123
|
+
prepareRequestBody: async function (body) {
|
|
124
|
+
const preparedBody = await storage.checkForMultipleVariables(body);
|
|
125
|
+
this.request = JSON.parse(preparedBody);
|
|
126
|
+
return this.request;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Put values in request 1 by 1
|
|
131
|
+
* Example object: {
|
|
132
|
+
* property: value
|
|
133
|
+
* }
|
|
134
|
+
* @async
|
|
135
|
+
* @function prepareRequestBody
|
|
136
|
+
* @param value - the value of the new property
|
|
137
|
+
* @param property - the name of the property
|
|
138
|
+
* @param object - parent object name
|
|
139
|
+
* @returns {Promise<Object>} - returns the request body object
|
|
140
|
+
*/
|
|
141
|
+
iPutValuesInRequestBody: async function (value, property, object) {
|
|
142
|
+
const preparedValue = await storage.checkForVariable(value);
|
|
143
|
+
if (!this.request) {
|
|
144
|
+
this.request = {};
|
|
145
|
+
}
|
|
146
|
+
this.request[object][property] = preparedValue;
|
|
147
|
+
return this.request;
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* This step is used to validate the status code of the response
|
|
152
|
+
* @param code
|
|
153
|
+
* @returns {Promise<void>}
|
|
154
|
+
*/
|
|
155
|
+
validateResponseCode: async function (code) {
|
|
156
|
+
if (this.response.status !== Number(code)) {
|
|
157
|
+
throw new Error(`Response code is different than expected, code: ${this.response.status}`);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Use this step whether the response is of type array or object
|
|
163
|
+
* @param type
|
|
164
|
+
* @returns {Promise<void>}
|
|
165
|
+
*/
|
|
166
|
+
validateResponseType: async function (type) {
|
|
167
|
+
await assert.typeOf(this.response.data, type, `Response is not an ${type}`);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Asynchronously checks if a property of the response data is of a specified type.
|
|
172
|
+
*
|
|
173
|
+
* @async
|
|
174
|
+
* @function propertyIs
|
|
175
|
+
* @param {string} property - The property of the response data to check.
|
|
176
|
+
* @param {string} type - The type that the property should be.
|
|
177
|
+
* @throws {Error} - Will throw an error if the property is not of the specified type.
|
|
178
|
+
*/
|
|
179
|
+
propertyIs: async function (property, type) {
|
|
180
|
+
const value = this.response.data[property];
|
|
181
|
+
await assert.typeOf(value, type, `The property is not an ${type}`);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Validate value of property from the http response.
|
|
186
|
+
* @param property
|
|
187
|
+
* @param expectedValue
|
|
188
|
+
* @returns {Promise<void>}
|
|
189
|
+
*/
|
|
190
|
+
propertyHasValue: async function (property, expectedValue) {
|
|
191
|
+
const actualValue = await this.getPropertyValue(property);
|
|
192
|
+
assert.strictEqual(actualValue, expectedValue, `Property "${property}" does not have the expected value`);
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @async
|
|
197
|
+
* @function iRememberVariable
|
|
198
|
+
* @param property - the name of the JSON property, written in root.parent.child syntax
|
|
199
|
+
* @param variable - the name of the variable to which it will be stored
|
|
200
|
+
* @throws {Error} - if no property is found in the response data
|
|
201
|
+
* @returns {Promise<void>}
|
|
202
|
+
*/
|
|
203
|
+
iRememberVariable: async function (property, variable) {
|
|
204
|
+
const propValue = await this.getPropertyValue(property);
|
|
205
|
+
await storage.iStoreVariableWithValueToTheJsonFile(propValue, variable);
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Go through the response object and return the value of specific property
|
|
210
|
+
* @param property - name of the property. For nested structure use -> parent.child1.child2 etc.
|
|
211
|
+
* @returns {Promise<*>}
|
|
212
|
+
*/
|
|
213
|
+
getPropertyValue: async function (property) {
|
|
214
|
+
const response = this.response.data;
|
|
215
|
+
const keys = property.split('.');
|
|
216
|
+
let value = response;
|
|
217
|
+
for (let key of keys) {
|
|
218
|
+
value = value[key];
|
|
219
|
+
}
|
|
220
|
+
if (!value) {
|
|
221
|
+
throw new Error(`Value with property: ${property} is not found!`);
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Load custom json file and make a request body from it
|
|
228
|
+
* @param path
|
|
229
|
+
* @returns {Promise<Object>}
|
|
230
|
+
*/
|
|
231
|
+
createRequestBodyFromFile: async function (path) {
|
|
232
|
+
this.request = storage.getJsonFile(path);
|
|
233
|
+
return this.request;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Send request to an endpoint and validate whether the response is valid xml.
|
|
238
|
+
* @param url
|
|
239
|
+
* @returns {Promise<void>}
|
|
240
|
+
*/
|
|
241
|
+
validateXMLEndpoint: async function (url) {
|
|
242
|
+
const xmlUrl = await this.prepareUrl(url);
|
|
243
|
+
let response;
|
|
244
|
+
try {
|
|
245
|
+
const auth = await this.setBasicAuth();
|
|
246
|
+
response = await axios.request({
|
|
247
|
+
url: xmlUrl,
|
|
248
|
+
method: 'get',
|
|
249
|
+
...(Object.keys(auth).length && { auth }),
|
|
250
|
+
});
|
|
251
|
+
} catch (error) {
|
|
252
|
+
throw new Error(`Request failed with: ${error}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const isValid = await xml2js.parseStringPromise(response.data);
|
|
256
|
+
if (!isValid) {
|
|
257
|
+
throw new Error('XML is not valid!');
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
*
|
|
263
|
+
* @param {string} method - method - GET,POST, PUT etc.
|
|
264
|
+
* @param {string} currentUrl - url of the page/endpoint
|
|
265
|
+
* @param {object} reqHeaders - request headers
|
|
266
|
+
* @param {string} resHeaders - response headers
|
|
267
|
+
* @param {boolean} flag - direction of presence (true/false)
|
|
268
|
+
* @returns {Promise<void>}
|
|
269
|
+
*/
|
|
270
|
+
validateResponseHeader: async function (method, currentUrl, reqHeaders, resHeaders, flag) {
|
|
271
|
+
const prepareUrl = await this.prepareUrl(currentUrl);
|
|
272
|
+
const requestHeaders = await this.setHeaders(reqHeaders);
|
|
273
|
+
const auth = await this.setBasicAuth();
|
|
274
|
+
let response;
|
|
275
|
+
try {
|
|
276
|
+
response = await axios.request({
|
|
277
|
+
url: prepareUrl,
|
|
278
|
+
method: method,
|
|
279
|
+
...(Object.keys(auth).length && { auth }),
|
|
280
|
+
headers: requestHeaders,
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw new Error(`Request failed with: ${error}`);
|
|
284
|
+
}
|
|
285
|
+
const hasProperty = Object.prototype.hasOwnProperty.call(response.headers, resHeaders.toLowerCase());
|
|
286
|
+
if (hasProperty !== flag) {
|
|
287
|
+
throw new Error('The response headers are different than expected!');
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module appiumTesting
|
|
3
|
+
*/
|
|
4
|
+
const config = require('config');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
/**
|
|
8
|
+
* Handles UiSelector case as it requires special prefixing
|
|
9
|
+
* @param {string} selector
|
|
10
|
+
* @returns {string} - Either the original selector or a UiSelector formatted string
|
|
11
|
+
*/
|
|
12
|
+
prepareSelector: function (selector) {
|
|
13
|
+
if (selector.startsWith('.')) {
|
|
14
|
+
// If the selector starts with ., treat it as a UiSelector
|
|
15
|
+
// For example .text('Example Text') becomes android='new UiSelector().text("Example Text")'
|
|
16
|
+
return `android=new UiSelector()${selector}`;
|
|
17
|
+
} else {
|
|
18
|
+
return selector; // Otherwise, return the selector as is
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {import('@wdio/globals').driver} driver - The WebDriverIO appium instance.
|
|
24
|
+
* @param {string} androidPackage - The name of the Android package.
|
|
25
|
+
* @param {string} activity - The name of the Android activity.
|
|
26
|
+
* @returns {Promise<void>} - Resolves when the session is reloaded successfully.
|
|
27
|
+
* @throws Will throw an error if the element is not found or not clickable.
|
|
28
|
+
*/
|
|
29
|
+
reloadSession: async function (driver, androidPackage, activity) {
|
|
30
|
+
const appiumCapabilities = config.get('appiumCapabilities');
|
|
31
|
+
const capabilities = {
|
|
32
|
+
...appiumCapabilities,
|
|
33
|
+
'appium:appPackage': androidPackage,
|
|
34
|
+
'appium:appActivity': activity,
|
|
35
|
+
};
|
|
36
|
+
// Reload the session with the updated capabilities
|
|
37
|
+
await driver.reloadSession(capabilities);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {import('@wdio/globals')} driver - The WebDriverIO appium instance.
|
|
42
|
+
* @param {string} selector - The selector of the element to click.
|
|
43
|
+
* Possible values for selector: https://webdriver.io/docs/selectors/
|
|
44
|
+
* - A UiSelector string, e.g. '.text("Example Text")'
|
|
45
|
+
* - An ID selector (e.g. 'com.example:id/button1')
|
|
46
|
+
* - A class name selector (e.g. 'android.widget.Button')
|
|
47
|
+
* - An accessibility ID selector (e.g. '~button1')
|
|
48
|
+
* @returns {Promise<void>} - Resolves when the element is clicked successfully.
|
|
49
|
+
* @throws Will throw an error if the element is not found or not clickable.
|
|
50
|
+
*/
|
|
51
|
+
clickElement: async function (driver, selector) {
|
|
52
|
+
try {
|
|
53
|
+
// $ and $$ are not async functions, as Wdio uses lazy loading.
|
|
54
|
+
// The actual element is not fetched until an action is performed like .click(), .getText(), .isDisplayed(), etc.
|
|
55
|
+
const element = driver.$(selector);
|
|
56
|
+
await element.waitForDisplayed({ timeout: 5000 });
|
|
57
|
+
await element.click();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`Error clicking element with selector ${selector}: ${error}`);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {import('@wdio/globals').driver} driver - The WebDriverIO appium instance.
|
|
65
|
+
* @param {string} selector - The selector of the element.
|
|
66
|
+
* @returns {Promise<void>} - Resolves when the element is scrolled into view successfully.
|
|
67
|
+
* @throws Will throw an error if the element is not found.
|
|
68
|
+
*/
|
|
69
|
+
scrollToElement: async function (driver, selector) {
|
|
70
|
+
try {
|
|
71
|
+
await driver.findElement(
|
|
72
|
+
'-android uiautomator',
|
|
73
|
+
`new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().${selector})`
|
|
74
|
+
);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Cannot find element with selector ${selector}: ${error}`);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|