suitest-js-api 3.12.2 → 3.14.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/README.md +1 -1
- package/index.d.ts +25 -1
- package/lib/api/webSockets.js +10 -3
- package/lib/chains/imageChain.js +168 -0
- package/lib/chains/ocrChain.js +11 -0
- package/lib/composers/accuracyComposer.js +24 -0
- package/lib/composers/inRegionComposer.js +17 -0
- package/lib/composers/index.js +6 -0
- package/lib/composers/languageComposer.js +18 -0
- package/lib/composers/thenComposer.js +21 -12
- package/lib/constants/accuracy.js +9 -0
- package/lib/constants/composer.js +4 -1
- package/lib/constants/lang.js +13 -0
- package/lib/constants/validationKeys.js +4 -0
- package/lib/texts.js +1 -0
- package/lib/utils/socketChainHelper.js +25 -0
- package/lib/utils/testHelpers/index.js +5 -1
- package/lib/utils/testHelpers/testInputError.js +25 -0
- package/lib/validation/jsonSchemas.js +51 -22
- package/lib/validation/validators.js +2 -0
- package/lib/validation/validatorsMap.js +12 -0
- package/package.json +6 -3
- package/suitest.js +8 -0
- package/typeDefinition/ImageChain.d.ts +47 -0
- package/typeDefinition/OcrChain.d.ts +16 -1
- package/typeDefinition/constants/Accuracy.d.ts +5 -0
- package/typeDefinition/constants/Langs.d.ts +9 -0
- package/typeDefinition/modifiers.d.ts +15 -0
- package/typeDefinition/utils/index.d.ts +50 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/SuitestAutomation/suitest-js-api/blob/master/LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/suitest-js-api)
|
|
5
|
-
[](https://circleci.com/gh/SuitestAutomation/suitest-js-api)
|
|
6
6
|
[](https://codeclimate.com/github/SuitestAutomation/suitest-js-api/test_coverage)
|
|
7
7
|
[](https://codeclimate.com/github/SuitestAutomation/suitest-js-api/maintainability)
|
|
8
8
|
|
package/index.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ import {OcrReadAs} from './typeDefinition/constants/OcrReadAs';
|
|
|
47
47
|
import {StringPropComparators} from './typeDefinition/constants/PropComparators';
|
|
48
48
|
import {ValueOf} from './typeDefinition/utils';
|
|
49
49
|
import {OcrColor} from './typeDefinition/constants/OcrColor';
|
|
50
|
+
import {ImageChain} from './typeDefinition/ImageChain';
|
|
51
|
+
import {Accuracy} from './typeDefinition/constants/Accuracy';
|
|
52
|
+
import {Lang} from './typeDefinition/constants/Langs';
|
|
50
53
|
|
|
51
54
|
// --------------- Suitest Interface ---------------------- //
|
|
52
55
|
|
|
@@ -126,6 +129,8 @@ declare namespace suitest {
|
|
|
126
129
|
saveScreenshot(fileName?: string): TakeScreenshotChain<void>;
|
|
127
130
|
openDeepLink(deepLink?: string): OpenDeepLinkChain;
|
|
128
131
|
ocr(comparators: OcrCommonItem[]): OcrChain;
|
|
132
|
+
image(imageData: ImageData): ImageChain;
|
|
133
|
+
image(apiId: string): ImageChain;
|
|
129
134
|
|
|
130
135
|
getPairedDevice(): null | {
|
|
131
136
|
deviceId: string,
|
|
@@ -165,6 +170,8 @@ declare namespace suitest {
|
|
|
165
170
|
COOKIE_PROP: CookieProp;
|
|
166
171
|
OCR_READ_AS: OcrReadAs;
|
|
167
172
|
OCR_COLOR: OcrColor;
|
|
173
|
+
ACCURACY: Accuracy;
|
|
174
|
+
LANG: Lang;
|
|
168
175
|
|
|
169
176
|
authContext: AuthContext;
|
|
170
177
|
appContext: Context;
|
|
@@ -212,6 +219,8 @@ declare namespace suitest {
|
|
|
212
219
|
setScreenOrientation(orientation: ScreenOrientationValues): SetScreenOrientationChain;
|
|
213
220
|
openDeepLink(deepLink?: string): OpenDeepLinkChain;
|
|
214
221
|
ocr(comparators: OcrCommonItem[]): OcrChain;
|
|
222
|
+
image(imageData: ImageData): ImageChain;
|
|
223
|
+
image(apiId: string): ImageChain;
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
type NetworkLogEvent = {
|
|
@@ -354,6 +363,21 @@ declare namespace suitest {
|
|
|
354
363
|
color?: ValueOf<OcrColor>,
|
|
355
364
|
whitelist?: string,
|
|
356
365
|
blacklist?: string,
|
|
357
|
-
region?: [
|
|
366
|
+
region?: [left: number, top: number, width: number, height: number],
|
|
358
367
|
}
|
|
368
|
+
|
|
369
|
+
type ImageData =
|
|
370
|
+
| {
|
|
371
|
+
// image api id from suitest image repository
|
|
372
|
+
apiId: string,
|
|
373
|
+
}
|
|
374
|
+
| {
|
|
375
|
+
// url to image somewhere in internet
|
|
376
|
+
url: string,
|
|
377
|
+
}
|
|
378
|
+
| {
|
|
379
|
+
// os path to image somewhere on a disc
|
|
380
|
+
filepath: string,
|
|
381
|
+
}
|
|
382
|
+
|
|
359
383
|
}
|
package/lib/api/webSockets.js
CHANGED
|
@@ -15,6 +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
19
|
|
|
19
20
|
/**
|
|
20
21
|
* @description print log message that comes from BE side with proper log level
|
|
@@ -281,20 +282,26 @@ const webSocketsFactory = (self) => {
|
|
|
281
282
|
|
|
282
283
|
/**
|
|
283
284
|
* Send ws message
|
|
284
|
-
* @param {Object}
|
|
285
|
+
* @param {Object | [Object, Buffer]} socketMessage
|
|
285
286
|
* @returns {Promise}
|
|
286
287
|
*/
|
|
287
|
-
function send(
|
|
288
|
+
function send(socketMessage) {
|
|
288
289
|
if (ws) {
|
|
289
290
|
const messageId = uuid();
|
|
290
291
|
|
|
292
|
+
const withBinaryData = Array.isArray(socketMessage);
|
|
293
|
+
const content = withBinaryData ? socketMessage[0] : socketMessage;
|
|
291
294
|
const msg = JSON.stringify({
|
|
292
295
|
messageId,
|
|
293
296
|
content,
|
|
294
297
|
});
|
|
295
298
|
|
|
296
299
|
logger.debug('Sending message:', msg);
|
|
297
|
-
|
|
300
|
+
if (withBinaryData) {
|
|
301
|
+
ws.send(createBufferFromSocketMessage([msg, socketMessage[1]]));
|
|
302
|
+
} else {
|
|
303
|
+
ws.send(msg);
|
|
304
|
+
}
|
|
298
305
|
|
|
299
306
|
return handleRequest(messageId, content.type);
|
|
300
307
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const {isNil} = require('ramda');
|
|
3
|
+
const {
|
|
4
|
+
makeToStringComposer,
|
|
5
|
+
makeThenComposer,
|
|
6
|
+
makeToJSONComposer,
|
|
7
|
+
cloneComposer,
|
|
8
|
+
assertComposer,
|
|
9
|
+
abandonComposer,
|
|
10
|
+
timeoutComposer,
|
|
11
|
+
visibleComposer,
|
|
12
|
+
notComposer,
|
|
13
|
+
inRegionComposer,
|
|
14
|
+
accuracyComposer,
|
|
15
|
+
} = require('../composers');
|
|
16
|
+
const makeChain = require('../utils/makeChain');
|
|
17
|
+
const {getRequestType} = require('../utils/socketChainHelper');
|
|
18
|
+
const {validate, validators} = require('../validation');
|
|
19
|
+
const {invalidInputMessage, imageMalformed} = require('../texts');
|
|
20
|
+
const {applyTimeout, applyNegation} = require('../utils/chainUtils');
|
|
21
|
+
const SuitestError = require('../utils/SuitestError');
|
|
22
|
+
const {fetch} = require('../utils/fetch');
|
|
23
|
+
const {getAccuracy} = require('../composers/accuracyComposer');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('../../index.d.ts').ISuitest} classInstance
|
|
27
|
+
*/
|
|
28
|
+
const imageChainFactory = (classInstance) => {
|
|
29
|
+
const toJSON = (data) => {
|
|
30
|
+
if (isNil(data.comparator)) {
|
|
31
|
+
throw new SuitestError(imageMalformed(), SuitestError.INVALID_INPUT);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const socketMessage = {
|
|
35
|
+
type: getRequestType(data, false),
|
|
36
|
+
request: applyTimeout(
|
|
37
|
+
{
|
|
38
|
+
type: 'assert',
|
|
39
|
+
condition: {
|
|
40
|
+
subject: {
|
|
41
|
+
type: 'image',
|
|
42
|
+
},
|
|
43
|
+
type: applyNegation(data.comparator.type, data),
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
data,
|
|
47
|
+
classInstance.config.defaultTimeout,
|
|
48
|
+
),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const condition = socketMessage.request.condition;
|
|
52
|
+
const accuracy = getAccuracy(data);
|
|
53
|
+
|
|
54
|
+
if (accuracy) {
|
|
55
|
+
condition.accuracy = accuracy;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (data.imageData.apiId) {
|
|
59
|
+
condition.subject.apiId = data.imageData.apiId;
|
|
60
|
+
} else if (data.imageData.url) {
|
|
61
|
+
condition.subject.url = data.imageData.url;
|
|
62
|
+
} else if (data.imageData.filepath) {
|
|
63
|
+
condition.subject.filepath = data.imageData.filepath;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (data.region) {
|
|
67
|
+
condition.region = data.region;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return socketMessage;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const toStringComposer = makeToStringComposer(toJSON);
|
|
74
|
+
|
|
75
|
+
const thenComposer = makeThenComposer(async(data) => {
|
|
76
|
+
const socketMessage = toJSON(data);
|
|
77
|
+
|
|
78
|
+
if (data.imageData.apiId) {
|
|
79
|
+
return socketMessage;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (data.imageData.filepath) {
|
|
83
|
+
const imageBuffer = await fs.promises.readFile(data.imageData.filepath);
|
|
84
|
+
|
|
85
|
+
return [socketMessage, imageBuffer];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (data.imageData.url) {
|
|
89
|
+
const imageBuffer = await (await fetch(data.imageData.url)).buffer();
|
|
90
|
+
|
|
91
|
+
return [socketMessage, imageBuffer];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return socketMessage;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const toJSONComposer = makeToJSONComposer(toJSON);
|
|
98
|
+
|
|
99
|
+
const getComposers = (data) => {
|
|
100
|
+
const output = [
|
|
101
|
+
toStringComposer,
|
|
102
|
+
thenComposer,
|
|
103
|
+
cloneComposer,
|
|
104
|
+
toJSONComposer,
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
if (!data.isAssert) {
|
|
108
|
+
output.push(assertComposer);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!data.isAbandoned) {
|
|
112
|
+
output.push(abandonComposer);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!data.isNegated) {
|
|
116
|
+
output.push(notComposer);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!data.timeout) {
|
|
120
|
+
output.push(timeoutComposer);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!data.region) {
|
|
124
|
+
output.push(inRegionComposer);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!data.comparator) {
|
|
128
|
+
output.push(visibleComposer);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!data.accuracy) {
|
|
132
|
+
output.push(accuracyComposer);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return output;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const makeImageChain = (imageData) => {
|
|
139
|
+
const unifiedImageData = typeof imageData === 'string'
|
|
140
|
+
? {apiId: imageData}
|
|
141
|
+
: imageData;
|
|
142
|
+
|
|
143
|
+
return makeChain(
|
|
144
|
+
classInstance,
|
|
145
|
+
getComposers,
|
|
146
|
+
{
|
|
147
|
+
type: 'image',
|
|
148
|
+
imageData:
|
|
149
|
+
validate(
|
|
150
|
+
validators.IMAGE_DATA,
|
|
151
|
+
unifiedImageData,
|
|
152
|
+
invalidInputMessage('image', 'Image data'),
|
|
153
|
+
),
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
image: makeImageChain,
|
|
160
|
+
imageAssert: (data) => makeImageChain(data).toAssert(),
|
|
161
|
+
|
|
162
|
+
// for unit tests
|
|
163
|
+
getComposers,
|
|
164
|
+
toJSON,
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
module.exports = imageChainFactory;
|
package/lib/chains/ocrChain.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
assertComposer,
|
|
7
7
|
abandonComposer,
|
|
8
8
|
timeoutComposer,
|
|
9
|
+
languageComposer,
|
|
9
10
|
} = require('../composers');
|
|
10
11
|
const makeChain = require('../utils/makeChain');
|
|
11
12
|
const {getRequestType} = require('../utils/socketChainHelper');
|
|
@@ -63,6 +64,9 @@ const ocrFactory = (classInstance) => {
|
|
|
63
64
|
if (data.ocrItems) {
|
|
64
65
|
socketMessage.subject.options = data.ocrItems;
|
|
65
66
|
}
|
|
67
|
+
if (data.language) {
|
|
68
|
+
socketMessage.subject.language = data.language;
|
|
69
|
+
}
|
|
66
70
|
} else {
|
|
67
71
|
socketMessage.request = applyTimeout(
|
|
68
72
|
{
|
|
@@ -75,6 +79,9 @@ const ocrFactory = (classInstance) => {
|
|
|
75
79
|
data,
|
|
76
80
|
classInstance.config.defaultTimeout,
|
|
77
81
|
);
|
|
82
|
+
if (data.language) {
|
|
83
|
+
socketMessage.request.condition.language = data.language;
|
|
84
|
+
}
|
|
78
85
|
if (data.ocrItems) {
|
|
79
86
|
socketMessage.request.condition.comparators = validateOcrComparators(data.ocrItems);
|
|
80
87
|
}
|
|
@@ -107,6 +114,10 @@ const ocrFactory = (classInstance) => {
|
|
|
107
114
|
output.push(timeoutComposer);
|
|
108
115
|
}
|
|
109
116
|
|
|
117
|
+
if (!data.language) {
|
|
118
|
+
output.push(languageComposer);
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
return output;
|
|
111
122
|
};
|
|
112
123
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const {makeModifierComposer} = require('../utils/makeComposer');
|
|
2
|
+
const composers = require('../constants/composer');
|
|
3
|
+
const {validate, validators} = require('../validation');
|
|
4
|
+
const {invalidInputMessage} = require('../texts');
|
|
5
|
+
|
|
6
|
+
const accuracyComposer = makeModifierComposer(composers.ACCURACY, ['accuracy'], (_, meta, accuracy) => {
|
|
7
|
+
return {
|
|
8
|
+
...meta,
|
|
9
|
+
accuracy: validate(
|
|
10
|
+
validators.ACCURACY,
|
|
11
|
+
accuracy,
|
|
12
|
+
invalidInputMessage('accuracy', 'Accuracy'),
|
|
13
|
+
),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const getAccuracy = (meta) => {
|
|
18
|
+
return meta.accuracy;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
accuracyComposer,
|
|
23
|
+
getAccuracy,
|
|
24
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const {makeModifierComposer} = require('../utils/makeComposer');
|
|
2
|
+
const composers = require('../constants/composer');
|
|
3
|
+
const {validate, validators} = require('../validation');
|
|
4
|
+
const {invalidInputMessage} = require('../texts');
|
|
5
|
+
|
|
6
|
+
const inRegionComposer = makeModifierComposer(composers.IN_REGION, ['inRegion'], (_, meta, region) => {
|
|
7
|
+
return {
|
|
8
|
+
...meta,
|
|
9
|
+
region: validate(
|
|
10
|
+
validators.REGION,
|
|
11
|
+
region,
|
|
12
|
+
invalidInputMessage('region', 'Region'),
|
|
13
|
+
),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
module.exports = inRegionComposer;
|
package/lib/composers/index.js
CHANGED
|
@@ -46,6 +46,9 @@ const attributesComposer = require('./attributesComposer');
|
|
|
46
46
|
const cssPropsComposer = require('./cssPropsComposer');
|
|
47
47
|
const deepLinkComposer = require('./deepLinkComposer');
|
|
48
48
|
const withPropertiesComposer = require('./withPropertiesComposer');
|
|
49
|
+
const inRegionComposer = require('./inRegionComposer');
|
|
50
|
+
const languageComposer = require('./languageComposer');
|
|
51
|
+
const {accuracyComposer} = require('./accuracyComposer');
|
|
49
52
|
|
|
50
53
|
module.exports = {
|
|
51
54
|
abandonComposer,
|
|
@@ -98,4 +101,7 @@ module.exports = {
|
|
|
98
101
|
cssPropsComposer,
|
|
99
102
|
deepLinkComposer,
|
|
100
103
|
withPropertiesComposer,
|
|
104
|
+
inRegionComposer,
|
|
105
|
+
languageComposer,
|
|
106
|
+
accuracyComposer,
|
|
101
107
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const {makeModifierComposer} = require('../utils/makeComposer');
|
|
2
|
+
const composers = require('../constants/composer');
|
|
3
|
+
const {validate, validators} = require('../validation');
|
|
4
|
+
const {invalidInputMessage} = require('../texts');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Defines language method
|
|
8
|
+
*/
|
|
9
|
+
const languageComposer = makeModifierComposer(composers.LANGUAGE, ['language'], (_, meta, value) => ({
|
|
10
|
+
...meta,
|
|
11
|
+
language: validate(
|
|
12
|
+
validators.LANGUAGE,
|
|
13
|
+
value,
|
|
14
|
+
invalidInputMessage('language', 'Language'),
|
|
15
|
+
),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
module.exports = languageComposer;
|
|
@@ -21,8 +21,12 @@ const makeThenComposer = (getSocketMessage, callback, beforeSend) => makeMethodC
|
|
|
21
21
|
if (promiseMap.has(data)) {
|
|
22
22
|
promise = promiseMap.get(data);
|
|
23
23
|
} else {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/** @type {Object | [Object, Buffer]} */
|
|
25
|
+
const message = await getSocketMessage(data);
|
|
26
|
+
const withBinaryData = Array.isArray(message);
|
|
27
|
+
const jsonSocketMessage = withBinaryData ? message[0] : message;
|
|
28
|
+
|
|
29
|
+
let dataToTranslate = jsonSocketMessage;
|
|
26
30
|
let snippets;
|
|
27
31
|
|
|
28
32
|
if (dataToTranslate.type === 'takeScreenshot') {
|
|
@@ -45,16 +49,21 @@ const makeThenComposer = (getSocketMessage, callback, beforeSend) => makeMethodC
|
|
|
45
49
|
logger.log(translation);
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
|
|
53
|
+
promise = authContext.authorizeWs(jsonSocketMessage, data.type)
|
|
54
|
+
.then(() => {
|
|
55
|
+
const defaultResponseHandler = processServerResponse(logger, config.logLevel);
|
|
56
|
+
|
|
57
|
+
return webSockets.send(message).then(result => {
|
|
58
|
+
return callback
|
|
59
|
+
? callback(result, data, jsonSocketMessage)
|
|
60
|
+
: defaultResponseHandler(result, data, jsonSocketMessage, snippets);
|
|
61
|
+
}, error => {
|
|
62
|
+
return callback
|
|
63
|
+
? callback(error, data, jsonSocketMessage)
|
|
64
|
+
: defaultResponseHandler(error, data, jsonSocketMessage, snippets);
|
|
65
|
+
});
|
|
66
|
+
})
|
|
58
67
|
.catch(err => {
|
|
59
68
|
if (err instanceof SuitestError && err.code === SuitestError.AUTH_NOT_ALLOWED) {
|
|
60
69
|
logger.error(connectionNotEstablished());
|
|
@@ -45,7 +45,10 @@ const composers = {
|
|
|
45
45
|
ATTRIBUTES: Symbol('getAttributes'),
|
|
46
46
|
CSS_PROPS: Symbol('getCssProp'),
|
|
47
47
|
DEEP_LINK: Symbol('deepLink'),
|
|
48
|
-
WITH_PROPERTIES:
|
|
48
|
+
WITH_PROPERTIES: Symbol('withProperties'),
|
|
49
|
+
IN_REGION: Symbol('inRegion'),
|
|
50
|
+
LANGUAGE: Symbol('language'),
|
|
51
|
+
ACCURACY: Symbol('accuracy'),
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
Object.freeze(composers);
|
|
@@ -35,6 +35,10 @@ const validationKeys = {
|
|
|
35
35
|
COOKIE_PROPS: Symbol('cookieProps'),
|
|
36
36
|
OCR_COMPARATORS: Symbol('ocrComparators'),
|
|
37
37
|
OCR_OPTIONS: Symbol('ocrOptions'),
|
|
38
|
+
IMAGE_DATA: Symbol('imageData'),
|
|
39
|
+
REGION: Symbol('region'),
|
|
40
|
+
LANGUAGE: Symbol('language'),
|
|
41
|
+
ACCURACY: Symbol('accuracy'),
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
Object.freeze(validationKeys);
|
package/lib/texts.js
CHANGED
|
@@ -78,6 +78,7 @@ module.exports = {
|
|
|
78
78
|
positionIsMalformed: () => 'position command is malformed',
|
|
79
79
|
relativePositionIsMalformed: () => 'relative position is malformed',
|
|
80
80
|
assertOcrMalformed: () => 'Assert ocr line is malformed - comparators are missing',
|
|
81
|
+
imageMalformed: () => 'Image line is malformed',
|
|
81
82
|
|
|
82
83
|
unusedLeaves: leaves => `Some of your Suitest chains were not executed.
|
|
83
84
|
Put an "await" in front of those chains or call .abandon() to suppress these warnings.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const {Buffer} = require('buffer');
|
|
3
4
|
const {isNil} = require('ramda');
|
|
4
5
|
const assert = require('assert');
|
|
5
6
|
const SuitestError = require('./SuitestError');
|
|
@@ -213,7 +214,31 @@ function getResponseForError(res) {
|
|
|
213
214
|
return responseForError;
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
/**
|
|
218
|
+
* @description concat socket message and binary data pair into single binary data
|
|
219
|
+
* protocol is:
|
|
220
|
+
* | protocol number (1 byte) | size of json socket message (32-bit unsigned integer) | socket message | binary data |
|
|
221
|
+
* example:
|
|
222
|
+
* | 0x00 | 0x000001c(28) | Buffer.from(JSON.stringify({type: 'eval', request: {}})) | binaryData |
|
|
223
|
+
* @param {Object|string} socketMessage
|
|
224
|
+
* @param {Buffer} binaryData
|
|
225
|
+
* @returns {Buffer}
|
|
226
|
+
*/
|
|
227
|
+
function createBufferFromSocketMessage([socketMessage, binaryData]) {
|
|
228
|
+
const protocolNumber = 0x00;
|
|
229
|
+
const socketMessageSizeMaxBytes = 4;
|
|
230
|
+
const socketMessageBuffer = Buffer.from(typeof socketMessage === 'string' ? socketMessage : JSON.stringify(socketMessage));
|
|
231
|
+
const sizeSocketMessage = Buffer.alloc(socketMessageSizeMaxBytes);
|
|
232
|
+
|
|
233
|
+
sizeSocketMessage.writeUInt32BE(socketMessageBuffer.length);
|
|
234
|
+
|
|
235
|
+
const header = Buffer.concat([Buffer.from([protocolNumber]), sizeSocketMessage]);
|
|
236
|
+
|
|
237
|
+
return Buffer.concat([header, socketMessageBuffer, binaryData]);
|
|
238
|
+
}
|
|
239
|
+
|
|
216
240
|
module.exports = {
|
|
217
241
|
processServerResponse,
|
|
218
242
|
getRequestType,
|
|
243
|
+
createBufferFromSocketMessage,
|
|
219
244
|
};
|
|
@@ -5,6 +5,9 @@ const {stripAnsiChars} = require('../stringUtils');
|
|
|
5
5
|
|
|
6
6
|
const bySymbol = (a, b) => a.toString() > b.toString() ? 1 : -1;
|
|
7
7
|
const getComposerTypes = comps => comps.map(c => c.composerType).sort(bySymbol);
|
|
8
|
+
const excludeComposer = (composers, toExclude) => {
|
|
9
|
+
return composers.filter((composer) => composer !== toExclude);
|
|
10
|
+
};
|
|
8
11
|
|
|
9
12
|
const assertBeforeSendMsg = curry((beforeSendMsgFn, logStub, chainData, substring) => {
|
|
10
13
|
logStub.resetHistory();
|
|
@@ -13,12 +16,13 @@ const assertBeforeSendMsg = curry((beforeSendMsgFn, logStub, chainData, substrin
|
|
|
13
16
|
|
|
14
17
|
assert.ok(
|
|
15
18
|
beforeSendMsgLog.includes(substring),
|
|
16
|
-
`Substring "${substring}" should be found in beforeSendMsg log: "${beforeSendMsgLog}"
|
|
19
|
+
`Substring "${substring}" should be found in beforeSendMsg log: "${beforeSendMsgLog}"`,
|
|
17
20
|
);
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
module.exports = {
|
|
21
24
|
bySymbol,
|
|
22
25
|
getComposerTypes,
|
|
26
|
+
excludeComposer,
|
|
23
27
|
assertBeforeSendMsg,
|
|
24
28
|
};
|
|
@@ -13,5 +13,30 @@ const commonTestInputError = curry((asserter, fn, args = [], expect = {}, msg) =
|
|
|
13
13
|
}, msg || `Invalid error if ${args.length ? args.map(JSON.stringify).join(', ') : 'no args'}`);
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
const suitestInvalidInputError = (message) => {
|
|
17
|
+
return new SuitestError(message, SuitestError.INVALID_INPUT);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated use assert.rejects and suitestInvalidInputError https://nodejs.org/api/assert.html#assertrejectsasyncfn-error-message
|
|
22
|
+
* @example
|
|
23
|
+
* // Before:
|
|
24
|
+
* await testInputErrorAsync(testingFunc, ['some arg']);
|
|
25
|
+
* await testInputErrorAsync(testingFunc, ['some arg'] {message: 'Error message'});
|
|
26
|
+
* // After:
|
|
27
|
+
* await assert.rejects(() => testingFunc('some arg'));
|
|
28
|
+
* await assert.rejects(() => testingFunc('some arg'), suitestInvalidInputError('Error message'));
|
|
29
|
+
*/
|
|
16
30
|
module.exports.testInputErrorAsync = commonTestInputError(assertThrowsAsync);
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated use assert.throws instead https://nodejs.org/api/assert.html#assertthrowsfn-error-message
|
|
33
|
+
* @example
|
|
34
|
+
* // Before:
|
|
35
|
+
* await testInputErrorSync(testingFunc, ['some arg']);
|
|
36
|
+
* await testInputErrorSync(testingFunc, ['some arg'], {message: 'Error message'});
|
|
37
|
+
* // After:
|
|
38
|
+
* await assert.throws(() => testingFunc('some arg'));
|
|
39
|
+
* await assert.throws(() => testingFunc('some arg'), suitestInvalidInputError('Error message'));
|
|
40
|
+
*/
|
|
17
41
|
module.exports.testInputErrorSync = commonTestInputError(assert.throws);
|
|
42
|
+
module.exports.suitestInvalidInputError = suitestInvalidInputError;
|
|
@@ -20,6 +20,8 @@ const LAUNCH_MODE = require('../constants/launchMode');
|
|
|
20
20
|
const COOKIE_PROP = require('../constants/cookieProp');
|
|
21
21
|
const OCR_READ_AS = require('../constants/ocrReadAs');
|
|
22
22
|
const OCR_COLOR = require('../constants/ocrColor');
|
|
23
|
+
const ACCURACY = require('../constants/accuracy');
|
|
24
|
+
const LANG = require('../constants/lang');
|
|
23
25
|
const schemas = {};
|
|
24
26
|
|
|
25
27
|
const nonNegativeNumber = () => ({
|
|
@@ -32,6 +34,21 @@ const positiveNumber = () => ({
|
|
|
32
34
|
exclusiveMinimum: 0,
|
|
33
35
|
});
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @description schema for validation region for OCR and Image assertions
|
|
39
|
+
*/
|
|
40
|
+
const regionSchema = () => ({
|
|
41
|
+
'type': 'array',
|
|
42
|
+
'items': [
|
|
43
|
+
{...nonNegativeNumber(), maximum: 100},
|
|
44
|
+
{...nonNegativeNumber(), maximum: 100},
|
|
45
|
+
{...positiveNumber(), maximum: 100},
|
|
46
|
+
{...positiveNumber(), maximum: 100},
|
|
47
|
+
],
|
|
48
|
+
'minItems': 4,
|
|
49
|
+
'additionalItems': false,
|
|
50
|
+
});
|
|
51
|
+
|
|
35
52
|
const CONFIG_OVERRIDE_PROPERTIES = {
|
|
36
53
|
'url': {'type': 'string'},
|
|
37
54
|
'suitestify': {'type': 'boolean'},
|
|
@@ -535,17 +552,7 @@ schemas[validationKeys.OCR_COMPARATORS] = {
|
|
|
535
552
|
},
|
|
536
553
|
'whitelist': schemas[validationKeys.NON_EMPTY_STRING],
|
|
537
554
|
'blacklist': schemas[validationKeys.NON_EMPTY_STRING],
|
|
538
|
-
'region':
|
|
539
|
-
'type': 'array',
|
|
540
|
-
'items': [
|
|
541
|
-
{...nonNegativeNumber(), maximum: 100},
|
|
542
|
-
{...nonNegativeNumber(), maximum: 100},
|
|
543
|
-
{...positiveNumber(), maximum: 100},
|
|
544
|
-
{...positiveNumber(), maximum: 100},
|
|
545
|
-
],
|
|
546
|
-
'minItems': 4,
|
|
547
|
-
'additionalItems': false,
|
|
548
|
-
},
|
|
555
|
+
'region': regionSchema(),
|
|
549
556
|
},
|
|
550
557
|
},
|
|
551
558
|
};
|
|
@@ -575,21 +582,43 @@ schemas[validationKeys.OCR_OPTIONS] = {
|
|
|
575
582
|
},
|
|
576
583
|
'whitelist': schemas[validationKeys.NON_EMPTY_STRING],
|
|
577
584
|
'blacklist': schemas[validationKeys.NON_EMPTY_STRING],
|
|
578
|
-
'region':
|
|
579
|
-
'type': 'array',
|
|
580
|
-
'items': [
|
|
581
|
-
{...nonNegativeNumber(), maximum: 100},
|
|
582
|
-
{...nonNegativeNumber(), maximum: 100},
|
|
583
|
-
{...positiveNumber(), maximum: 100},
|
|
584
|
-
{...positiveNumber(), maximum: 100},
|
|
585
|
-
],
|
|
586
|
-
'minItems': 4,
|
|
587
|
-
'additionalItems': false,
|
|
588
|
-
},
|
|
585
|
+
'region': regionSchema(),
|
|
589
586
|
},
|
|
590
587
|
},
|
|
591
588
|
};
|
|
592
589
|
|
|
590
|
+
schemas[validationKeys.REGION] = {
|
|
591
|
+
'schemaId': validationKeys.REGION,
|
|
592
|
+
...regionSchema(),
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
schemas[validationKeys.IMAGE_DATA] = {
|
|
596
|
+
'schemaId': validationKeys.IMAGE_DATA,
|
|
597
|
+
'type': 'object',
|
|
598
|
+
'properties': {
|
|
599
|
+
'url': {'type': 'string'},
|
|
600
|
+
'filepath': {'type': 'string'},
|
|
601
|
+
'apiId': {'type': 'string'},
|
|
602
|
+
},
|
|
603
|
+
'anyOf': [
|
|
604
|
+
{'required': ['url']},
|
|
605
|
+
{'required': ['filepath']},
|
|
606
|
+
{'required': ['apiId']},
|
|
607
|
+
],
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
schemas[validationKeys.LANGUAGE] = {
|
|
611
|
+
'schemaId': validationKeys.LANGUAGE,
|
|
612
|
+
'type': 'string',
|
|
613
|
+
'enum': Object.values(LANG),
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
schemas[validationKeys.ACCURACY] = {
|
|
617
|
+
'schemaId': validationKeys.ACCURACY,
|
|
618
|
+
'type': 'string',
|
|
619
|
+
'enum': Object.values(ACCURACY),
|
|
620
|
+
};
|
|
621
|
+
|
|
593
622
|
Object.freeze(schemas);
|
|
594
623
|
|
|
595
624
|
module.exports = schemas;
|
|
@@ -245,6 +245,7 @@ const allowedUntilConditionChainTypes = [
|
|
|
245
245
|
'network',
|
|
246
246
|
'video',
|
|
247
247
|
'psVideo',
|
|
248
|
+
'image',
|
|
248
249
|
];
|
|
249
250
|
|
|
250
251
|
const allowedUntilConditionChainNames = [
|
|
@@ -256,6 +257,7 @@ const allowedUntilConditionChainNames = [
|
|
|
256
257
|
'networkRequest',
|
|
257
258
|
'video',
|
|
258
259
|
'psVideo',
|
|
260
|
+
'image',
|
|
259
261
|
];
|
|
260
262
|
|
|
261
263
|
const getSubjectType = path(['request', 'condition', 'subject', 'type']);
|
|
@@ -111,6 +111,18 @@ const validatorsMap = {
|
|
|
111
111
|
[validationKeys.OCR_OPTIONS]: (value, text) => {
|
|
112
112
|
return validators.validateJsonSchema(validationKeys.OCR_OPTIONS, value, text);
|
|
113
113
|
},
|
|
114
|
+
[validationKeys.IMAGE_DATA]: (value, text) => {
|
|
115
|
+
return validators.validateJsonSchema(validationKeys.IMAGE_DATA, value, text);
|
|
116
|
+
},
|
|
117
|
+
[validationKeys.REGION]: (value, text) => {
|
|
118
|
+
return validators.validateJsonSchema(validationKeys.REGION, value, text);
|
|
119
|
+
},
|
|
120
|
+
[validationKeys.LANGUAGE]: (value, text) => {
|
|
121
|
+
return validators.validateJsonSchema(validationKeys.LANGUAGE, value, text);
|
|
122
|
+
},
|
|
123
|
+
[validationKeys.ACCURACY]: (value, text) => {
|
|
124
|
+
return validators.validateJsonSchema(validationKeys.ACCURACY, value, text);
|
|
125
|
+
},
|
|
114
126
|
};
|
|
115
127
|
|
|
116
128
|
Object.freeze(validatorsMap);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "suitest-js-api",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": "git@github.com:SuitestAutomation/suitest-js-api.git",
|
|
6
6
|
"author": "Suitest <hello@suite.st>",
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
"XClass TV testing",
|
|
26
26
|
"Xumo testing",
|
|
27
27
|
"Smart TV test automation",
|
|
28
|
-
"Fire TV test automation"
|
|
28
|
+
"Fire TV test automation",
|
|
29
|
+
"visual testing",
|
|
30
|
+
"image template matching",
|
|
31
|
+
"ocr"
|
|
29
32
|
],
|
|
30
33
|
"homepage": "https://suite.st/",
|
|
31
34
|
"license": "MIT",
|
|
@@ -91,7 +94,7 @@
|
|
|
91
94
|
},
|
|
92
95
|
"dependencies": {
|
|
93
96
|
"@suitest/smst-to-text": "^4.13.0",
|
|
94
|
-
"@suitest/translate": "^4.
|
|
97
|
+
"@suitest/translate": "^4.16.0",
|
|
95
98
|
"@types/node": "^14.0.10",
|
|
96
99
|
"ajv": "^6.12.2",
|
|
97
100
|
"ansi-regex": "^5.0.0",
|
package/suitest.js
CHANGED
|
@@ -40,6 +40,7 @@ const playstationVideoFactory = require('./lib/chains/playstationVideoChain');
|
|
|
40
40
|
const setScreenOrientationFactory = require('./lib/chains/setScreenOrientationChain');
|
|
41
41
|
const openDeepLinkFactory = require('./lib/chains/openDeepLinkChain');
|
|
42
42
|
const ocrFactory = require('./lib/chains/ocrChain');
|
|
43
|
+
const imageFactory = require('./lib/chains/imageChain');
|
|
43
44
|
|
|
44
45
|
// Constants
|
|
45
46
|
const {ELEMENT_PROP, VALUE} = require('./lib/constants/element');
|
|
@@ -62,6 +63,8 @@ const SCREEN_ORIENTATION = require('./lib/constants/screenOrientation');
|
|
|
62
63
|
const COOKIE_PROP = require('./lib/constants/cookieProp');
|
|
63
64
|
const OCR_READ_AS = require('./lib/constants/ocrReadAs');
|
|
64
65
|
const OCR_COLOR = require('./lib/constants/ocrColor');
|
|
66
|
+
const ACCURACY = require('./lib/constants/accuracy');
|
|
67
|
+
const LANG = require('./lib/constants/lang');
|
|
65
68
|
|
|
66
69
|
// Network
|
|
67
70
|
const webSocketsFactory = require('./lib/api/webSockets');
|
|
@@ -139,6 +142,7 @@ class SUITEST_API extends EventEmitter {
|
|
|
139
142
|
const {setScreenOrientation, setScreenOrientationAssert} = setScreenOrientationFactory(this);
|
|
140
143
|
const {openDeepLink, openDeepLinkAssert} = openDeepLinkFactory(this);
|
|
141
144
|
const {ocr, ocrAssert} = ocrFactory(this);
|
|
145
|
+
const {image, imageAssert} = imageFactory(this);
|
|
142
146
|
|
|
143
147
|
this.openApp = openApp;
|
|
144
148
|
this.closeApp = closeApp;
|
|
@@ -167,6 +171,7 @@ class SUITEST_API extends EventEmitter {
|
|
|
167
171
|
this.setScreenOrientation = setScreenOrientation;
|
|
168
172
|
this.openDeepLink = openDeepLink;
|
|
169
173
|
this.ocr = ocr;
|
|
174
|
+
this.image = image;
|
|
170
175
|
|
|
171
176
|
this.PROP = ELEMENT_PROP;
|
|
172
177
|
this.COMP = PROP_COMPARATOR;
|
|
@@ -190,6 +195,8 @@ class SUITEST_API extends EventEmitter {
|
|
|
190
195
|
this.COOKIE_PROP = COOKIE_PROP;
|
|
191
196
|
this.OCR_READ_AS = OCR_READ_AS;
|
|
192
197
|
this.OCR_COLOR = OCR_COLOR;
|
|
198
|
+
this.ACCURACY = ACCURACY;
|
|
199
|
+
this.LANG = LANG;
|
|
193
200
|
|
|
194
201
|
this.assert = {
|
|
195
202
|
application: applicationAssert,
|
|
@@ -218,6 +225,7 @@ class SUITEST_API extends EventEmitter {
|
|
|
218
225
|
setScreenOrientation: setScreenOrientationAssert,
|
|
219
226
|
openDeepLink: openDeepLinkAssert,
|
|
220
227
|
ocr: ocrAssert,
|
|
228
|
+
image: imageAssert,
|
|
221
229
|
};
|
|
222
230
|
|
|
223
231
|
// Listen to process events to trigger websocket termination and dump warnings, if any
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractChain,
|
|
3
|
+
Timeout,
|
|
4
|
+
InRegionModifier,
|
|
5
|
+
VisibleModifier,
|
|
6
|
+
AccuracyModifier,
|
|
7
|
+
Negatable,
|
|
8
|
+
Assertable,
|
|
9
|
+
BaseEmptyChain,
|
|
10
|
+
} from './modifiers';
|
|
11
|
+
import {
|
|
12
|
+
AccuracyModifierNames,
|
|
13
|
+
AssertableMethodsNames,
|
|
14
|
+
InRegionMethodsNames,
|
|
15
|
+
NegatableMethodsNames,
|
|
16
|
+
TimeoutMethodsNames,
|
|
17
|
+
VisibleMethodsNames,
|
|
18
|
+
Chainable,
|
|
19
|
+
ChainWithoutMethods,
|
|
20
|
+
} from './utils';
|
|
21
|
+
|
|
22
|
+
interface ImageBase extends
|
|
23
|
+
// not(), doesNot(), isNot()
|
|
24
|
+
Negatable<ChainWithoutMethods<ImageBase, NegatableMethodsNames>>,
|
|
25
|
+
// timeout(...)
|
|
26
|
+
Timeout<ChainWithoutMethods<ImageBase, TimeoutMethodsNames>>,
|
|
27
|
+
// visible()
|
|
28
|
+
VisibleModifier<ChainWithoutMethods<ImageBase, VisibleMethodsNames>>,
|
|
29
|
+
// inRegion(...)
|
|
30
|
+
InRegionModifier<ChainWithoutMethods<ImageBase, InRegionMethodsNames>>,
|
|
31
|
+
// accuracy(...)
|
|
32
|
+
AccuracyModifier<ChainWithoutMethods<ImageBase, AccuracyModifierNames>>,
|
|
33
|
+
// toAssert(...)
|
|
34
|
+
Assertable<ChainWithoutMethods<ImageBase, AssertableMethodsNames>>,
|
|
35
|
+
// toString(), then(), abandon(), clone()
|
|
36
|
+
ImageBaseEvalChain<ImageBase>
|
|
37
|
+
{}
|
|
38
|
+
|
|
39
|
+
type ImageChain = Chainable<ImageBase>;
|
|
40
|
+
|
|
41
|
+
interface ImageBaseEvalChain<TSelf> extends
|
|
42
|
+
BaseEmptyChain<TSelf, ImageEvalResult, ImageAbandonedChain>
|
|
43
|
+
{}
|
|
44
|
+
|
|
45
|
+
interface ImageAbandonedChain extends AbstractChain {}
|
|
46
|
+
|
|
47
|
+
type ImageEvalResult = boolean;
|
|
@@ -3,17 +3,32 @@ import {
|
|
|
3
3
|
Assertable,
|
|
4
4
|
Timeout,
|
|
5
5
|
BaseEmptyChain,
|
|
6
|
+
Language,
|
|
6
7
|
} from './modifiers';
|
|
7
8
|
|
|
9
|
+
// +language +timeout
|
|
8
10
|
export interface OcrChain extends
|
|
9
11
|
OcrBaseQueryChain<OcrChain>,
|
|
10
|
-
Timeout<OcrWithoutTimeout
|
|
12
|
+
Timeout<OcrWithoutTimeout>,
|
|
13
|
+
Language<OcrWithoutLanguage>
|
|
11
14
|
{}
|
|
12
15
|
|
|
16
|
+
// -language +timeout
|
|
17
|
+
interface OcrWithoutLanguage extends
|
|
18
|
+
Timeout<OcrEmptyChain>,
|
|
19
|
+
OcrBaseQueryChain<OcrWithoutLanguage>
|
|
20
|
+
{}
|
|
21
|
+
|
|
22
|
+
// + language -timeout
|
|
13
23
|
interface OcrWithoutTimeout extends
|
|
24
|
+
Language<OcrEmptyChain>,
|
|
14
25
|
OcrBaseQueryChain<OcrWithoutTimeout>
|
|
15
26
|
{}
|
|
16
27
|
|
|
28
|
+
interface OcrEmptyChain extends
|
|
29
|
+
OcrBaseQueryChain<OcrEmptyChain>
|
|
30
|
+
{}
|
|
31
|
+
|
|
17
32
|
interface OcrBaseQueryChain<TSelf> extends
|
|
18
33
|
BaseEmptyChain<TSelf, OcrQueryResult, OcrAbandonedChain>,
|
|
19
34
|
Assertable<TSelf>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import {ValueOf} from './utils';
|
|
1
2
|
import {LaunchModeValues} from './constants/LaunchMode';
|
|
3
|
+
import {Lang} from './constants/Langs';
|
|
4
|
+
import {Accuracy} from './constants/Accuracy';
|
|
2
5
|
|
|
3
6
|
export interface Thenable <R> {
|
|
4
7
|
then <U> (onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
|
|
@@ -264,3 +267,15 @@ export interface HandleModifier<T> {
|
|
|
264
267
|
export interface GetAttributesModifier<T> {
|
|
265
268
|
getAttributes(attributes?: string[]): T;
|
|
266
269
|
}
|
|
270
|
+
|
|
271
|
+
export interface InRegionModifier<T> {
|
|
272
|
+
inRegion(region: [number, number, number, number]): T;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface Language<T> {
|
|
276
|
+
language(lang: ValueOf<Lang>): T;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface AccuracyModifier<T> {
|
|
280
|
+
accuracy(accuracy: ValueOf<Accuracy>): T;
|
|
281
|
+
}
|
|
@@ -1,6 +1,56 @@
|
|
|
1
|
+
import {AccuracyModifier, Assertable, InRegionModifier, Negatable, Timeout, VisibleModifier} from '../modifiers';
|
|
2
|
+
|
|
1
3
|
export type ValueOf<T> = T[keyof T];
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @description utility type for checking that TObject is satisfies TSubject (aka satisfies operator)
|
|
5
7
|
*/
|
|
6
8
|
export type Satisfies<TObject extends TSubject, TSubject> = TObject;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @description utility type for converting given interface for describe suitest chains
|
|
12
|
+
* @example
|
|
13
|
+
*
|
|
14
|
+
* interface ExampleInterface {
|
|
15
|
+
* method1: () => ChainWithoutMethods<ExampleInterface, 'method1'>;
|
|
16
|
+
* method2: () => ChainWithoutMethods<ExampleInterface, 'method2'>;
|
|
17
|
+
* method3: () => string;
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* type ChainableExample = Chainable<ExampleInterface>;
|
|
21
|
+
*
|
|
22
|
+
* declare const example: ChainableExample;
|
|
23
|
+
*
|
|
24
|
+
* example
|
|
25
|
+
* .method1()
|
|
26
|
+
* .method3(); // -> string will be returned
|
|
27
|
+
*
|
|
28
|
+
* example
|
|
29
|
+
* .method1()
|
|
30
|
+
* .method1(); // -> will be displayed error since method1 already called and no longer presents in chain
|
|
31
|
+
*
|
|
32
|
+
* example
|
|
33
|
+
* .method1()
|
|
34
|
+
* .method2()
|
|
35
|
+
* .method2(); // -> will be displayed error since method2 already called and no longer presents in chain;
|
|
36
|
+
*/
|
|
37
|
+
export type Chainable<T> = {
|
|
38
|
+
[K in keyof T]: T[K] extends (...args: any[]) => ChainWithoutMethods<T, infer ExcludeMethods>
|
|
39
|
+
// if field is function than return new chain without specified methods names
|
|
40
|
+
? (...args: Parameters<T[K]>) => Chainable<WithoutMethods<T, ExcludeMethods>>
|
|
41
|
+
// will be returned as it is, if field not function or function which not return MethodToOmit helper type
|
|
42
|
+
: T[K];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type WithoutMethods<T, K extends keyof T> = Omit<T, K>;
|
|
46
|
+
|
|
47
|
+
export type ChainWithoutMethods<T, ExcludeMethods extends keyof T> = {
|
|
48
|
+
__excludeMethods: ExcludeMethods,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type NegatableMethodsNames = keyof Negatable<any>;
|
|
52
|
+
export type TimeoutMethodsNames = keyof Timeout<any>;
|
|
53
|
+
export type VisibleMethodsNames = keyof VisibleModifier<any>;
|
|
54
|
+
export type InRegionMethodsNames = keyof InRegionModifier<any>;
|
|
55
|
+
export type AssertableMethodsNames = keyof Assertable<any>;
|
|
56
|
+
export type AccuracyModifierNames = keyof AccuracyModifier<any>;
|