suitest-js-api 3.9.0 → 3.10.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/index.d.ts CHANGED
@@ -127,7 +127,6 @@ declare namespace suitest {
127
127
  model: string,
128
128
  owner: string,
129
129
  firmware: string,
130
- isShared: boolean,
131
130
  modelId: string,
132
131
  platforms: string[],
133
132
  customName?: string,
package/index.js CHANGED
@@ -4,7 +4,10 @@ const suitest = new SUITEST_API({exitOnError: true});
4
4
 
5
5
  // Check if we are in launcher child process, connect to master IPC,
6
6
  // override config, start session, pair device, set app config
7
- connectToIpcAndBootstrapSession(suitest);
7
+ connectToIpcAndBootstrapSession(suitest).catch(function(err) {
8
+ suitest.logger.error(err);
9
+ process.exit(1);
10
+ });
8
11
 
9
12
  // Export public API
10
13
  module.exports = suitest;
@@ -12,7 +12,7 @@ const endpoints = {
12
12
  appConfigById: '/apps/:appId/configs/:configId',
13
13
  appTestDefinitions: '/apps/:appId/test-definitions',
14
14
  appTestDefinitionById: '/apps/:appId/versions/:versionId/tests/:testId',
15
- devices: '/devices',
15
+ device: '/devices/:deviceId',
16
16
  testRun: '/test-run',
17
17
  testRunById: '/test-run/:testRunId',
18
18
  testRunOnDevice: '/test-run/:testRunId/device/:deviceId',
@@ -2,7 +2,7 @@
2
2
  * http request module
3
3
  */
4
4
 
5
- const fetch = require('node-fetch');
5
+ const {fetch} = require('../utils/fetch');
6
6
 
7
7
  const {apiUrl} = require('../../config');
8
8
  const SuitestError = require('../utils/SuitestError');
@@ -15,10 +15,10 @@ const {captureException} = require('../utils/sentry/Raven');
15
15
  *
16
16
  * @param {string|[string, any]} url
17
17
  * @param {Object|any} requestObject
18
- * @param {Function} onReject - will be invoked if response not ok
18
+ * @param {function(Response)} [onFail] - will be invoked if response not ok
19
19
  * @returns {Promise}
20
20
  */
21
- async function request(url, requestObject, onReject) {
21
+ async function request(url, requestObject, onFail) {
22
22
  if (requestObject.body) {
23
23
  requestObject.body = JSON.stringify(requestObject.body);
24
24
 
@@ -47,8 +47,8 @@ async function request(url, requestObject, onReject) {
47
47
  return res.json();
48
48
  }
49
49
 
50
- if (onReject) {
51
- return Promise.reject(onReject(res));
50
+ if (onFail) {
51
+ return onFail(res);
52
52
  }
53
53
 
54
54
  throw new SuitestError(
@@ -7,7 +7,7 @@ const WS = require('ws');
7
7
  const {v1: uuid} = require('uuid');
8
8
  const {path} = require('ramda');
9
9
  const Raven = require('raven');
10
- const fetch = require('node-fetch');
10
+ const {fetch, setUserAgent} = require('../utils/fetch');
11
11
 
12
12
  const SuitestError = require('../utils/SuitestError');
13
13
  const texts = require('../texts');
@@ -31,6 +31,11 @@ const webSocketsFactory = (self) => {
31
31
  disconnect();
32
32
  logger.debug('Initializing websocket connection with options:', connectionOps);
33
33
 
34
+ if (!connectionOps.headers) {
35
+ connectionOps.headers = {};
36
+ }
37
+ setUserAgent(connectionOps.headers);
38
+
34
39
  ws = new WS(config.wsUrl, connectionOps);
35
40
 
36
41
  ws.on('message', msg => {
@@ -132,8 +132,8 @@ const positionFactory = (classInstance) => {
132
132
  return makeChain(classInstance, getComposers, {
133
133
  type: 'position',
134
134
  coordinates: {
135
- x: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, x, invalidInputMessage('position', 'Position x')),
136
- y: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, y, invalidInputMessage('position', 'Position y')),
135
+ x: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, x, invalidInputMessage('position', 'Position x')),
136
+ y: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, y, invalidInputMessage('position', 'Position y')),
137
137
  },
138
138
  });
139
139
  };
@@ -92,7 +92,7 @@ const pressButtonFactory = (classInstance) => {
92
92
  ),
93
93
  longPressMs: options.longPressMs !== undefined
94
94
  ? validate(
95
- validators.ST_VAR_OR_POSITIVE_NUMBER,
95
+ validators.ST_VAR_NOT_NEGATIVE_NUMBER,
96
96
  options.longPressMs,
97
97
  invalidInputMessage('pressButton', 'Invalid longPressMs'),
98
98
  )
@@ -59,7 +59,7 @@ const sleepFactory = (classInstance) => {
59
59
  const sleepChain = milliseconds => makeChain(classInstance, getComposers, {
60
60
  type: 'sleep',
61
61
  milliseconds: validation.validate(
62
- validation.validators.ST_VAR_OR_POSITIVE_NUMBER,
62
+ validation.validators.ST_VAR_NOT_NEGATIVE_NUMBER,
63
63
  milliseconds,
64
64
  invalidInputMessage('sleep', 'Sleep milliseconds')
65
65
  ),
@@ -8,7 +8,7 @@ const {invalidInputMessage} = require('../texts');
8
8
  */
9
9
  const intervalComposer = makeModifierComposer(composers.INTERVAL, ['interval'], (_, meta, value) => ({
10
10
  ...meta,
11
- interval: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, value, invalidInputMessage('interval', 'Interval')),
11
+ interval: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, value, invalidInputMessage('interval', 'Interval')),
12
12
  }));
13
13
 
14
14
  module.exports = intervalComposer;
@@ -8,7 +8,7 @@ const {invalidInputMessage} = require('../texts');
8
8
  */
9
9
  const repeatComposer = makeModifierComposer(composers.REPEAT, ['repeat'], (_, meta, value) => ({
10
10
  ...meta,
11
- repeat: validate(validators.ST_VAR_OR_POSITIVE_NUMBER_NOT_ZERO, value, invalidInputMessage('repeat', 'Repeat')),
11
+ repeat: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, value, invalidInputMessage('repeat', 'Repeat')),
12
12
  }));
13
13
 
14
14
  module.exports = repeatComposer;
@@ -14,7 +14,7 @@ const scrollComposer = makeModifierComposer(composers.TAP, ['scroll'], (_, meta,
14
14
  isScroll: true,
15
15
  direction: validate(validators.DIRECTIONS, direction, invalidInputMessage('scroll', 'direction')),
16
16
  distance: distance !== undefined
17
- ? validate(validators.ST_VAR_OR_POSITIVE_NUMBER, distance, invalidInputMessage('scroll', 'distance'))
17
+ ? validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, distance, invalidInputMessage('scroll', 'distance'))
18
18
  : undefined,
19
19
  };
20
20
  });
@@ -11,8 +11,8 @@ const {invalidInputMessage} = require('../texts');
11
11
  const setSizeComposer = makeModifierComposer(composers.SET_SIZE, ['setSize'], (_, meta, width, height) => ({
12
12
  ...meta,
13
13
  isSetSize: true,
14
- width: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, width, invalidInputMessage('setSize', 'Width')),
15
- height: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, height, invalidInputMessage('setSize', 'Height')),
14
+ width: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, width, invalidInputMessage('setSize', 'Width')),
15
+ height: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, height, invalidInputMessage('setSize', 'Height')),
16
16
  }));
17
17
 
18
18
  module.exports = setSizeComposer;
@@ -14,11 +14,11 @@ const swipeComposer = makeModifierComposer(
14
14
  isSwipe: true,
15
15
  direction: validate(validators.DIRECTIONS, direction, invalidInputMessage('swipe/flick', 'direction')),
16
16
  distance: validate(
17
- validators.ST_VAR_OR_POSITIVE_NUMBER, distance,
17
+ validators.ST_VAR_NOT_NEGATIVE_NUMBER, distance,
18
18
  invalidInputMessage('swipe/flick', 'distance'),
19
19
  ),
20
20
  duration: validate(
21
- validators.ST_VAR_OR_POSITIVE_NUMBER, duration,
21
+ validators.ST_VAR_NOT_NEGATIVE_NUMBER, duration,
22
22
  invalidInputMessage('swipe/flick', 'duration'),
23
23
  ),
24
24
  }));
@@ -7,7 +7,7 @@ const {validate, validators} = require('../validation');
7
7
  */
8
8
  const timeoutComposer = makeModifierComposer(composers.TIMEOUT, ['timeout'], (_, meta, value) => ({
9
9
  ...meta,
10
- timeout: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, value, 'timeout'),
10
+ timeout: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, value, 'timeout'),
11
11
  }));
12
12
 
13
13
  module.exports = timeoutComposer;
@@ -3,6 +3,7 @@ const ENVIRONMENT_VARS = Object.freeze({
3
3
  * @description indicates that session is running as launcher child process, contains deviceId|configId|ipcPortNumber
4
4
  */
5
5
  SUITEST_CHILD_PROCESS: 'SUITEST_CHILD_PROCESS',
6
+ SUITEST_DEVICE_ID: 'SUITEST_DEVICE_ID',
6
7
  SUITEST_PRESET_NAME: 'SUITEST_PRESET_NAME',
7
8
  SUITEST_BE_SERVER: 'SUITEST_BE_SERVER',
8
9
  });
@@ -2,9 +2,9 @@ const validationKeys = {
2
2
  CONFIG_OVERRIDE: Symbol('configOverride'),
3
3
  CONFIGURE: Symbol('configure'),
4
4
  NON_EMPTY_STRING: Symbol('nonEmptyString'),
5
- POSITIVE_NUMBER: Symbol('positiveNumber'),
6
- ST_VAR_OR_POSITIVE_NUMBER: Symbol('stVarOrPositiveNumber'),
7
- ST_VAR_OR_POSITIVE_NUMBER_NOT_ZERO: Symbol('stVarOrPositiveNumberNotZero'),
5
+ NOT_NEGATIVE_NUMBER: Symbol('notNegativeNumber'),
6
+ ST_VAR_NOT_NEGATIVE_NUMBER: Symbol('stVarOrPositiveNumber'),
7
+ ST_VAR_OR_POSITIVE_NUMBER: Symbol('stVarOrPositiveNumberNotZero'),
8
8
  ST_VAR_OR_NUMBER: Symbol('stVarOrNumber'),
9
9
  NON_EMPTY_STRING_OR_UNDEFINED: Symbol('nonEmptyStringOrUndefined'),
10
10
  NON_EMPTY_STRING_OR_NUll: Symbol('nonEmptyStringOrNull'),
@@ -28,7 +28,7 @@ const tokenAllowed = {
28
28
  [endpoints.appConfigById]: ['GET'],
29
29
  [endpoints.appTestDefinitions]: ['GET'],
30
30
  [endpoints.appTestDefinitionById]: ['GET'],
31
- [endpoints.devices]: ['GET'],
31
+ [endpoints.device]: ['GET'],
32
32
  [endpoints.testRun]: ['POST'],
33
33
  [endpoints.testRunById]: ['GET', 'DELETE'],
34
34
  [endpoints.testRunOnDevice]: ['DELETE'],
@@ -0,0 +1,27 @@
1
+ const nodeFetch = require('node-fetch');
2
+ const {name, version} = require('../../package.json');
3
+
4
+ async function fetch(resource, options = {}) {
5
+ if (!options.headers) {
6
+ options.headers = {};
7
+ }
8
+ setUserAgent(options.headers);
9
+
10
+ return await nodeFetch(resource, options);
11
+ }
12
+
13
+ /**
14
+ * Always overwrite user agent to ours
15
+ * @param {Object} headers
16
+ */
17
+ function setUserAgent(headers) {
18
+ const uaHeader = 'user-agent';
19
+ const headerName = Object.keys(headers).find(headerName => headerName.toLowerCase() === uaHeader);
20
+
21
+ headers[headerName || uaHeader] = `${name}/${version}`;
22
+ }
23
+
24
+ module.exports = {
25
+ fetch,
26
+ setUserAgent,
27
+ };
@@ -1,7 +1,8 @@
1
1
  const request = require('../api/request');
2
2
  const endpoints = require('../api/endpoints');
3
- const fetch = require('node-fetch');
4
3
  const R = require('ramda');
4
+ const SuitestError = require('./SuitestError');
5
+ const {suitestServerError} = require('../texts');
5
6
 
6
7
  /**
7
8
  * Return devices details
@@ -10,19 +11,28 @@ const R = require('ramda');
10
11
  * @returns {object}
11
12
  */
12
13
  async function getDevicesDetails({authContext}, devicesList) {
13
- const authorizeHttp = await authContext.authorizeHttp(endpoints.devices, {
14
+ const authorizeHttp = await authContext.authorizeHttp(endpoints.device, {
14
15
  method: 'GET',
15
16
  });
16
- let response = await request(
17
- [endpoints.devices, null, {limit: 100}],
18
- authorizeHttp,
19
- );
20
- let devices = [...response.values];
21
17
 
22
- while (response.next) {
23
- response = await fetch(response.next, authorizeHttp);
24
- response = await response.json();
25
- devices = [...devices, ...response.values];
18
+ let devices = [];
19
+
20
+ for (const {device: deviceId} of devicesList) {
21
+ const response = await request([endpoints.device, {deviceId}], authorizeHttp, function(res) {
22
+ if (res.status === 404) {
23
+ return undefined;
24
+ }
25
+ throw new SuitestError(
26
+ suitestServerError(`[getDeviceDetail(deviceId: ${deviceId})]`, res.status, res.statusText),
27
+ SuitestError.SERVER_ERROR,
28
+ );
29
+ });
30
+ if (response) {
31
+ devices.push({
32
+ deviceId,
33
+ ...response,
34
+ });
35
+ }
26
36
  }
27
37
 
28
38
  return devicesList.reduce((result, d) => {
@@ -57,7 +67,7 @@ function getDeviceName(deviceInfo, short = false) {
57
67
  name = short ? `${model}` : `${manufacturer} ${model}`;
58
68
 
59
69
  if (short && name.length > 15)
60
- name = `${model} (${deviceId.substr(0, 3)})`;
70
+ name = `${model} (${deviceId.slice(0, 3)})`;
61
71
 
62
72
  return name;
63
73
  }
@@ -8,7 +8,7 @@ function makeUrlFromArray(url) {
8
8
  let key, reg;
9
9
  let firstLoop = true;
10
10
 
11
- if (Object.prototype.toString.call(url) === '[object Array]') {
11
+ if (Array.isArray(url)) {
12
12
  // refill variables in url
13
13
  if (url[1]) {
14
14
  for (key in url[1]) {
@@ -3,50 +3,21 @@ const config = require('../../../config');
3
3
  const makeUrlFromArray = require('../makeUrlFromArray');
4
4
  const endpoints = require('../../api/endpoints');
5
5
 
6
- const devices = {
7
- values: [
8
- {
9
- manufacturer: 'Google',
10
- model: 'Chrome',
11
- owner: 'Suitest, Inc.',
12
- firmware: '68.0.3440.106',
13
- isShared: false,
14
- modelId: '046b8cbc-1278-4c01-ae2e-5db509c19d33',
15
- platforms: ['browser'],
16
- status: 'OFF',
17
- },
18
- {
19
- deviceId: '1',
20
- manufacturer: 'Google',
21
- model: 'Chrome',
22
- owner: 'Suitest, Inc.',
23
- firmware: '68.0.3440.106',
24
- isShared: false,
25
- modelId: '046b8cbc-1278-4c01-ae2e-5db509c19d33',
26
- platforms: ['browser'],
27
- status: 'OFF',
28
- },
29
-
30
- {
31
- deviceId: 'deviceId',
32
- manufacturer: 'Google',
33
- model: 'Chrome',
34
- owner: 'Suitest, Inc.',
35
- firmware: '68.0.3440.106',
36
- isShared: false,
37
- modelId: '046b8cbc-1278-4c01-ae2e-5db509c19d33',
38
- platforms: ['browser'],
39
- status: 'OFF',
40
- },
41
-
42
- ],
6
+ const device = {
7
+ manufacturer: 'Google',
8
+ model: 'Chrome',
9
+ owner: 'Suitest, Inc.',
10
+ firmware: '68.0.3440.106',
11
+ isShared: false,
12
+ modelId: '046b8cbc-1278-4c01-ae2e-5db509c19d33',
13
+ platforms: ['browser'],
14
+ status: 'OFF',
43
15
  };
44
16
 
45
17
  function stubDeviceInfoFeed(deviceId) {
46
- devices.values[0].deviceId = deviceId;
47
18
  nock(config.apiUrl)
48
- .get(makeUrlFromArray([endpoints.devices, null, {limit: 100}]))
49
- .reply(200, devices);
19
+ .get(makeUrlFromArray([endpoints.device, {deviceId}]))
20
+ .reply(200, device);
50
21
  }
51
22
 
52
23
  module.exports = stubDeviceInfoFeed;
@@ -14,7 +14,7 @@ process.on('exit', () => {
14
14
  async function setUp() {
15
15
  await testServer.restart();
16
16
 
17
- stubDeviceInfoFeed();
17
+ stubDeviceInfoFeed('deviceId');
18
18
 
19
19
  const suitest = require('../../../index');
20
20
 
@@ -298,6 +298,7 @@ function getChildOptions(device, port) {
298
298
  env: {
299
299
  ...process.env,
300
300
  [envVars.SUITEST_CHILD_PROCESS]: `${device.deviceId}|${device.config}|${port}`,
301
+ [envVars.SUITEST_DEVICE_ID]: device.deviceId,
301
302
  [envVars.SUITEST_PRESET_NAME]: device.presetName,
302
303
  FORCE_COLOR: true,
303
304
  NODE_NO_READLINE: 1, // enable repl in advanced consoles
@@ -28,6 +28,10 @@ function throwError(text) {
28
28
  );
29
29
  }
30
30
 
31
+ function validSuitestVariable(value) {
32
+ return typeof value === 'string' && !/^<%.+%>$/.test(value);
33
+ }
34
+
31
35
  /**
32
36
  * @description returns prettified errors messages for element properties errors
33
37
  * @param {Object} validate
@@ -155,15 +159,15 @@ function validateJsonSchema(schemaKey, data, errorMessage) {
155
159
  return data;
156
160
  }
157
161
 
158
- const validatePositiveNumber = (val, name) => {
162
+ const validateNotNegativeNumber = (val, name) => {
159
163
  if (!Number.isFinite(val) || val < 0) {
160
- throwError(name + ' should be positive number');
164
+ throwError(name + ' should be not negative number');
161
165
  }
162
166
 
163
167
  return val;
164
168
  };
165
169
 
166
- const validatePositiveNumberNotZero = (val, name) => {
170
+ const validatePositiveNumber = (val, name) => {
167
171
  if (!Number.isFinite(val) || val <= 0) {
168
172
  throwError(name + ' should be positive number');
169
173
  }
@@ -179,25 +183,31 @@ const validateNumber = (val, name) => {
179
183
  return val;
180
184
  };
181
185
 
182
- const createStVarOrNumberValidator = (onlyPositiveNumber = false, biggerThanZero = false) => (val, name) => {
183
- if (!['number', 'string'].includes(typeof val)) {
186
+ const createStVarOrNumberValidator = ({
187
+ notNegativeNumbers = false,
188
+ positiveNumbers = false,
189
+ }) => (val, name) => {
190
+ if (
191
+ !['number', 'string'].includes(typeof val)
192
+ || validSuitestVariable(val)
193
+ ) {
184
194
  throwError(name + ' should be suitest configuration variable or number');
185
195
  } else if (typeof val === 'number') {
186
- if (onlyPositiveNumber && biggerThanZero) {
187
- (validatePositiveNumberNotZero)(val, name);
196
+ if (positiveNumbers) {
197
+ validatePositiveNumber(val, name);
198
+ } else if (notNegativeNumbers) {
199
+ validateNotNegativeNumber(val, name);
188
200
  } else {
189
- (onlyPositiveNumber ? validatePositiveNumber : validateNumber)(val, name);
201
+ validateNumber(val, name);
190
202
  }
191
- } else if (typeof val === 'string' && !/^<%.+%>$/.test(val)) {
192
- throwError(name + ' should be suitest configuration variable');
193
203
  }
194
204
 
195
205
  return val;
196
206
  };
197
207
 
198
- const validateStVarOrNumber = createStVarOrNumberValidator();
199
- const validateStVarOrPositiveNumber = createStVarOrNumberValidator(true);
200
- const validateStVarOrPositiveNumberNotZero = createStVarOrNumberValidator(true, true);
208
+ const validateStVarOrNumber = createStVarOrNumberValidator({});
209
+ const validateStVarOrPositiveNumber = createStVarOrNumberValidator({notNegativeNumbers: true});
210
+ const validateStVarOrPositiveNumberNotZero = createStVarOrNumberValidator({positiveNumbers: true});
201
211
 
202
212
  const validateNonEmptyStringOrUndefined = (val, name) => {
203
213
  if (typeof val === 'string' && val.length || val === undefined) {
@@ -306,7 +316,7 @@ const validateTapTypeAndDuration = ({tapType, tapDuration}, tapTypeErrorMsg, dur
306
316
 
307
317
  module.exports = {
308
318
  validateJsonSchema,
309
- validatePositiveNumber,
319
+ validateNotNegativeNumber,
310
320
  validateNumber,
311
321
  validateNonEmptyStringOrUndefined,
312
322
  validateNonEmptyStringOrNull,
@@ -8,13 +8,13 @@ const validatorsMap = {
8
8
  [validationKeys.STRING]: (value, text) => {
9
9
  return validators.validateJsonSchema(validationKeys.STRING, value, text);
10
10
  },
11
- [validationKeys.POSITIVE_NUMBER]: (value, text) => {
12
- return validators.validatePositiveNumber(value, text);
11
+ [validationKeys.NOT_NEGATIVE_NUMBER]: (value, text) => {
12
+ return validators.validateNotNegativeNumber(value, text);
13
13
  },
14
- [validationKeys.ST_VAR_OR_POSITIVE_NUMBER]: (value, text) => {
14
+ [validationKeys.ST_VAR_NOT_NEGATIVE_NUMBER]: (value, text) => {
15
15
  return validators.validateStVarOrPositiveNumber(value, text);
16
16
  },
17
- [validationKeys.ST_VAR_OR_POSITIVE_NUMBER_NOT_ZERO]: (value, text) => {
17
+ [validationKeys.ST_VAR_OR_POSITIVE_NUMBER]: (value, text) => {
18
18
  return validators.validateStVarOrPositiveNumberNotZero(value, text);
19
19
  },
20
20
  [validationKeys.ST_VAR_OR_NUMBER]: (value, text) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suitest-js-api",
3
- "version": "3.9.0",
3
+ "version": "3.10.0",
4
4
  "main": "index.js",
5
5
  "repository": "git@github.com:SuitestAutomation/suitest-js-api.git",
6
6
  "author": "Suitest <hello@suite.st>",