@wdio/ocr-service 1.0.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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +13 -0
- package/assets/target.png +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +165 -0
- package/dist/commands/ocrClickOnText.d.ts +3 -0
- package/dist/commands/ocrClickOnText.d.ts.map +1 -0
- package/dist/commands/ocrClickOnText.js +27 -0
- package/dist/commands/ocrGetElementPositionByText.d.ts +3 -0
- package/dist/commands/ocrGetElementPositionByText.d.ts.map +1 -0
- package/dist/commands/ocrGetElementPositionByText.js +46 -0
- package/dist/commands/ocrGetText.d.ts +3 -0
- package/dist/commands/ocrGetText.d.ts.map +1 -0
- package/dist/commands/ocrGetText.js +5 -0
- package/dist/commands/ocrSetValue.d.ts +3 -0
- package/dist/commands/ocrSetValue.d.ts.map +1 -0
- package/dist/commands/ocrSetValue.js +38 -0
- package/dist/commands/ocrWaitForTextDisplayed.d.ts +3 -0
- package/dist/commands/ocrWaitForTextDisplayed.d.ts.map +1 -0
- package/dist/commands/ocrWaitForTextDisplayed.js +8 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/service.d.ts +15 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +97 -0
- package/dist/types.d.ts +331 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/constants.d.ts +67 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +66 -0
- package/dist/utils/fuzzySearch.d.ts +3 -0
- package/dist/utils/fuzzySearch.d.ts.map +1 -0
- package/dist/utils/fuzzySearch.js +29 -0
- package/dist/utils/getData.d.ts +3 -0
- package/dist/utils/getData.d.ts.map +1 -0
- package/dist/utils/getData.js +71 -0
- package/dist/utils/getTextPositions.d.ts +3 -0
- package/dist/utils/getTextPositions.d.ts.map +1 -0
- package/dist/utils/getTextPositions.js +13 -0
- package/dist/utils/imageProcessing.d.ts +5 -0
- package/dist/utils/imageProcessing.d.ts.map +1 -0
- package/dist/utils/imageProcessing.js +73 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +46 -0
- package/dist/utils/sendKeys.d.ts +2 -0
- package/dist/utils/sendKeys.d.ts.map +1 -0
- package/dist/utils/sendKeys.js +21 -0
- package/dist/utils/tesseract.d.ts +13 -0
- package/dist/utils/tesseract.d.ts.map +1 -0
- package/dist/utils/tesseract.js +210 -0
- package/package.json +41 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/// <reference types="./standalone.js" />
|
|
2
|
+
/**
|
|
3
|
+
* Class options for OCR
|
|
4
|
+
*/
|
|
5
|
+
export interface OcrOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Adjusts the contrast of the image, value between -1 and 1
|
|
8
|
+
* Default: 0.25
|
|
9
|
+
*/
|
|
10
|
+
contrast?: number;
|
|
11
|
+
/**
|
|
12
|
+
* The path to store the images for OCR
|
|
13
|
+
*/
|
|
14
|
+
imagesFolder?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The language to use for OCR, can be found here:
|
|
17
|
+
* ocr/utils/constants.ts > SUPPORTED_LANGUAGES
|
|
18
|
+
* Default: eng
|
|
19
|
+
*/
|
|
20
|
+
language?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Browser command options
|
|
24
|
+
*/
|
|
25
|
+
type DefaultCommandOptions = {
|
|
26
|
+
contrast?: number;
|
|
27
|
+
haystack?: WebdriverIO.Element | ChainablePromiseElement | RectReturn;
|
|
28
|
+
language?: string;
|
|
29
|
+
};
|
|
30
|
+
export type GetTextOptions = DefaultCommandOptions & {};
|
|
31
|
+
export type GetElementPositionByTextOptions = DefaultCommandOptions & {
|
|
32
|
+
fuzzyFindOptions?: FuzzySearchOptions;
|
|
33
|
+
text: string;
|
|
34
|
+
};
|
|
35
|
+
export type WaitForTextDisplayedOptions = GetElementPositionByTextOptions & {
|
|
36
|
+
timeout?: number;
|
|
37
|
+
timeoutMsg?: string;
|
|
38
|
+
};
|
|
39
|
+
export type ClickOnTextOptions = GetElementPositionByTextOptions & {
|
|
40
|
+
clickDuration?: Number;
|
|
41
|
+
relativePosition?: RelativePosition;
|
|
42
|
+
};
|
|
43
|
+
export type SetValueOptions = ClickOnTextOptions & {
|
|
44
|
+
submitValue?: boolean;
|
|
45
|
+
value: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Method options
|
|
49
|
+
*/
|
|
50
|
+
type DefaultMethodOptions = DefaultCommandOptions & {
|
|
51
|
+
contrast: number;
|
|
52
|
+
isTesseractAvailable: boolean;
|
|
53
|
+
language: string;
|
|
54
|
+
ocrImagesPath: string;
|
|
55
|
+
};
|
|
56
|
+
export type OcrGetTextOptions = DefaultMethodOptions & {};
|
|
57
|
+
export type GetDataOptions = DefaultMethodOptions & {
|
|
58
|
+
cliFile?: string;
|
|
59
|
+
};
|
|
60
|
+
export type OcrGetElementPositionByTextOptions = DefaultMethodOptions & {
|
|
61
|
+
fuzzyFindOptions?: FuzzySearchOptions;
|
|
62
|
+
relativePosition?: RelativePosition;
|
|
63
|
+
text: string;
|
|
64
|
+
};
|
|
65
|
+
export type OcrWaitForTextDisplayedOptions = OcrGetElementPositionByTextOptions & {
|
|
66
|
+
timeout?: number;
|
|
67
|
+
timeoutMsg?: string;
|
|
68
|
+
};
|
|
69
|
+
export type OcrClickOnTextOptions = OcrGetElementPositionByTextOptions & {
|
|
70
|
+
clickDuration?: Number;
|
|
71
|
+
};
|
|
72
|
+
export type OcrSetValueOptions = OcrClickOnTextOptions & {
|
|
73
|
+
submitValue?: boolean;
|
|
74
|
+
value: string;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Internal Methods
|
|
78
|
+
*/
|
|
79
|
+
export type GetOcrData = {
|
|
80
|
+
text: string;
|
|
81
|
+
lines: Line[];
|
|
82
|
+
words: Words[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Internal types
|
|
86
|
+
*/
|
|
87
|
+
export type Line = {
|
|
88
|
+
text: string;
|
|
89
|
+
bbox: Rectangles;
|
|
90
|
+
};
|
|
91
|
+
export type Rectangles = {
|
|
92
|
+
left: number;
|
|
93
|
+
right: number;
|
|
94
|
+
top: number;
|
|
95
|
+
bottom: number;
|
|
96
|
+
};
|
|
97
|
+
export type Words = Line & {
|
|
98
|
+
wc: number;
|
|
99
|
+
};
|
|
100
|
+
export type TessaractDataOptions = {
|
|
101
|
+
filePath: string;
|
|
102
|
+
language: string;
|
|
103
|
+
};
|
|
104
|
+
export type ParseWordData = {
|
|
105
|
+
bbox: Rectangles;
|
|
106
|
+
wc: number;
|
|
107
|
+
};
|
|
108
|
+
export type GetTextPositionsOptions = DefaultMethodOptions & {};
|
|
109
|
+
export type GetTextPositions = {
|
|
110
|
+
dprPosition: Rectangles;
|
|
111
|
+
filePath: string;
|
|
112
|
+
originalPosition: Rectangles;
|
|
113
|
+
text: string;
|
|
114
|
+
};
|
|
115
|
+
export type FuzzyFindOptions = {
|
|
116
|
+
pattern: string;
|
|
117
|
+
searchOptions?: FuzzySearchOptions;
|
|
118
|
+
textArray: {
|
|
119
|
+
/**
|
|
120
|
+
* the matched string
|
|
121
|
+
*/
|
|
122
|
+
text: string;
|
|
123
|
+
/**
|
|
124
|
+
* The original position
|
|
125
|
+
*/
|
|
126
|
+
originalPosition: Rectangles;
|
|
127
|
+
/**
|
|
128
|
+
* The position after DPR check
|
|
129
|
+
* screenshots for iOS are with DPR
|
|
130
|
+
* position on the screen for iOS is smaller
|
|
131
|
+
*/
|
|
132
|
+
dprPosition: Rectangles;
|
|
133
|
+
}[];
|
|
134
|
+
};
|
|
135
|
+
export type FuzzySearchOptions = {
|
|
136
|
+
/**
|
|
137
|
+
* Determines how close the match must be to the fuzzy location (specified by location). An exact letter match
|
|
138
|
+
* which is distance characters away from the fuzzy location would score as a complete mismatch. A distance of
|
|
139
|
+
* 0 requires the match be at the exact location specified. A distance of 1000 would require a perfect match to
|
|
140
|
+
* be within 800 characters of the location to be found using a threshold of 0.8.
|
|
141
|
+
* Default: 100
|
|
142
|
+
*/
|
|
143
|
+
distance?: number;
|
|
144
|
+
/**
|
|
145
|
+
* Whether the search should be case sensitive
|
|
146
|
+
* Default: false
|
|
147
|
+
*/
|
|
148
|
+
isCaseSensitive?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Only the matches whose length exceeds this value will be returned.
|
|
151
|
+
* (For instance, if you want to ignore single character matches in the result, set it to 2)
|
|
152
|
+
* Default: 2
|
|
153
|
+
*/
|
|
154
|
+
minMatchCharLength?: number;
|
|
155
|
+
/**
|
|
156
|
+
* When true, the matching function will continue to the end of a search pattern even if a perfect match has
|
|
157
|
+
* already been located in the string.
|
|
158
|
+
* Default: false
|
|
159
|
+
*/
|
|
160
|
+
findAllMatches?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Determines approximately where in the text is the pattern expected to be found
|
|
163
|
+
* Default: 0
|
|
164
|
+
*/
|
|
165
|
+
location?: number;
|
|
166
|
+
/**
|
|
167
|
+
* At what point does the match algorithm give up. A threshold of 0.0 requires a perfect match (of both letters
|
|
168
|
+
* and location), a threshold of 1.0 would match anything.
|
|
169
|
+
* Default: 0.6
|
|
170
|
+
*/
|
|
171
|
+
threshold?: number;
|
|
172
|
+
};
|
|
173
|
+
export type FuzzyElement = {
|
|
174
|
+
/**
|
|
175
|
+
* The found item
|
|
176
|
+
*/
|
|
177
|
+
item: {
|
|
178
|
+
/**
|
|
179
|
+
* The position after DPR check
|
|
180
|
+
* screenshots for iOS are with DPR
|
|
181
|
+
* position on the screen for iOS is smaller
|
|
182
|
+
*/
|
|
183
|
+
dprPosition: Rectangles;
|
|
184
|
+
/**
|
|
185
|
+
* The path of the OCR screenshot
|
|
186
|
+
*/
|
|
187
|
+
filePath: string;
|
|
188
|
+
/**
|
|
189
|
+
* The original position
|
|
190
|
+
*/
|
|
191
|
+
originalPosition: Rectangles;
|
|
192
|
+
/**
|
|
193
|
+
* the matched string
|
|
194
|
+
*/
|
|
195
|
+
text: string;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Index of the fuzzy logic check
|
|
199
|
+
*/
|
|
200
|
+
refIndex: number;
|
|
201
|
+
/**
|
|
202
|
+
* Matched score of the fuzzy logic check
|
|
203
|
+
*/
|
|
204
|
+
score: number;
|
|
205
|
+
};
|
|
206
|
+
export type DetermineClickPointOptions = {
|
|
207
|
+
rectangles: Rectangles;
|
|
208
|
+
};
|
|
209
|
+
export type ClickPoint = {
|
|
210
|
+
x: number;
|
|
211
|
+
y: number;
|
|
212
|
+
};
|
|
213
|
+
export type RectReturn = {
|
|
214
|
+
x: number;
|
|
215
|
+
y: number;
|
|
216
|
+
width: number;
|
|
217
|
+
height: number;
|
|
218
|
+
};
|
|
219
|
+
export type RelativePosition = {
|
|
220
|
+
above?: number;
|
|
221
|
+
below?: number;
|
|
222
|
+
left?: number;
|
|
223
|
+
right?: number;
|
|
224
|
+
};
|
|
225
|
+
export type ProcessImageOptions = {
|
|
226
|
+
contrast: number;
|
|
227
|
+
elementRectangles?: RectReturn;
|
|
228
|
+
isAndroid: boolean;
|
|
229
|
+
isIOS: boolean;
|
|
230
|
+
ocrImagesPath: string;
|
|
231
|
+
screenshot: string;
|
|
232
|
+
};
|
|
233
|
+
export type ProcessImage = {
|
|
234
|
+
filePath: string;
|
|
235
|
+
};
|
|
236
|
+
export type DrawHighlightedWords = {
|
|
237
|
+
filePath: string;
|
|
238
|
+
highlights: Rectangles[];
|
|
239
|
+
};
|
|
240
|
+
export type ScreenshotSize = {
|
|
241
|
+
height: number;
|
|
242
|
+
width: number;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* xml2js System Tessaract Types
|
|
246
|
+
*/
|
|
247
|
+
export type UnprocessedSystemBlock = {
|
|
248
|
+
TextBlock?: UnprocessedSystemTextBlockElement[];
|
|
249
|
+
};
|
|
250
|
+
export type UnprocessedSystemTextBlockElement = {
|
|
251
|
+
TextLine: UnprocessedSystemTextLineElement[];
|
|
252
|
+
};
|
|
253
|
+
export type UnprocessedSystemTextLineElement = {
|
|
254
|
+
$: {
|
|
255
|
+
HPOS: string;
|
|
256
|
+
VPOS: string;
|
|
257
|
+
WIDTH: string;
|
|
258
|
+
HEIGHT: string;
|
|
259
|
+
};
|
|
260
|
+
String: UnprocessedSystemStringElement[];
|
|
261
|
+
};
|
|
262
|
+
export type UnprocessedSystemStringElement = {
|
|
263
|
+
$: {
|
|
264
|
+
CONTENT: string;
|
|
265
|
+
HPOS: string;
|
|
266
|
+
VPOS: string;
|
|
267
|
+
WIDTH: string;
|
|
268
|
+
HEIGHT: string;
|
|
269
|
+
WC: string;
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
export type TargetOptions = {
|
|
273
|
+
filePath: string;
|
|
274
|
+
targetX: number;
|
|
275
|
+
targetY: number;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* xml2js Nodejs Tessaract Types
|
|
279
|
+
*/
|
|
280
|
+
export type UnprocessedNodejsBlock = {
|
|
281
|
+
p?: UnprocessedNodejsParagraph[];
|
|
282
|
+
};
|
|
283
|
+
export type UnprocessedNodejsParagraph = {
|
|
284
|
+
span?: UnprocessedNodejsLine[];
|
|
285
|
+
};
|
|
286
|
+
export type UnprocessedNodejsLine = {
|
|
287
|
+
span?: UnprocessedNodejsWord[];
|
|
288
|
+
};
|
|
289
|
+
export type UnprocessedNodejsWord = {
|
|
290
|
+
_: string;
|
|
291
|
+
$: {
|
|
292
|
+
title: string;
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Browser command return types
|
|
297
|
+
*/
|
|
298
|
+
export interface GetData extends GetOcrData {
|
|
299
|
+
dpr: number;
|
|
300
|
+
filePath: string;
|
|
301
|
+
}
|
|
302
|
+
export type OcrGetElementPositionByText = {
|
|
303
|
+
/**
|
|
304
|
+
* The position after DPR check
|
|
305
|
+
* screenshots for iOS are with DPR
|
|
306
|
+
* position on the screen for iOS is smaller
|
|
307
|
+
*/
|
|
308
|
+
dprPosition: Rectangles;
|
|
309
|
+
/**
|
|
310
|
+
* The path of the OCR screenshot
|
|
311
|
+
*/
|
|
312
|
+
filePath: string;
|
|
313
|
+
/**
|
|
314
|
+
* the matched string
|
|
315
|
+
*/
|
|
316
|
+
matchedString: string;
|
|
317
|
+
/**
|
|
318
|
+
* The original position
|
|
319
|
+
*/
|
|
320
|
+
originalPosition: Rectangles;
|
|
321
|
+
/**
|
|
322
|
+
* the original search value
|
|
323
|
+
*/
|
|
324
|
+
searchValue: string;
|
|
325
|
+
/**
|
|
326
|
+
* Matched score of the fuzzy logic check
|
|
327
|
+
*/
|
|
328
|
+
score: number;
|
|
329
|
+
};
|
|
330
|
+
export {};
|
|
331
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export declare const DEFAULT_IMAGES_FOLDER = ".tmp/";
|
|
2
|
+
export declare const SUPPORTED_LANGUAGES: {
|
|
3
|
+
AFRIKAANS: string;
|
|
4
|
+
AMHARIC: string;
|
|
5
|
+
ARABIC: string;
|
|
6
|
+
ASSANESE: string;
|
|
7
|
+
AZERBAIJANI: string;
|
|
8
|
+
BELARUSIAN: string;
|
|
9
|
+
BENGALI: string;
|
|
10
|
+
TIBETAN: string;
|
|
11
|
+
BOSNIAN: string;
|
|
12
|
+
BULGARIAN: string;
|
|
13
|
+
CATALAN: string;
|
|
14
|
+
CZECH: string;
|
|
15
|
+
CHINESE: string;
|
|
16
|
+
CORSICAN: string;
|
|
17
|
+
DANISH: string;
|
|
18
|
+
GERMAN: string;
|
|
19
|
+
GREEK: string;
|
|
20
|
+
ENGLISH: string;
|
|
21
|
+
MATH: string;
|
|
22
|
+
ESTONIAN: string;
|
|
23
|
+
FINNISH: string;
|
|
24
|
+
FRENCH: string;
|
|
25
|
+
GAELIC: string;
|
|
26
|
+
IRISH: string;
|
|
27
|
+
HINDI: string;
|
|
28
|
+
CROATIAN: string;
|
|
29
|
+
HUNGARIAN: string;
|
|
30
|
+
ARMENIAN: string;
|
|
31
|
+
ITALIAN: string;
|
|
32
|
+
JAPANESE: string;
|
|
33
|
+
KANNADA: string;
|
|
34
|
+
KOREAN: string;
|
|
35
|
+
LATIN: string;
|
|
36
|
+
LATVIAN: string;
|
|
37
|
+
LITHUANIAN: string;
|
|
38
|
+
LUXEMBOURGISH: string;
|
|
39
|
+
MACEDONIAN: string;
|
|
40
|
+
DUTCH: string;
|
|
41
|
+
NORWEGIAN: string;
|
|
42
|
+
POLISH: string;
|
|
43
|
+
PORTUGUESE: string;
|
|
44
|
+
ROMANIAN: string;
|
|
45
|
+
RUSSIAN: string;
|
|
46
|
+
SLOVAK: string;
|
|
47
|
+
SLOVENIAN: string;
|
|
48
|
+
SPANISH: string;
|
|
49
|
+
ALBANIAN: string;
|
|
50
|
+
SERBIAN: string;
|
|
51
|
+
SWAHILI: string;
|
|
52
|
+
SWEDISH: string;
|
|
53
|
+
THAI: string;
|
|
54
|
+
TURKISH: string;
|
|
55
|
+
UKRAINIAN: string;
|
|
56
|
+
VIETNAMESE: string;
|
|
57
|
+
};
|
|
58
|
+
export declare const CONTRAST = 0.25;
|
|
59
|
+
export declare const DEFAULT_FUZZY_OPTIONS: {
|
|
60
|
+
distance: number;
|
|
61
|
+
isCaseSensitive: boolean;
|
|
62
|
+
findAllMatches: boolean;
|
|
63
|
+
location: number;
|
|
64
|
+
minMatchCharLength: number;
|
|
65
|
+
threshold: number;
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/utils/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,UAAU,CAAA;AAC5C,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuD/B,CAAA;AACD,eAAO,MAAM,QAAQ,OAAO,CAAA;AAC5B,eAAO,MAAM,qBAAqB;;;;;;;CAOjC,CAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const DEFAULT_IMAGES_FOLDER = '.tmp/';
|
|
2
|
+
export const SUPPORTED_LANGUAGES = {
|
|
3
|
+
AFRIKAANS: 'afr',
|
|
4
|
+
AMHARIC: 'amh',
|
|
5
|
+
ARABIC: 'ara',
|
|
6
|
+
ASSANESE: 'asm',
|
|
7
|
+
AZERBAIJANI: 'aze',
|
|
8
|
+
BELARUSIAN: 'bel',
|
|
9
|
+
BENGALI: 'bel',
|
|
10
|
+
TIBETAN: 'bod',
|
|
11
|
+
BOSNIAN: 'bos',
|
|
12
|
+
BULGARIAN: 'bul',
|
|
13
|
+
CATALAN: 'cat',
|
|
14
|
+
CZECH: 'cat',
|
|
15
|
+
CHINESE: 'chi_sim',
|
|
16
|
+
CORSICAN: 'cos',
|
|
17
|
+
DANISH: 'dan',
|
|
18
|
+
GERMAN: 'deu',
|
|
19
|
+
GREEK: 'ell',
|
|
20
|
+
ENGLISH: 'eng',
|
|
21
|
+
MATH: 'equ',
|
|
22
|
+
ESTONIAN: 'est',
|
|
23
|
+
FINNISH: 'fin',
|
|
24
|
+
FRENCH: 'fra',
|
|
25
|
+
GAELIC: 'gla',
|
|
26
|
+
IRISH: 'gle',
|
|
27
|
+
HINDI: 'hin',
|
|
28
|
+
CROATIAN: 'hrv',
|
|
29
|
+
HUNGARIAN: 'hun',
|
|
30
|
+
ARMENIAN: 'hye',
|
|
31
|
+
ITALIAN: 'ita',
|
|
32
|
+
JAPANESE: 'jpn',
|
|
33
|
+
KANNADA: 'kan',
|
|
34
|
+
KOREAN: 'kor',
|
|
35
|
+
LATIN: 'lat',
|
|
36
|
+
LATVIAN: 'lav',
|
|
37
|
+
LITHUANIAN: 'lit',
|
|
38
|
+
LUXEMBOURGISH: 'ltz',
|
|
39
|
+
MACEDONIAN: 'mkd',
|
|
40
|
+
DUTCH: 'nld',
|
|
41
|
+
NORWEGIAN: 'nor',
|
|
42
|
+
POLISH: 'pol',
|
|
43
|
+
PORTUGUESE: 'por',
|
|
44
|
+
ROMANIAN: 'ron',
|
|
45
|
+
RUSSIAN: 'rus',
|
|
46
|
+
SLOVAK: 'slk',
|
|
47
|
+
SLOVENIAN: 'slv',
|
|
48
|
+
SPANISH: 'spa',
|
|
49
|
+
ALBANIAN: 'sqi',
|
|
50
|
+
SERBIAN: 'srp',
|
|
51
|
+
SWAHILI: 'swa',
|
|
52
|
+
SWEDISH: 'swe',
|
|
53
|
+
THAI: 'tha',
|
|
54
|
+
TURKISH: 'tur',
|
|
55
|
+
UKRAINIAN: 'ukr',
|
|
56
|
+
VIETNAMESE: 'vie'
|
|
57
|
+
};
|
|
58
|
+
export const CONTRAST = 0.25;
|
|
59
|
+
export const DEFAULT_FUZZY_OPTIONS = {
|
|
60
|
+
distance: 100,
|
|
61
|
+
isCaseSensitive: false,
|
|
62
|
+
findAllMatches: false,
|
|
63
|
+
location: 0,
|
|
64
|
+
minMatchCharLength: 2,
|
|
65
|
+
threshold: 0.6,
|
|
66
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuzzySearch.d.ts","sourceRoot":"","sources":["../../src/utils/fuzzySearch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGjE,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,EAAE,GAAG,EAAE,CA4BxE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Fuse from 'fuse.js';
|
|
2
|
+
import { DEFAULT_FUZZY_OPTIONS } from './constants.js';
|
|
3
|
+
export function fuzzyFind(options) {
|
|
4
|
+
const { textArray, pattern, searchOptions } = options;
|
|
5
|
+
const fuzzyOptions = {
|
|
6
|
+
// Defaults that can be overwritten
|
|
7
|
+
...DEFAULT_FUZZY_OPTIONS,
|
|
8
|
+
// Provided options
|
|
9
|
+
...searchOptions,
|
|
10
|
+
// Defaults that should not be overwritten
|
|
11
|
+
// See https://fusejs.io/api/options.html for more options
|
|
12
|
+
...{
|
|
13
|
+
includeMatches: false,
|
|
14
|
+
includeScore: true,
|
|
15
|
+
ignoreFieldNorm: false,
|
|
16
|
+
ignoreLocation: false,
|
|
17
|
+
keys: ['text'],
|
|
18
|
+
shouldSort: true,
|
|
19
|
+
useExtendedSearch: false,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const fuse = new Fuse(textArray, fuzzyOptions);
|
|
23
|
+
return fuse.search(pattern).map((item) => {
|
|
24
|
+
if (item.score) {
|
|
25
|
+
item.score = item.score < 1e-10 ? 0 : item.score;
|
|
26
|
+
return item;
|
|
27
|
+
}
|
|
28
|
+
}).filter((item) => item !== undefined);
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getData.d.ts","sourceRoot":"","sources":["../../src/utils/getData.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAoB,OAAO,EAAE,cAAc,EAAqB,MAAM,aAAa,CAAA;AAM/F,wBAA8B,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA+E/E"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logger from '@wdio/logger';
|
|
2
|
+
import { browser } from '@wdio/globals';
|
|
3
|
+
import { getNodeOcrData, getSystemOcrData } from './tesseract.js';
|
|
4
|
+
import { adjustElementBbox, getScreenshotSize, isRectanglesObject } from './index.js';
|
|
5
|
+
import { drawHighlightedWords, processImage } from './imageProcessing.js';
|
|
6
|
+
const log = logger('@wdio/ocr-service:getData');
|
|
7
|
+
export default async function getData(options) {
|
|
8
|
+
const { contrast, cliFile, haystack, isTesseractAvailable, language, ocrImagesPath, } = options;
|
|
9
|
+
try {
|
|
10
|
+
let dpr = 1;
|
|
11
|
+
let screenshot;
|
|
12
|
+
if (!cliFile) {
|
|
13
|
+
const screenSize = await browser.getWindowSize();
|
|
14
|
+
screenshot = await browser.takeScreenshot();
|
|
15
|
+
const { width } = getScreenshotSize(screenshot);
|
|
16
|
+
dpr = width / screenSize.width;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
screenshot = cliFile;
|
|
20
|
+
}
|
|
21
|
+
const { filePath } = await processImage({ contrast, isAndroid: browser.isAndroid = false, isIOS: browser.isIOS = false, ocrImagesPath, screenshot });
|
|
22
|
+
let elementRectangles;
|
|
23
|
+
let croppedFilePath;
|
|
24
|
+
if (haystack) {
|
|
25
|
+
elementRectangles = isRectanglesObject(haystack) ? haystack : await browser.getElementRect((await haystack).elementId);
|
|
26
|
+
croppedFilePath = (await processImage({
|
|
27
|
+
contrast,
|
|
28
|
+
elementRectangles,
|
|
29
|
+
isAndroid: cliFile ? false : browser.isAndroid,
|
|
30
|
+
isIOS: cliFile ? false : browser.isIOS,
|
|
31
|
+
ocrImagesPath,
|
|
32
|
+
screenshot,
|
|
33
|
+
})).filePath;
|
|
34
|
+
}
|
|
35
|
+
// OCR the image
|
|
36
|
+
let ocrData;
|
|
37
|
+
const start = process.hrtime();
|
|
38
|
+
if (isTesseractAvailable) {
|
|
39
|
+
log.info('Using system installed version of Tesseract');
|
|
40
|
+
ocrData = await getSystemOcrData({ filePath: croppedFilePath || filePath, language });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
log.info('Using NodeJS version of Tesseract');
|
|
44
|
+
ocrData = await getNodeOcrData({ filePath: croppedFilePath || filePath, language });
|
|
45
|
+
}
|
|
46
|
+
if (haystack && elementRectangles) {
|
|
47
|
+
ocrData.lines.forEach((line) => {
|
|
48
|
+
line.bbox = adjustElementBbox(line.bbox, elementRectangles);
|
|
49
|
+
});
|
|
50
|
+
ocrData.words.forEach((word) => {
|
|
51
|
+
word.bbox = adjustElementBbox(word.bbox, elementRectangles);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const diff = process.hrtime(start);
|
|
55
|
+
const processTime = ((diff[0] * 1000000 + diff[1] / 1000) / 1000000).toFixed(3);
|
|
56
|
+
log.info(`It took '${processTime}s' to process the image.`);
|
|
57
|
+
log.info(`The following text was found through OCR:\n\n${ocrData.text.replace(/[\r\n]{2,}/g, '\n')}`);
|
|
58
|
+
// Get all words and highlight them
|
|
59
|
+
const highlights = ocrData.words.map((word) => word.bbox);
|
|
60
|
+
await drawHighlightedWords({ filePath, highlights });
|
|
61
|
+
log.info(`OCR Image with found text can be found here:\n\n${filePath}`);
|
|
62
|
+
return {
|
|
63
|
+
dpr,
|
|
64
|
+
filePath,
|
|
65
|
+
...ocrData,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
throw new Error(String(e));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getTextPositions.d.ts","sourceRoot":"","sources":["../../src/utils/getTextPositions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAQ,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAElF,wBAA8B,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAa5G"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import getData from './getData.js';
|
|
2
|
+
import { getDprPositions } from './index.js';
|
|
3
|
+
export default async function getTextPositions(options) {
|
|
4
|
+
const { dpr, filePath, words } = await getData(options);
|
|
5
|
+
return (words
|
|
6
|
+
.map(({ text, bbox }) => ({
|
|
7
|
+
dprPosition: getDprPositions(JSON.parse(JSON.stringify(bbox)), dpr),
|
|
8
|
+
filePath,
|
|
9
|
+
originalPosition: bbox,
|
|
10
|
+
text: text.replace(/(^\s+|\s+$)/g, ''),
|
|
11
|
+
}))
|
|
12
|
+
.filter((word) => word.text));
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DrawHighlightedWords, ProcessImage, ProcessImageOptions, TargetOptions } from '../types.js';
|
|
2
|
+
export declare function processImage({ contrast, elementRectangles, isAndroid, isIOS, ocrImagesPath, screenshot }: ProcessImageOptions): Promise<ProcessImage>;
|
|
3
|
+
export declare function drawTarget({ filePath, targetX, targetY }: TargetOptions): Promise<void>;
|
|
4
|
+
export declare function drawHighlightedWords({ filePath, highlights }: DrawHighlightedWords): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=imageProcessing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageProcessing.d.ts","sourceRoot":"","sources":["../../src/utils/imageProcessing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAKzG,wBAAsB,YAAY,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,EAAC,mBAAmB,GAChI,OAAO,CAAC,YAAY,CAAC,CAyBvB;AAED,wBAAsB,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,aAAa,iBAc7E;AAED,wBAAsB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,oBAAoB,iBA+BxF"}
|