suitest-js-api 3.1.4 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +5 -14
- package/lib/chains/pressButtonChain.js +35 -11
- package/lib/testLauncher/SuitestLauncher.js +5 -1
- package/lib/testLauncher/composeConfig.js +83 -39
- package/lib/texts.js +0 -3
- package/lib/utils/socketErrorMessages.js +1 -11
- package/lib/utils/testLauncherHelper.js +3 -2
- package/package.json +2 -2
package/index.d.ts
CHANGED
|
@@ -85,8 +85,8 @@ declare namespace suitest {
|
|
|
85
85
|
pollUrl(url: string, response: string): PollUrlChain;
|
|
86
86
|
position(x: number, y: number): PositionChain;
|
|
87
87
|
relativePosition(x: number, y: number): RelativePosition;
|
|
88
|
-
press(key: string): PressButtonChain;
|
|
89
|
-
press(keys: string[]): PressButtonChain;
|
|
88
|
+
press(key: string, options?: { longPressMs?: string | number }): PressButtonChain;
|
|
89
|
+
press(keys: string[], options?: { longPressMs?: string | number }): PressButtonChain;
|
|
90
90
|
sleep(milliseconds: number): SleepChain;
|
|
91
91
|
window(): WindowChain;
|
|
92
92
|
|
|
@@ -190,8 +190,8 @@ declare namespace suitest {
|
|
|
190
190
|
pollUrl(url: string, response: string): PollUrlChain;
|
|
191
191
|
position(x: number, y: number): PositionChain;
|
|
192
192
|
relativePosition(x: number, y: number): RelativePosition;
|
|
193
|
-
press(key: string): PressButtonChain;
|
|
194
|
-
press(keys: string[]): PressButtonChain;
|
|
193
|
+
press(key: string, options?: { longPressMs?: string | number }): PressButtonChain;
|
|
194
|
+
press(keys: string[], options?: { longPressMs?: string | number }): PressButtonChain;
|
|
195
195
|
runTest(testId: string): RunTestChain;
|
|
196
196
|
sleep(milliseconds: number): SleepChain;
|
|
197
197
|
window(): WindowChain;
|
|
@@ -232,16 +232,7 @@ declare namespace suitest {
|
|
|
232
232
|
interface DeviceData {
|
|
233
233
|
id: string;
|
|
234
234
|
firmware: string;
|
|
235
|
-
|
|
236
|
-
codeName: string;
|
|
237
|
-
deviceType: string;
|
|
238
|
-
};
|
|
239
|
-
status: {
|
|
240
|
-
type: string;
|
|
241
|
-
canPair: boolean;
|
|
242
|
-
};
|
|
243
|
-
platforms: string[];
|
|
244
|
-
workingPlatforms: string[];
|
|
235
|
+
modelId: string;
|
|
245
236
|
}
|
|
246
237
|
|
|
247
238
|
interface ConfigOverride {
|
|
@@ -20,16 +20,24 @@ const {validate, validators} = require('../validation');
|
|
|
20
20
|
const {getRequestType} = require('../utils/socketChainHelper');
|
|
21
21
|
|
|
22
22
|
const pressButtonFactory = (classInstance) => {
|
|
23
|
-
const toJSON = (data) =>
|
|
24
|
-
|
|
25
|
-
request: compose(
|
|
26
|
-
msg => applyUntilCondition(msg, data),
|
|
27
|
-
msg => applyCountAndDelay(msg, data),
|
|
28
|
-
)({
|
|
23
|
+
const toJSON = (data) => {
|
|
24
|
+
const base = {
|
|
29
25
|
type: 'button',
|
|
30
26
|
ids: data.ids,
|
|
31
|
-
}
|
|
32
|
-
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (data.longPressMs !== undefined) {
|
|
30
|
+
base.longPressMs = data.longPressMs;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
type: getRequestType(data, false),
|
|
35
|
+
request: compose(
|
|
36
|
+
msg => applyUntilCondition(msg, data),
|
|
37
|
+
msg => applyCountAndDelay(msg, data),
|
|
38
|
+
)(base),
|
|
39
|
+
};
|
|
40
|
+
};
|
|
33
41
|
|
|
34
42
|
const toStringComposer = makeToStringComposer(toJSON);
|
|
35
43
|
const thenComposer = makeThenComposer(toJSON);
|
|
@@ -67,18 +75,34 @@ const pressButtonFactory = (classInstance) => {
|
|
|
67
75
|
return output;
|
|
68
76
|
};
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @param {string | string[]} buttonOrButtons
|
|
80
|
+
* @param {{longPressMs?: number}} [options]
|
|
81
|
+
* @returns {PressButtonChain}
|
|
82
|
+
*/
|
|
83
|
+
const pressButtonChain = (buttonOrButtons, options = {}) => {
|
|
71
84
|
const ids = Array.isArray(buttonOrButtons) ? buttonOrButtons : [buttonOrButtons];
|
|
72
85
|
|
|
73
86
|
return makeChain(classInstance, getComposers, {
|
|
74
87
|
type: 'press',
|
|
75
|
-
ids: validate(
|
|
88
|
+
ids: validate(
|
|
89
|
+
validators.ARRAY_OF_BUTTONS,
|
|
90
|
+
ids,
|
|
91
|
+
invalidInputMessage('pressButton', 'Illegal button ids.'),
|
|
92
|
+
),
|
|
93
|
+
longPressMs: options.longPressMs !== undefined
|
|
94
|
+
? validate(
|
|
95
|
+
validators.ST_VAR_OR_POSITIVE_NUMBER,
|
|
96
|
+
options.longPressMs,
|
|
97
|
+
invalidInputMessage('pressButton', 'Invalid longPressMs'),
|
|
98
|
+
)
|
|
99
|
+
: undefined,
|
|
76
100
|
});
|
|
77
101
|
};
|
|
78
102
|
|
|
79
103
|
return {
|
|
80
104
|
pressButton: pressButtonChain,
|
|
81
|
-
pressButtonAssert:
|
|
105
|
+
pressButtonAssert: (...args) => pressButtonChain(...args).toAssert(),
|
|
82
106
|
|
|
83
107
|
// For testing
|
|
84
108
|
toJSON,
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
addLauncherIpcListeners,
|
|
8
8
|
throwDebugForManyDevicesError,
|
|
9
9
|
increaseMaxListeners,
|
|
10
|
+
handleChildResult,
|
|
10
11
|
} = require('../utils/testLauncherHelper');
|
|
11
12
|
const {TEST_COMMAND, TOKEN} = require('../constants/modes');
|
|
12
13
|
const sessionConstants = require('../constants/session');
|
|
@@ -88,6 +89,8 @@ class SuitestLauncher {
|
|
|
88
89
|
throwDebugForManyDevicesError();
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
let finishedWithErrors = false;
|
|
93
|
+
|
|
91
94
|
try {
|
|
92
95
|
const ipcPort = await ipcServer.start({
|
|
93
96
|
...this.ownArgv,
|
|
@@ -101,7 +104,7 @@ class SuitestLauncher {
|
|
|
101
104
|
// increase stdout max listeners based on number of child processes to avoid node warning
|
|
102
105
|
increaseMaxListeners(process.stdout, devices.length, this.ownArgv.concurrency);
|
|
103
106
|
|
|
104
|
-
await runAllDevices(
|
|
107
|
+
finishedWithErrors = await runAllDevices(
|
|
105
108
|
this.restArgs,
|
|
106
109
|
this.ownArgv,
|
|
107
110
|
devicesWithDetails,
|
|
@@ -109,6 +112,7 @@ class SuitestLauncher {
|
|
|
109
112
|
);
|
|
110
113
|
} finally {
|
|
111
114
|
await closeSessionUnchained(suitest);
|
|
115
|
+
handleChildResult(finishedWithErrors);
|
|
112
116
|
}
|
|
113
117
|
} catch (error) {
|
|
114
118
|
await captureException(error);
|
|
@@ -17,33 +17,40 @@ const path = require('path');
|
|
|
17
17
|
const PRESETS = 'presets';
|
|
18
18
|
const APP_NAME = 'suitest';
|
|
19
19
|
const ETC_DIR = '/etc';
|
|
20
|
-
const
|
|
21
|
-
const HOME_DIR =
|
|
20
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
21
|
+
const HOME_DIR = IS_WINDOWS
|
|
22
22
|
? process.env.USERPROFILE
|
|
23
23
|
: process.env.HOME;
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`.${APP_NAME}rc`,
|
|
25
|
+
const CONFIG_FORMATS = [
|
|
26
|
+
'.js',
|
|
27
|
+
'.json',
|
|
28
|
+
'.yaml',
|
|
29
|
+
'.yml',
|
|
30
|
+
'.json5',
|
|
31
|
+
'.ini',
|
|
33
32
|
];
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
|
-
* @description
|
|
37
|
-
* @type {
|
|
35
|
+
* @description default directories to search. Logic the same as in rc
|
|
36
|
+
* @type {
|
|
37
|
+
* {
|
|
38
|
+
* path: string,
|
|
39
|
+
* filename: string,
|
|
40
|
+
* deepSearch: boolean,
|
|
41
|
+
* isGeneral: boolean
|
|
42
|
+
* } []
|
|
43
|
+
* }
|
|
38
44
|
*/
|
|
39
|
-
const
|
|
40
|
-
path.
|
|
41
|
-
path.join(HOME_DIR,
|
|
42
|
-
path.join(HOME_DIR, '.config', APP_NAME
|
|
43
|
-
path.join(HOME_DIR, '
|
|
44
|
-
path
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const DEFAULT_PATHS = [
|
|
46
|
+
{path: process.cwd(), filename: `.${APP_NAME}rc`, deepSearch: true, isGeneral: true},
|
|
47
|
+
{path: path.join(HOME_DIR, '.config', APP_NAME), filename: 'config', deepSearch: false, isGeneral: true},
|
|
48
|
+
{path: path.join(HOME_DIR, '.config'), filename: APP_NAME, deepSearch: false, isGeneral: true},
|
|
49
|
+
{path: path.join(HOME_DIR, `.${APP_NAME}`), filename: 'config', deepSearch: false, isGeneral: true},
|
|
50
|
+
{path: HOME_DIR, filename: `.${APP_NAME}rc`, deepSearch: false, isGeneral: true},
|
|
51
|
+
|
|
52
|
+
{path: path.join(ETC_DIR, APP_NAME), filename: 'config', deepSearch: false, isGeneral: false},
|
|
53
|
+
{path: ETC_DIR, filename: `${APP_NAME}rc`, deepSearch: false, isGeneral: false},
|
|
47
54
|
];
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -109,34 +116,73 @@ function findExtendConfigs(defaultConfigObject, extendPath, filePath, foundPaths
|
|
|
109
116
|
return mainConfigFile;
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @description Search for configuration files.
|
|
121
|
+
* @param {String} pathToSearch path to directory to search
|
|
122
|
+
* @param {String} filename base filename without extension
|
|
123
|
+
*/
|
|
124
|
+
function findConfig(pathToSearch, filename) {
|
|
125
|
+
if (!fs.existsSync(pathToSearch)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const files = fs.readdirSync(pathToSearch);
|
|
129
|
+
const file = files.find(file => {
|
|
130
|
+
const {name, ext} = path.parse(file);
|
|
131
|
+
|
|
132
|
+
return name === filename && (CONFIG_FORMATS.includes(ext) || !ext);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return file ? path.join(pathToSearch, file) : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @description Search for configuration files up to the root.
|
|
140
|
+
* @param {String} pathToSearch path to directory to search
|
|
141
|
+
* @param {String} filename base filename without extension
|
|
142
|
+
*/
|
|
143
|
+
function findConfigUpToRoot(pathToSearch, filename) {
|
|
144
|
+
const foundConfigFile = findConfig(pathToSearch, filename);
|
|
145
|
+
|
|
146
|
+
if (foundConfigFile || path.parse(pathToSearch).root === pathToSearch) {
|
|
147
|
+
return foundConfigFile;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return findConfigUpToRoot(path.join(pathToSearch, '../'), filename);
|
|
151
|
+
}
|
|
152
|
+
|
|
112
153
|
/**
|
|
113
154
|
* Read `.suitestrc` launcher config file.
|
|
155
|
+
* Also, searches for default RC paths.
|
|
114
156
|
* If file not found, return empty object.
|
|
115
|
-
* Supports json
|
|
157
|
+
* Supports json, json5, js, yaml, yml, ini formats.
|
|
158
|
+
* Searches for 'extends' property for other config file and if presents merge them.
|
|
116
159
|
* cli arguments are not parsed.
|
|
117
160
|
* If file found, but json invalid, throw error.
|
|
118
161
|
* @returns {Object}
|
|
119
162
|
*/
|
|
120
163
|
function readRcConfig(pathToConfig) {
|
|
121
164
|
let mainConfigFilePath = '';
|
|
122
|
-
const foundFiles = [];
|
|
123
165
|
|
|
124
166
|
if (pathToConfig) {
|
|
125
|
-
foundFiles.push(pathToConfig);
|
|
126
167
|
mainConfigFilePath = pathToConfig;
|
|
127
168
|
} else {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (files.length > 0) {
|
|
135
|
-
mainConfigFilePath = files[0];
|
|
136
|
-
foundFiles.push(files[0]);
|
|
137
|
-
}
|
|
169
|
+
const defaultConfigurations = DEFAULT_PATHS
|
|
170
|
+
.filter(defaultConfig => IS_WINDOWS ? defaultConfig.isGeneral : true);
|
|
171
|
+
|
|
172
|
+
for (const defaultConfig of defaultConfigurations) {
|
|
173
|
+
if (mainConfigFilePath) {
|
|
174
|
+
break;
|
|
138
175
|
}
|
|
139
|
-
|
|
176
|
+
if (
|
|
177
|
+
fs.existsSync(defaultConfig.path) &&
|
|
178
|
+
fs.lstatSync(defaultConfig.path).isDirectory()
|
|
179
|
+
) {
|
|
180
|
+
mainConfigFilePath = (
|
|
181
|
+
defaultConfig.deepSearch ?
|
|
182
|
+
findConfigUpToRoot :
|
|
183
|
+
findConfig)(defaultConfig.path, defaultConfig.filename);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
140
186
|
}
|
|
141
187
|
|
|
142
188
|
if (!mainConfigFilePath) {
|
|
@@ -151,17 +197,15 @@ function readRcConfig(pathToConfig) {
|
|
|
151
197
|
configFile,
|
|
152
198
|
configFile.extends,
|
|
153
199
|
mainConfigFilePath,
|
|
154
|
-
[
|
|
200
|
+
[mainConfigFilePath],
|
|
155
201
|
),
|
|
156
|
-
|
|
157
|
-
config: foundFiles[0],
|
|
202
|
+
config: mainConfigFilePath,
|
|
158
203
|
};
|
|
159
204
|
}
|
|
160
205
|
|
|
161
206
|
return {
|
|
162
207
|
...configFile,
|
|
163
|
-
|
|
164
|
-
config: foundFiles[0],
|
|
208
|
+
config: mainConfigFilePath,
|
|
165
209
|
};
|
|
166
210
|
}
|
|
167
211
|
|
package/lib/texts.js
CHANGED
|
@@ -158,9 +158,6 @@ ${leaves}`,
|
|
|
158
158
|
// ipc
|
|
159
159
|
ipcFailedToCreateServer: template`Failed to create IPC server. Port ${0} is busy.`,
|
|
160
160
|
|
|
161
|
-
// suffixes
|
|
162
|
-
'suffix.sessionWillClose': () => 'Test session will now close and all remaining Suitest commands will fail.',
|
|
163
|
-
|
|
164
161
|
// logger msg
|
|
165
162
|
sessionOpen: () => 'Connecting to Suitest ...',
|
|
166
163
|
sessionOpened: () => 'Connected to Suitest',
|
|
@@ -133,15 +133,6 @@ function getErrorMessage({response, jsonMessage, verbosity, snippets}) {
|
|
|
133
133
|
return chainUtils.translateLineResult(jsonMessage, verbosity, response);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
/**
|
|
137
|
-
* @description check if error should be considered as fatal
|
|
138
|
-
* @param {Object} res websocket message
|
|
139
|
-
* @returns {boolean}
|
|
140
|
-
*/
|
|
141
|
-
function isErrorFatal(res) {
|
|
142
|
-
return res.result === 'fatal' || normalizeErrorType(res) === 'testIsNotStarted';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
136
|
/**
|
|
146
137
|
* @description Normalize errorType
|
|
147
138
|
* @param {*} response webscoket message
|
|
@@ -169,8 +160,7 @@ module.exports = {
|
|
|
169
160
|
* @returns {string}
|
|
170
161
|
*/
|
|
171
162
|
getInfoErrorMessage: (message, prefix = '', res, stack) => {
|
|
172
|
-
const
|
|
173
|
-
const msg = prefix + stripAnsiChars(message) + suffix;
|
|
163
|
+
const msg = prefix + stripAnsiChars(message);
|
|
174
164
|
const firstStackLine = stack && getFirstStackLine(stack);
|
|
175
165
|
const nl = firstStackLine && msg.endsWith(EOL) ? '' : EOL;
|
|
176
166
|
|
|
@@ -312,7 +312,7 @@ function getChildOptions(device, port) {
|
|
|
312
312
|
* @param {Object} ownArgv - implicitly derived parameters
|
|
313
313
|
* @param {Array} devices - array with items containing full device information
|
|
314
314
|
* @param {number} ipcPort - ipc port number
|
|
315
|
-
* @returns {Promise
|
|
315
|
+
* @returns {Promise<boolean>} - finished with errors or not.
|
|
316
316
|
*/
|
|
317
317
|
function runAllDevices(cmdArgv, ownArgv, devices, ipcPort) {
|
|
318
318
|
const tests = devices.map(device => () => runTestOnDevice(
|
|
@@ -343,7 +343,8 @@ function runAllDevices(cmdArgv, ownArgv, devices, ipcPort) {
|
|
|
343
343
|
|
|
344
344
|
log.final(failedDevices.length, result.length - failedDevices.length);
|
|
345
345
|
warnNewVersionAvailable(logger, version, suitestVersion);
|
|
346
|
-
|
|
346
|
+
|
|
347
|
+
return failedDevices.length !== 0;
|
|
347
348
|
});
|
|
348
349
|
}
|
|
349
350
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suitest-js-api",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": "git@github.com:SuitestAutomation/suitest-js-api.git",
|
|
6
6
|
"author": "Suitest <hello@suite.st>",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
87
|
"@suitest/smst-to-text": "^4.4.3",
|
|
88
|
-
"@suitest/translate": "^4.4.
|
|
88
|
+
"@suitest/translate": "^4.4.4",
|
|
89
89
|
"@types/node": "^14.0.10",
|
|
90
90
|
"ajv": "^6.12.2",
|
|
91
91
|
"ansi-regex": "^5.0.0",
|