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 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
- deviceMeta: {
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
- type: getRequestType(data, false),
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
- const pressButtonChain = buttonOrButtons => {
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(validators.ARRAY_OF_BUTTONS, ids, invalidInputMessage('pressButton', 'Illegal button ids.')),
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: buttonOrButtons => pressButtonChain(buttonOrButtons).toAssert(),
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 IS_WIN = process.platform === 'win32';
21
- const HOME_DIR = IS_WIN
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 CONFIG_FILES = [
26
- `.${APP_NAME}rc.js`,
27
- `.${APP_NAME}rc.json`,
28
- `.${APP_NAME}rc.yaml`,
29
- `.${APP_NAME}rc.yml`,
30
- `.${APP_NAME}rc.json5`,
31
- `.${APP_NAME}rc.ini`,
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 should repeat lookup logic from rc library
37
- * @type {string[]}
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 LOOKUP_DIRS = [
40
- path.join(HOME_DIR, `.${APP_NAME}rc`),
41
- path.join(HOME_DIR, `.${APP_NAME}`, 'config'),
42
- path.join(HOME_DIR, '.config', APP_NAME),
43
- path.join(HOME_DIR, '.config', APP_NAME, 'config'),
44
- path.join(ETC_DIR, `.${APP_NAME}rc`),
45
- path.join(ETC_DIR, APP_NAME, 'config'),
46
- process.cwd(),
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 and ini formats.
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
- LOOKUP_DIRS.forEach((projectDir) => {
129
- if (!mainConfigFilePath && fs.existsSync(projectDir)) {
130
- const files = fs.readdirSync(projectDir)
131
- .filter(fileName => CONFIG_FILES.includes(fileName))
132
- .map(file => path.join(projectDir, file));
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
- [foundFiles[0]],
200
+ [mainConfigFilePath],
155
201
  ),
156
- configs: foundFiles,
157
- config: foundFiles[0],
202
+ config: mainConfigFilePath,
158
203
  };
159
204
  }
160
205
 
161
206
  return {
162
207
  ...configFile,
163
- configs: foundFiles,
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 suffix = isErrorFatal(res) ? ` ${t['suffix.sessionWillClose']()}` : '';
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
- handleChildResult(failedDevices.length !== 0);
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.4",
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.3",
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",