suitest-js-api 3.19.1 → 3.20.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
@@ -50,6 +50,7 @@ import {OcrColor} from './typeDefinition/constants/OcrColor';
50
50
  import {ImageChain} from './typeDefinition/ImageChain';
51
51
  import {Accuracy} from './typeDefinition/constants/Accuracy';
52
52
  import {Lang} from './typeDefinition/constants/Langs';
53
+ import {GetLastVTScreenshotChain} from './typeDefinition/GetLastVTScreenshotChain';
53
54
 
54
55
  // --------------- Suitest Interface ---------------------- //
55
56
 
@@ -66,7 +67,7 @@ declare namespace suitest {
66
67
  releaseDevice(): Promise<void|SuitestError>;
67
68
  startREPL(options?: ReplOptions): Promise<void>;
68
69
  getAppConfig(): Promise<AppConfiguration|SuitestError>;
69
- startRecording({webhookUrl}?: {webhookUrl: string}): Promise<void|SuitestError>;
70
+ startRecording({webhookUrl}?: {webhookUrl: string}): Promise<string|SuitestError>;
70
71
  stopRecording({discard}?: {discard: boolean}): Promise<void|SuitestError>;
71
72
 
72
73
  // config
@@ -104,13 +105,13 @@ declare namespace suitest {
104
105
  press(keys: string[], options?: { longPressMs?: string | number }): PressButtonChain;
105
106
  sleep(milliseconds: number): SleepChain;
106
107
  window(): WindowChain;
108
+ setScreenOrientation(orientation: ScreenOrientationValues): SetScreenOrientationChain;
107
109
  /**
108
- * @description return PromiseLike object with Buffer as value
110
+ * @description return PromiseLike object with Buffer as value which represents latest screenshot made for visual testing/assertions
109
111
  */
110
112
  takeScreenshot(dataFormat?: 'raw'): TakeScreenshotChain<Buffer>;
111
- setScreenOrientation(orientation: ScreenOrientationValues): SetScreenOrientationChain;
112
113
  /**
113
- * @description return PromiseLike object with base64 string as value
114
+ * @description return PromiseLike object with base64 string as value which represents latest screenshot made for visual testing/assertions
114
115
  */
115
116
  takeScreenshot(dataFormat: 'base64'): TakeScreenshotChain<string>;
116
117
  /**
@@ -127,6 +128,14 @@ declare namespace suitest {
127
128
  * suitest.saveScreenshot('{screenshotDir}/{dateTime}-{currentFile}-l{currentLine}.png');
128
129
  */
129
130
  saveScreenshot(fileName?: string): TakeScreenshotChain<void>;
131
+ /**
132
+ * @description returns a screenshot taken with a previous visual testing assertion (OCR or image assertions). Screenshot data will be provided as a Buffer.
133
+ */
134
+ getLastVTScreenshot(dataFormat?: 'raw'): GetLastVTScreenshotChain<Buffer>;
135
+ /**
136
+ * @description returns a screenshot taken with a previous visual testing assertion (OCR or image assertions). Screenshot data will be provided as a base64 encoded string.
137
+ */
138
+ getLastVTScreenshot(dataFormat: 'base64'): GetLastVTScreenshotChain<string>;
130
139
  openDeepLink(deepLink?: string): OpenDeepLinkChain;
131
140
  ocr(comparators: OcrCommonItem[]): OcrChain;
132
141
  image(imageData: ImageData): ImageChain;
@@ -15,7 +15,7 @@ const {handleProgress} = require('../utils/progressHandler');
15
15
  const {getInfoErrorMessage} = require('../utils/socketErrorMessages');
16
16
  const {translateNotStartedReason} = require('../utils/translateResults');
17
17
  const logLevels = require('../../lib/constants/logLevels');
18
- const {createBufferFromSocketMessage} = require('../utils/socketChainHelper');
18
+ const {createBufferFromSocketMessage, parseBinarySocketMessage} = require('../utils/socketChainHelper');
19
19
 
20
20
  /**
21
21
  * @description print log message that comes from BE side with proper log level
@@ -151,12 +151,22 @@ const webSocketsFactory = (self) => {
151
151
  },
152
152
  });
153
153
 
154
- // receiving Buffer for previous ws message related to takeScreenshot
155
154
  if (bufferReceived) {
156
- const data = screenshotData(message, rawDataMessageId);
155
+ // if rawDataMessageId specified received buffer should be related to previous takeScreenshot, saveScreenshot ws message
156
+ if (rawDataMessageId !== null) {
157
+ const data = screenshotData(message, rawDataMessageId);
157
158
 
158
- rawDataMessageId = null;
159
- handleResponse(data);
159
+ rawDataMessageId = null;
160
+ handleResponse(data);
161
+ }
162
+ // otherwise should be received binary format which contains json and binary pair for lastScreenshot ws message
163
+ else {
164
+ const [text, binaryData] = parseBinarySocketMessage(message);
165
+ const jsonMessage = JSON.parse(text);
166
+
167
+ jsonMessage.content.buffer = binaryData;
168
+ handleResponse(jsonMessage);
169
+ }
160
170
  } else if (
161
171
  path([message.messageId, 'contentType'])(requestPromises) === 'takeScreenshot' &&
162
172
  message.content.result === 'success'
@@ -236,7 +246,7 @@ const webSocketsFactory = (self) => {
236
246
 
237
247
  /* istanbul ignore else */
238
248
  if (req) {
239
- if (['query', 'testLine', 'eval', 'takeScreenshot'].includes(req.contentType)) {
249
+ if (['query', 'testLine', 'eval', 'takeScreenshot', 'lastScreenshot'].includes(req.contentType)) {
240
250
  req.resolve({
241
251
  ...res,
242
252
  contentType: req.contentType,
@@ -11,6 +11,7 @@ const contentTypes = {
11
11
  eval: 'eval',
12
12
  testLine: 'testLine',
13
13
  takeScreenshot: 'takeScreenshot',
14
+ lastScreenshot: 'lastScreenshot',
14
15
  getConfiguration: 'getConfiguration',
15
16
  startRecording: 'startRecording',
16
17
  stopRecording: 'stopRecording',
@@ -0,0 +1,56 @@
1
+ const makeChain = require('../utils/makeChain');
2
+ const {
3
+ makeToStringComposer,
4
+ makeThenComposer,
5
+ makeToJSONComposer,
6
+ abandonComposer,
7
+ } = require('../composers');
8
+ const t = require('../texts');
9
+ const {validate, validators} = require('../validation');
10
+
11
+ /**
12
+ * @param {import('../../index.d.ts').ISuitest} classInstance
13
+ */
14
+ const getLastVTScreenshot = (classInstance) => {
15
+ const toJSON = () => ({type: 'lastScreenshot'});
16
+
17
+ const toStringComposer = makeToStringComposer(toJSON);
18
+ const thenComposer = makeThenComposer(toJSON);
19
+ const toJSONComposer = makeToJSONComposer(toJSON);
20
+
21
+ const getComposers = (data) => {
22
+ const output = [
23
+ toStringComposer,
24
+ thenComposer,
25
+ toJSONComposer,
26
+ ];
27
+
28
+ if (!data.isAbandoned) {
29
+ output.push(abandonComposer);
30
+ }
31
+
32
+ return output;
33
+ };
34
+
35
+ /**
36
+ * @param {'raw' | 'base64'} [dataFormat]
37
+ * @returns {*}
38
+ */
39
+ const getLastVTScreenshotChain = (dataFormat = 'raw') => makeChain(classInstance, getComposers, {
40
+ type: 'lastScreenshot',
41
+ dataFormat: validate(
42
+ validators.LAST_SCREENSHOT,
43
+ dataFormat,
44
+ t.invalidInputMessage('getLastVTScreenshot', 'Data format'),
45
+ ),
46
+ });
47
+
48
+ return {
49
+ getLastVTScreenshot: getLastVTScreenshotChain,
50
+ // For Unit Testing
51
+ getComposers,
52
+ toJSON,
53
+ };
54
+ };
55
+
56
+ module.exports = getLastVTScreenshot;
@@ -9,7 +9,7 @@ const {startRecordingMessage} = require('../texts');
9
9
  /**
10
10
  * Start recording
11
11
  * @param {SUITEST_API} instance of main class
12
- * @returns {ChainablePromise.<void>}
12
+ * @returns {ChainablePromise.<string>}
13
13
  */
14
14
  async function startRecording({webSockets, authContext, logger}, recordingSettings) {
15
15
  const webhookUrl = recordingSettings ? recordingSettings.webhookUrl : undefined;
@@ -29,7 +29,7 @@ const makeThenComposer = (getSocketMessage, callback, beforeSend) => makeMethodC
29
29
  let dataToTranslate = jsonSocketMessage;
30
30
  let snippets;
31
31
 
32
- if (dataToTranslate.type === 'takeScreenshot') {
32
+ if (dataToTranslate.type === 'takeScreenshot' || dataToTranslate.type === 'lastScreenshot') {
33
33
  dataToTranslate = {...data};
34
34
  delete dataToTranslate.stack;
35
35
  } else if (data.type === 'runSnippet') {
@@ -25,6 +25,7 @@ const validationKeys = {
25
25
  STRING: Symbol('string'),
26
26
  HAD_NO_ERROR: Symbol('hadNoError'),
27
27
  TAKE_SCREENSHOT: Symbol('takeScreenshot'),
28
+ LAST_SCREENSHOT: Symbol('lastScreenshot'),
28
29
  TAP_TYPE: Symbol('tapType'),
29
30
  TAP_TYPE_AND_DURATION: Symbol('tapTypeAndDuration'),
30
31
  DIRECTIONS: Symbol('direction'),
@@ -39,6 +39,7 @@ const tokenAllowed = {
39
39
  wsContentTypes.eval, wsContentTypes.testLine,
40
40
  wsContentTypes.takeScreenshot, wsContentTypes.getConfiguration,
41
41
  wsContentTypes.startRecording, wsContentTypes.stopRecording,
42
+ wsContentTypes.lastScreenshot,
42
43
  ],
43
44
  };
44
45
 
@@ -97,7 +97,7 @@ function getPureComparatorType(def) {
97
97
  * @returns {Object}
98
98
  */
99
99
  function processJsonMessageForToString(jsonMessage) {
100
- return !['query', 'takeScreenshot'].includes(jsonMessage.type) && 'request' in jsonMessage
100
+ return !['query', 'takeScreenshot', 'lastScreenshot'].includes(jsonMessage.type) && 'request' in jsonMessage
101
101
  ? jsonMessage.request
102
102
  : jsonMessage;
103
103
  }
@@ -45,6 +45,8 @@ module.exports = {
45
45
  return `|${opTypes.assertLine}|`;
46
46
  case 'takeScreenshot':
47
47
  return `|${opTypes.evalLine}|`;
48
+ case 'lastScreenshot':
49
+ return `|${opTypes.evalLine}|`;
48
50
  default:
49
51
  return '';
50
52
  }
@@ -43,6 +43,7 @@ const processServerResponse = (logger, verbosity) =>
43
43
  const isTestLine = res.contentType === 'testLine';
44
44
  const isQuery = res.contentType === 'query';
45
45
  const isTakeScreenshot = res.contentType === 'takeScreenshot';
46
+ const isLastScreenshot = res.contentType === 'lastScreenshot';
46
47
  const isAborted = res.result === 'aborted';
47
48
 
48
49
  // warnings
@@ -146,6 +147,18 @@ const processServerResponse = (logger, verbosity) =>
146
147
  throwErr(new SuitestError(message, SuitestError.EVALUATION_ERROR, responseForError));
147
148
  }
148
149
 
150
+ // getting last screenshot for visual testing
151
+ if (isLastScreenshot && isSuccess) {
152
+ if (data.dataFormat === 'raw') {
153
+ return res.buffer;
154
+ } else if (data.dataFormat === 'base64') {
155
+ return res.buffer.toString('base64');
156
+ }
157
+ } else if (isLastScreenshot && !isSuccess) {
158
+ logger.error(infoMessage());
159
+ throwErr(new SuitestError(message, SuitestError.EVALUATION_ERROR, responseForError));
160
+ }
161
+
149
162
  logger.error(infoMessage(''));
150
163
  throwErr(new SuitestError(message, SuitestError.UNKNOWN_ERROR, responseForError));
151
164
  };
@@ -214,6 +227,8 @@ function getResponseForError(res) {
214
227
  return responseForError;
215
228
  }
216
229
 
230
+ const PROTOCOL_NUMBER = 0x00;
231
+
217
232
  /**
218
233
  * @description concat socket message and binary data pair into single binary data
219
234
  * protocol is:
@@ -225,7 +240,7 @@ function getResponseForError(res) {
225
240
  * @returns {Buffer}
226
241
  */
227
242
  function createBufferFromSocketMessage([socketMessage, binaryData]) {
228
- const protocolNumber = 0x00;
243
+ const protocolNumber = PROTOCOL_NUMBER;
229
244
  const socketMessageSizeMaxBytes = 4;
230
245
  const socketMessageBuffer = Buffer.from(typeof socketMessage === 'string' ? socketMessage : JSON.stringify(socketMessage));
231
246
  const sizeSocketMessage = Buffer.alloc(socketMessageSizeMaxBytes);
@@ -237,8 +252,36 @@ function createBufferFromSocketMessage([socketMessage, binaryData]) {
237
252
  return Buffer.concat([header, socketMessageBuffer, binaryData]);
238
253
  }
239
254
 
255
+ /**
256
+ * @param {Buffer} binarySocketMessage
257
+ * @returns {null | [string, Buffer]}
258
+ */
259
+ function parseBinarySocketMessage(binarySocketMessage) {
260
+ const protocolBufferOffset = 1;
261
+ const jsonMessageDataSizeOffset = 4;
262
+ const headerOffset = protocolBufferOffset + jsonMessageDataSizeOffset;
263
+ const protocolNumber = binarySocketMessage[0];
264
+
265
+ if (protocolNumber !== PROTOCOL_NUMBER) {
266
+ return null;
267
+ }
268
+
269
+ const textSize = binarySocketMessage.readUInt32BE(protocolBufferOffset);
270
+ const textPart = binarySocketMessage.toString(
271
+ 'utf-8',
272
+ headerOffset,
273
+ headerOffset + textSize,
274
+ );
275
+ const binaryPart = binarySocketMessage.subarray(headerOffset + textSize);
276
+
277
+ return [
278
+ textPart,
279
+ binaryPart,
280
+ ];
281
+ }
240
282
  module.exports = {
241
283
  processServerResponse,
242
284
  getRequestType,
243
285
  createBufferFromSocketMessage,
286
+ parseBinarySocketMessage,
244
287
  };
@@ -111,6 +111,11 @@ schemas[validationKeys.TAKE_SCREENSHOT] = {
111
111
  'enum': ['raw', 'base64'],
112
112
  };
113
113
 
114
+ schemas[validationKeys.LAST_SCREENSHOT] = {
115
+ 'type': 'string',
116
+ 'enum': ['raw', 'base64'],
117
+ };
118
+
114
119
  schemas[validationKeys.UUID] = {
115
120
  'type': 'string',
116
121
  'format': 'uuid',
@@ -87,6 +87,9 @@ const validatorsMap = {
87
87
  [validationKeys.TAKE_SCREENSHOT]: (value, text) => {
88
88
  return validators.validateJsonSchema(validationKeys.TAKE_SCREENSHOT, value, text);
89
89
  },
90
+ [validationKeys.LAST_SCREENSHOT]: (value, text) => {
91
+ return validators.validateJsonSchema(validationKeys.LAST_SCREENSHOT, value, text);
92
+ },
90
93
  [validationKeys.TAP_TYPE_AND_DURATION]: ({tapType, tapDuration}, tapTypeErrorMsg, durationErrorMsg) => {
91
94
  return validators.validateTapTypeAndDuration({tapType, tapDuration}, tapTypeErrorMsg, durationErrorMsg);
92
95
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "suitest-js-api",
3
- "version": "3.19.1",
3
+ "version": "3.20.0",
4
4
  "main": "index.js",
5
5
  "repository": "git@github.com:SuitestAutomation/suitest-js-api.git",
6
6
  "author": "Suitest <hello@suite.st>",
@@ -94,7 +94,7 @@
94
94
  },
95
95
  "dependencies": {
96
96
  "@suitest/smst-to-text": "^4.13.0",
97
- "@suitest/translate": "^4.20.1",
97
+ "@suitest/translate": "^4.22.0",
98
98
  "@types/node": "^14.0.10",
99
99
  "ajv": "^6.12.2",
100
100
  "ansi-regex": "^5.0.0",
package/suitest.js CHANGED
@@ -17,6 +17,7 @@ const closeAppFactory = require('./lib/chains/closeAppChain');
17
17
  const suspendAppFactory = require('./lib/chains/suspendAppChain');
18
18
  const takeScreenshotFactory = require('./lib/chains/takeScreenshotChain');
19
19
  const saveScreenshotFactory = require('./lib/chains/saveScreenshotChain');
20
+ const getLastVTScreenshotFactory = require('./lib/chains/getLastVTScreenshotChain');
20
21
  const openUrlFactory = require('./lib/chains/openUrlChain');
21
22
  const locationFactory = require('./lib/chains/locationChain');
22
23
  const applicationFactory = require('./lib/chains/applicationChain');
@@ -139,6 +140,7 @@ class SUITEST_API extends EventEmitter {
139
140
  const {runTestAssert} = runTestFactory(this);
140
141
  const {takeScreenshot} = takeScreenshotFactory(this);
141
142
  const {saveScreenshot} = saveScreenshotFactory(this);
143
+ const {getLastVTScreenshot} = getLastVTScreenshotFactory(this);
142
144
  const {setScreenOrientation, setScreenOrientationAssert} = setScreenOrientationFactory(this);
143
145
  const {openDeepLink, openDeepLinkAssert} = openDeepLinkFactory(this);
144
146
  const {ocr, ocrAssert} = ocrFactory(this);
@@ -168,6 +170,7 @@ class SUITEST_API extends EventEmitter {
168
170
  this.pollUrl = pollUrl;
169
171
  this.takeScreenshot = takeScreenshot;
170
172
  this.saveScreenshot = saveScreenshot;
173
+ this.getLastVTScreenshot = getLastVTScreenshot;
171
174
  this.setScreenOrientation = setScreenOrientation;
172
175
  this.openDeepLink = openDeepLink;
173
176
  this.ocr = ocr;
@@ -0,0 +1,13 @@
1
+ import {
2
+ Abandable,
3
+ AbstractChain,
4
+ Thenable
5
+ } from './modifiers';
6
+
7
+ export interface GetLastVTScreenshotChain<TResult> extends
8
+ AbstractChain,
9
+ Abandable<GetLastVTScreenshotAbandonedChain>,
10
+ Thenable<TResult>
11
+ {}
12
+
13
+ interface GetLastVTScreenshotAbandonedChain extends AbstractChain {}