@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +13 -0
  4. package/assets/target.png +0 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +165 -0
  8. package/dist/commands/ocrClickOnText.d.ts +3 -0
  9. package/dist/commands/ocrClickOnText.d.ts.map +1 -0
  10. package/dist/commands/ocrClickOnText.js +27 -0
  11. package/dist/commands/ocrGetElementPositionByText.d.ts +3 -0
  12. package/dist/commands/ocrGetElementPositionByText.d.ts.map +1 -0
  13. package/dist/commands/ocrGetElementPositionByText.js +46 -0
  14. package/dist/commands/ocrGetText.d.ts +3 -0
  15. package/dist/commands/ocrGetText.d.ts.map +1 -0
  16. package/dist/commands/ocrGetText.js +5 -0
  17. package/dist/commands/ocrSetValue.d.ts +3 -0
  18. package/dist/commands/ocrSetValue.d.ts.map +1 -0
  19. package/dist/commands/ocrSetValue.js +38 -0
  20. package/dist/commands/ocrWaitForTextDisplayed.d.ts +3 -0
  21. package/dist/commands/ocrWaitForTextDisplayed.d.ts.map +1 -0
  22. package/dist/commands/ocrWaitForTextDisplayed.js +8 -0
  23. package/dist/index.d.ts +90 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +4 -0
  26. package/dist/service.d.ts +15 -0
  27. package/dist/service.d.ts.map +1 -0
  28. package/dist/service.js +97 -0
  29. package/dist/types.d.ts +331 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +1 -0
  32. package/dist/utils/constants.d.ts +67 -0
  33. package/dist/utils/constants.d.ts.map +1 -0
  34. package/dist/utils/constants.js +66 -0
  35. package/dist/utils/fuzzySearch.d.ts +3 -0
  36. package/dist/utils/fuzzySearch.d.ts.map +1 -0
  37. package/dist/utils/fuzzySearch.js +29 -0
  38. package/dist/utils/getData.d.ts +3 -0
  39. package/dist/utils/getData.d.ts.map +1 -0
  40. package/dist/utils/getData.js +71 -0
  41. package/dist/utils/getTextPositions.d.ts +3 -0
  42. package/dist/utils/getTextPositions.d.ts.map +1 -0
  43. package/dist/utils/getTextPositions.js +13 -0
  44. package/dist/utils/imageProcessing.d.ts +5 -0
  45. package/dist/utils/imageProcessing.d.ts.map +1 -0
  46. package/dist/utils/imageProcessing.js +73 -0
  47. package/dist/utils/index.d.ts +15 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +46 -0
  50. package/dist/utils/sendKeys.d.ts +2 -0
  51. package/dist/utils/sendKeys.d.ts.map +1 -0
  52. package/dist/utils/sendKeys.js +21 -0
  53. package/dist/utils/tesseract.d.ts +13 -0
  54. package/dist/utils/tesseract.d.ts.map +1 -0
  55. package/dist/utils/tesseract.js +210 -0
  56. package/package.json +41 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # @wdio/ocr-service
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - a924dfc: # 🚀 New Feature
8
+
9
+ Sometimes it can be hard to find an element in a mobile native app or desktop site, with an interactable Canvas, with the default [WebdriverIO selectors](https://webdriver.io/docs/selectors). In that case, it would be nice if you would be able to use something like OCR ([Optical Character Recognition](https://en.wikipedia.org/wiki/Optical_character_recognition)) to interact with elements on your device/screen.
10
+
11
+ The new `@wdio/ocr-service` service provides you with the option to interact with elements based on **visible text**. It will provide multiple commands to:
12
+
13
+ - wait
14
+ - search
15
+ - and interact
16
+
17
+ with an element, all based on text.
18
+
19
+ The following commands will be added
20
+
21
+ - `ocrGetText`
22
+ - `ocrGetElementPositionByText`
23
+ - `ocrWaitForTextDisplayed`
24
+ - `ocrClickOnText`
25
+ - `ocrSetValue`
26
+
27
+ A CLI command will also be provided to pre-check text received from the image. For a demo check this video
28
+
29
+ https://github.com/webdriverio/visual-testing/assets/11979740/6e4677ba-1463-4d6e-98b0-9a3bf70b702f
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 OpenJS Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # WebdriverIO OCR Service
2
+
3
+ > A WebdriverIO service for using OCR to interact with elements on a page.
4
+
5
+ ## Installation
6
+
7
+ The easiest way is to keep `@wdio/ocr-service` as a dev-dependency in your `package.json`, via:
8
+
9
+ ```sh
10
+ npm install @wdio/ocr-service --save-dev
11
+ ```
12
+
13
+ Instructions on how to get started can be found in the [visual testing](https://webdriver.io/docs/visual-testing) docs on the WebdriverIO project page.
Binary file
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const CONFIG_HELPER_INTRO = "\n=============================\n\uD83E\uDD16 WDIO OCR-Service Wizard \uD83E\uDDD9\n=============================\n";
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,mBAAmB,wHAI/B,CAAA"}
package/dist/cli.js ADDED
@@ -0,0 +1,165 @@
1
+ import inquirer from 'inquirer';
2
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import getData from './utils/getData.js';
5
+ import { createOcrDir } from './utils/index.js';
6
+ import { isSystemTesseractAvailable } from './utils/tesseract.js';
7
+ import { SUPPORTED_LANGUAGES } from './utils/constants.js';
8
+ export const CONFIG_HELPER_INTRO = `
9
+ =============================
10
+ 🤖 WDIO OCR-Service Wizard 🧙
11
+ =============================
12
+ `;
13
+ const currentPath = process.cwd();
14
+ function convertFileToBase64(filePath) {
15
+ if (!existsSync(filePath)) {
16
+ throw new Error(`File does not exist at: "${filePath}"`);
17
+ }
18
+ const fileBuffer = readFileSync(filePath);
19
+ const base64String = fileBuffer.toString('base64');
20
+ return base64String;
21
+ }
22
+ function listFilesAndFolders(folderPath) {
23
+ const items = readdirSync(folderPath, { withFileTypes: true });
24
+ const choices = items.filter(item => {
25
+ if (item.isDirectory() && !item.name.startsWith('node_modules')) {
26
+ return true;
27
+ }
28
+ return /\.(jpg|jpeg|png|gif)$/i.test(item.name);
29
+ }).map(item => {
30
+ const isDirectory = item.isDirectory();
31
+ const value = item.name + (isDirectory ? '/' : '');
32
+ return {
33
+ name: value,
34
+ value,
35
+ };
36
+ });
37
+ if (resolve(folderPath) !== resolve('/')) {
38
+ choices.push({
39
+ name: '\x1b[36m↩ Go Back\x1b[0m',
40
+ value: '..'
41
+ });
42
+ }
43
+ return choices;
44
+ }
45
+ async function chooseFolder(currentPath) {
46
+ async function prompt(srcPath) {
47
+ const choices = listFilesAndFolders(srcPath);
48
+ const answers = await inquirer.prompt([{
49
+ type: 'list',
50
+ name: 'path',
51
+ message: `Please choose a folder (current folder: ${srcPath})`,
52
+ choices: choices
53
+ }]);
54
+ const newPath = join(srcPath, answers.path);
55
+ if (answers.path === '..') {
56
+ // Clears the previous line
57
+ process.stdout.write('\u001b[1A\u001b[2K');
58
+ return prompt(newPath);
59
+ }
60
+ if (answers.path.endsWith('/')) {
61
+ // Clears the previous line
62
+ process.stdout.write('\u001b[1A\u001b[2K');
63
+ return prompt(newPath);
64
+ }
65
+ return newPath;
66
+ }
67
+ return prompt(currentPath);
68
+ }
69
+ async function main() {
70
+ const ocrDir = createOcrDir('.tmp/');
71
+ const defaultOptions = {
72
+ contrast: 0.25,
73
+ isTesseractAvailable: isSystemTesseractAvailable(),
74
+ language: SUPPORTED_LANGUAGES.ENGLISH,
75
+ ocrImagesPath: ocrDir,
76
+ };
77
+ let filePath;
78
+ console.log(CONFIG_HELPER_INTRO);
79
+ const initialChoice = await inquirer.prompt({
80
+ type: 'list',
81
+ name: 'method',
82
+ message: 'How would you like to specify the file?',
83
+ choices: [
84
+ { name: 'Use a "file explorer"', value: 'explore' },
85
+ { name: 'Type the file path manually', value: 'type' },
86
+ ],
87
+ });
88
+ if (initialChoice.method === 'explore') {
89
+ filePath = await chooseFolder(currentPath);
90
+ }
91
+ else {
92
+ const pathInput = await inquirer.prompt({
93
+ type: 'input',
94
+ name: 'path',
95
+ message: 'Please enter the file path:',
96
+ });
97
+ filePath = pathInput.path;
98
+ }
99
+ const useHaystack = await inquirer.prompt({
100
+ type: 'confirm',
101
+ name: 'use',
102
+ message: 'Would you like to use a haystack?',
103
+ });
104
+ let haystack;
105
+ if (useHaystack.use) {
106
+ const { x } = await inquirer.prompt({
107
+ type: 'input',
108
+ name: 'x',
109
+ message: 'Enter the x coordinate:',
110
+ filter: (input) => Number(input),
111
+ });
112
+ const { y } = await inquirer.prompt({
113
+ type: 'input',
114
+ name: 'y',
115
+ message: 'Enter the y coordinate:',
116
+ filter: (input) => Number(input),
117
+ });
118
+ const { width } = await inquirer.prompt({
119
+ type: 'input',
120
+ name: 'width',
121
+ message: 'Enter the width:',
122
+ filter: (input) => Number(input),
123
+ });
124
+ const { height } = await inquirer.prompt({
125
+ type: 'input',
126
+ name: 'height',
127
+ message: 'Enter the height:',
128
+ filter: (input) => Number(input),
129
+ });
130
+ haystack = { x, y, width, height };
131
+ }
132
+ let options = {
133
+ ...defaultOptions,
134
+ ...{ haystack },
135
+ cliFile: convertFileToBase64(filePath),
136
+ };
137
+ const { useAdvanced } = await inquirer.prompt([{
138
+ type: 'confirm',
139
+ name: 'useAdvanced',
140
+ message: 'Do you want to use the advanced mode?',
141
+ }]);
142
+ if (useAdvanced) {
143
+ const advancedOptions = await inquirer.prompt([
144
+ {
145
+ type: 'input',
146
+ name: 'contrast',
147
+ message: 'Enter new contrast (0.0 - 1.0):',
148
+ default: defaultOptions.contrast.toString(),
149
+ validate: (input) => {
150
+ const value = parseFloat(input);
151
+ return (value >= 0.0 && value <= 1.0) || 'Please enter a contrast between 0.0 and 1.0';
152
+ },
153
+ filter: (input) => input ? parseFloat(input) : defaultOptions.contrast
154
+ },
155
+ ]);
156
+ options = {
157
+ ...options,
158
+ contrast: advancedOptions.contrast,
159
+ };
160
+ }
161
+ console.log('\nProcessing image...\n');
162
+ await getData(options);
163
+ console.log('\nDone!');
164
+ }
165
+ main();
@@ -0,0 +1,3 @@
1
+ import type { OcrClickOnTextOptions } from '../types.js';
2
+ export default function ocrClickOnText(options: OcrClickOnTextOptions): Promise<void>;
3
+ //# sourceMappingURL=ocrClickOnText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrClickOnText.d.ts","sourceRoot":"","sources":["../../src/commands/ocrClickOnText.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAGxD,wBAA8B,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1F"}
@@ -0,0 +1,27 @@
1
+ import ocrGetElementPositionByText from './ocrGetElementPositionByText.js';
2
+ import { determineClickPoint } from '../utils/index.js';
3
+ import { drawTarget } from '../utils/imageProcessing.js';
4
+ export default async function ocrClickOnText(options) {
5
+ const element = await ocrGetElementPositionByText(options);
6
+ let { x, y } = determineClickPoint({ rectangles: element.dprPosition });
7
+ const { relativePosition } = options;
8
+ // Adjust the x and y coordinates based on the relative position
9
+ if (relativePosition) {
10
+ const { above = 0, below = 0, left = 0, right = 0 } = relativePosition;
11
+ x = x - left + right;
12
+ y = y - above + below;
13
+ }
14
+ // Draw a target on the image so a user can see where the click will happen
15
+ await drawTarget({ filePath: element.filePath, targetX: x, targetY: y });
16
+ const actionType = browser.isMobile ? 'touch' : 'mouse';
17
+ const clickDuration = options.clickDuration ? options.clickDuration : 500;
18
+ await browser
19
+ .action('pointer', {
20
+ parameters: { pointerType: actionType }
21
+ })
22
+ .move({ duration: 0, x, y })
23
+ .down({ button: 0 })
24
+ .pause(clickDuration)
25
+ .up({ button: 0 })
26
+ .perform();
27
+ }
@@ -0,0 +1,3 @@
1
+ import type { OcrGetElementPositionByText, OcrGetElementPositionByTextOptions } from '../types.js';
2
+ export default function ocrGetElementPositionByText(data: OcrGetElementPositionByTextOptions): Promise<OcrGetElementPositionByText>;
3
+ //# sourceMappingURL=ocrGetElementPositionByText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrGetElementPositionByText.d.ts","sourceRoot":"","sources":["../../src/commands/ocrGetElementPositionByText.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,2BAA2B,EAAE,kCAAkC,EAAE,MAAM,aAAa,CAAA;AAMhH,wBAA8B,2BAA2B,CACrD,IAAI,EAAE,kCAAkC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CAuDtC"}
@@ -0,0 +1,46 @@
1
+ import logger from '@wdio/logger';
2
+ import getTextPositions from '../utils/getTextPositions.js';
3
+ import { fuzzyFind } from '../utils/fuzzySearch.js';
4
+ const log = logger('@wdio/ocr-service:ocrGetElementPositionByText');
5
+ export default async function ocrGetElementPositionByText(data) {
6
+ const { contrast, haystack, fuzzyFindOptions, isTesseractAvailable, language, ocrImagesPath, text, } = data;
7
+ const textPositions = await getTextPositions({
8
+ contrast,
9
+ haystack,
10
+ isTesseractAvailable,
11
+ language,
12
+ ocrImagesPath,
13
+ });
14
+ const matches = fuzzyFind({
15
+ pattern: text,
16
+ searchOptions: fuzzyFindOptions,
17
+ textArray: textPositions,
18
+ });
19
+ let matchedTextElements;
20
+ let score;
21
+ if (matches.length === 0) {
22
+ log.warn(`No matches were found based on the word "${text}"`);
23
+ throw new Error(`Invalid Text Selector Match. Visual OCR has failed to find the word '${text}' in the image`);
24
+ }
25
+ else if (matches.length > 1) {
26
+ matches.sort((a, b) => (a.score > b.score ? 1 : -1));
27
+ matchedTextElements = matches[0];
28
+ score = Number(((1 - matchedTextElements.score) * 100).toFixed(2));
29
+ const messageOne = `Multiple matches were found based on the word "${text}".`;
30
+ const messageTwo = `The match "${matchedTextElements.item.text}" with score "${score}%" will be used.`;
31
+ log.info(`${messageOne} ${messageTwo}`);
32
+ }
33
+ else {
34
+ matchedTextElements = matches[0];
35
+ score = Number(((1 - matchedTextElements.score) * 100).toFixed(2));
36
+ log.info(`We searched for the word "${text}" and found one match "${matchedTextElements.item.text}" with score "${score}%"`);
37
+ }
38
+ return {
39
+ dprPosition: matchedTextElements.item.dprPosition,
40
+ filePath: matchedTextElements.item.filePath,
41
+ matchedString: matchedTextElements.item.text,
42
+ originalPosition: matchedTextElements.item.originalPosition,
43
+ score,
44
+ searchValue: text,
45
+ };
46
+ }
@@ -0,0 +1,3 @@
1
+ import type { OcrGetTextOptions } from '../types.js';
2
+ export default function ocrGetText(options: OcrGetTextOptions): Promise<string>;
3
+ //# sourceMappingURL=ocrGetText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrGetText.d.ts","sourceRoot":"","sources":["../../src/commands/ocrGetText.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,wBAA8B,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAIpF"}
@@ -0,0 +1,5 @@
1
+ import getData from '../utils/getData.js';
2
+ export default async function ocrGetText(options) {
3
+ const { text } = await getData(options);
4
+ return text.replace(/\n\s*\n/g, '\n');
5
+ }
@@ -0,0 +1,3 @@
1
+ import type { OcrSetValueOptions } from '../types.js';
2
+ export default function ocrSetValue(options: OcrSetValueOptions): Promise<void>;
3
+ //# sourceMappingURL=ocrSetValue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrSetValue.d.ts","sourceRoot":"","sources":["../../src/commands/ocrSetValue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIrD,wBAA8B,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDpF"}
@@ -0,0 +1,38 @@
1
+ import sendKeys from '../utils/sendKeys.js';
2
+ import ocrClickOnText from './ocrClickOnText.js';
3
+ export default async function ocrSetValue(options) {
4
+ const { contrast, clickDuration, haystack, fuzzyFindOptions, isTesseractAvailable, language, ocrImagesPath, submitValue = false, text, value, } = options;
5
+ // 1. First click on the position of the text to make sure it is intractable
6
+ await ocrClickOnText({
7
+ contrast,
8
+ clickDuration,
9
+ haystack,
10
+ fuzzyFindOptions,
11
+ isTesseractAvailable,
12
+ language,
13
+ ocrImagesPath,
14
+ text,
15
+ });
16
+ // 2. If Mobile then a keyboard might be shown
17
+ if (driver.isMobile) {
18
+ try {
19
+ // Wait for 3 seconds for the keyboard to be shown
20
+ await driver.waitUntil(async () => driver.isKeyboardShown(), { timeout: 3 * 1000 });
21
+ }
22
+ catch (ign) {
23
+ // Keyboard is not shown
24
+ }
25
+ }
26
+ // 3. Send the value to the active element
27
+ await sendKeys(value, submitValue);
28
+ // 4. If Mobile then hide the keyboard
29
+ if (driver.isMobile) {
30
+ try {
31
+ await driver.hideKeyboard();
32
+ await driver.waitUntil(async () => !(await driver.isKeyboardShown()), { timeout: 3 * 1000 });
33
+ }
34
+ catch (ign) {
35
+ // Keyboard is not present or not hidden
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ import type { OcrWaitForTextDisplayedOptions } from '../types.js';
2
+ export default function ocrWaitForTextDisplayed(options: OcrWaitForTextDisplayedOptions): Promise<import("../types.js").OcrGetElementPositionByText>;
3
+ //# sourceMappingURL=ocrWaitForTextDisplayed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ocrWaitForTextDisplayed.d.ts","sourceRoot":"","sources":["../../src/commands/ocrWaitForTextDisplayed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAA;AAGjE,wBAA8B,uBAAuB,CACjD,OAAO,EAAE,8BAA8B,8DAW1C"}
@@ -0,0 +1,8 @@
1
+ import ocrGetElementPositionByText from './ocrGetElementPositionByText.js';
2
+ export default async function ocrWaitForTextDisplayed(options) {
3
+ const { timeout, timeoutMsg } = options;
4
+ return driver.waitUntil(async () => ocrGetElementPositionByText(options), {
5
+ timeout: timeout || 180000,
6
+ timeoutMsg: timeoutMsg || `Could not find the text "${options.text}" within the requested time.`,
7
+ });
8
+ }
@@ -0,0 +1,90 @@
1
+ import WdioOcrService from './service.js';
2
+ import type { ClickOnTextOptions, GetElementPositionByTextOptions, GetTextOptions, OcrGetElementPositionByText, SetValueOptions, WaitForTextDisplayedOptions } from './types.js';
3
+ declare global {
4
+ namespace WebdriverIO {
5
+ interface Browser {
6
+ /**
7
+ * Get the text of an image base on OCR
8
+ */
9
+ ocrGetText(options?: GetTextOptions): Promise<string>;
10
+ /**
11
+ * Get the text of an image base on OCR
12
+ */
13
+ ocrGetElementPositionByText(options: GetElementPositionByTextOptions): Promise<OcrGetElementPositionByText>;
14
+ /**
15
+ * Click on an element based on text
16
+ */
17
+ ocrClickOnText(options: ClickOnTextOptions): Promise<void>;
18
+ /**
19
+ * Set value on an element based on text
20
+ */
21
+ ocrSetValue(options: SetValueOptions): Promise<void>;
22
+ /**
23
+ * Wait for the text to be displayed based on OCR
24
+ */
25
+ ocrWaitForTextDisplayed(options: WaitForTextDisplayedOptions): Promise<void>;
26
+ }
27
+ interface Element {
28
+ }
29
+ }
30
+ }
31
+ export default WdioOcrService;
32
+ export declare const SUPPORTED_OCR_LANGUAGES: {
33
+ AFRIKAANS: string;
34
+ AMHARIC: string;
35
+ ARABIC: string;
36
+ ASSANESE: string;
37
+ AZERBAIJANI: string;
38
+ BELARUSIAN: string;
39
+ BENGALI: string;
40
+ TIBETAN: string;
41
+ BOSNIAN: string;
42
+ BULGARIAN: string;
43
+ CATALAN: string;
44
+ CZECH: string;
45
+ CHINESE: string;
46
+ CORSICAN: string;
47
+ DANISH: string;
48
+ GERMAN: string;
49
+ GREEK: string;
50
+ ENGLISH: string;
51
+ MATH: string;
52
+ ESTONIAN: string;
53
+ FINNISH: string;
54
+ FRENCH: string;
55
+ GAELIC: string;
56
+ IRISH: string; /**
57
+ * Get the text of an image base on OCR
58
+ */
59
+ HINDI: string;
60
+ CROATIAN: string;
61
+ HUNGARIAN: string;
62
+ ARMENIAN: string;
63
+ ITALIAN: string;
64
+ JAPANESE: string;
65
+ KANNADA: string;
66
+ KOREAN: string;
67
+ LATIN: string;
68
+ LATVIAN: string;
69
+ LITHUANIAN: string;
70
+ LUXEMBOURGISH: string;
71
+ MACEDONIAN: string;
72
+ DUTCH: string;
73
+ NORWEGIAN: string;
74
+ POLISH: string;
75
+ PORTUGUESE: string;
76
+ ROMANIAN: string;
77
+ RUSSIAN: string;
78
+ SLOVAK: string;
79
+ SLOVENIAN: string;
80
+ SPANISH: string;
81
+ ALBANIAN: string;
82
+ SERBIAN: string;
83
+ SWAHILI: string;
84
+ SWEDISH: string;
85
+ THAI: string;
86
+ TURKISH: string;
87
+ UKRAINIAN: string;
88
+ VIETNAMESE: string;
89
+ };
90
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,cAAc,CAAA;AACzC,OAAO,KAAK,EACR,kBAAkB,EAClB,+BAA+B,EAC/B,cAAc,EACd,2BAA2B,EAC3B,eAAe,EACf,2BAA2B,EAC9B,MAAM,YAAY,CAAA;AAGnB,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,OAAO;YACb;;eAEG;YACH,UAAU,CACN,OAAO,CAAC,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,CAAC,CAAC;YAEnB;;eAEG;YACH,2BAA2B,CACvB,OAAO,EAAE,+BAA+B,GACzC,OAAO,CAAC,2BAA2B,CAAC,CAAC;YAExC;;eAEG;YACH,cAAc,CACV,OAAO,EAAE,kBAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;YAEjB;;eAEG;YACH,WAAW,CACP,OAAO,EAAE,eAAe,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;YAEjB;;eAEG;YACH,uBAAuB,CACnB,OAAO,EAAE,2BAA2B,GACrC,OAAO,CAAC,IAAI,CAAC,CAAC;SACpB;QACD,UAAU,OAAO;SAAI;KACxB;CACJ;AAED,eAAe,cAAc,CAAA;AAC7B,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;mBAjCxB;;OAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+B2C,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import WdioOcrService from './service.js';
2
+ import { SUPPORTED_LANGUAGES } from './utils/constants.js';
3
+ export default WdioOcrService;
4
+ export const SUPPORTED_OCR_LANGUAGES = SUPPORTED_LANGUAGES;
@@ -0,0 +1,15 @@
1
+ import type { OcrOptions } from './types.js';
2
+ export default class WdioOcrService {
3
+ #private;
4
+ private _browser?;
5
+ private _ocrDir;
6
+ private _ocrLanguage;
7
+ private _ocrContrast;
8
+ constructor(options: OcrOptions);
9
+ /**
10
+ * Set up the service if users want to use it in standalone mode
11
+ */
12
+ remoteSetup(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): Promise<void>;
13
+ before(capabilities: WebdriverIO.Capabilities, _specs: string[], browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): Promise<void>;
14
+ }
15
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAgB5C,MAAM,CAAC,OAAO,OAAO,cAAc;;IAC/B,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,YAAY,CAAQ;gBAEhB,OAAO,EAAE,UAAU;IAM/B;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IAIzE,MAAM,CACR,YAAY,EAAE,WAAW,CAAC,YAAY,EACtC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;CA2EpE"}
@@ -0,0 +1,97 @@
1
+ import logger from '@wdio/logger';
2
+ import { isSystemTesseractAvailable } from './utils/tesseract.js';
3
+ import { CONTRAST, DEFAULT_IMAGES_FOLDER, SUPPORTED_LANGUAGES } from './utils/constants.js';
4
+ import { createOcrDir } from './utils/index.js';
5
+ import ocrGetText from './commands/ocrGetText.js';
6
+ import ocrGetElementPositionByText from './commands/ocrGetElementPositionByText.js';
7
+ import ocrWaitForTextDisplayed from './commands/ocrWaitForTextDisplayed.js';
8
+ import ocrClickOnText from './commands/ocrClickOnText.js';
9
+ import ocrSetValue from './commands/ocrSetValue.js';
10
+ const log = logger('@wdio/ocr-service');
11
+ const ocrCommands = {
12
+ ocrGetText,
13
+ ocrGetElementPositionByText,
14
+ ocrWaitForTextDisplayed,
15
+ ocrClickOnText,
16
+ ocrSetValue,
17
+ };
18
+ export default class WdioOcrService {
19
+ _browser;
20
+ _ocrDir;
21
+ _ocrLanguage;
22
+ _ocrContrast;
23
+ constructor(options) {
24
+ this._ocrDir = createOcrDir(options?.imagesFolder || DEFAULT_IMAGES_FOLDER);
25
+ this._ocrLanguage = options?.language || SUPPORTED_LANGUAGES.ENGLISH;
26
+ this._ocrContrast = options?.contrast || CONTRAST;
27
+ }
28
+ /**
29
+ * Set up the service if users want to use it in standalone mode
30
+ */
31
+ async remoteSetup(browser) {
32
+ await this.before(browser.capabilities, [], browser);
33
+ }
34
+ async before(capabilities, _specs, browser) {
35
+ this._browser = browser;
36
+ if (!this._browser.isMultiremote) {
37
+ log.info('Adding commands to global browser');
38
+ await this.#addCommandsToBrowser(this._browser);
39
+ }
40
+ else {
41
+ await this.#extendMultiremoteBrowser(capabilities);
42
+ }
43
+ }
44
+ async #extendMultiremoteBrowser(capabilities) {
45
+ const browser = this._browser;
46
+ const browserNames = Object.keys(capabilities);
47
+ const self = this;
48
+ log.info(`Adding commands to Multi Browser: ${browserNames.join(', ')}`);
49
+ for (const browserName of browserNames) {
50
+ const multiremoteBrowser = browser;
51
+ const browserInstance = multiremoteBrowser.getInstance(browserName);
52
+ await this.#addCommandsToBrowser(browserInstance);
53
+ }
54
+ /**
55
+ * Add all OCR commands to the global browser object that will execute
56
+ * on each browser in the Multi Remote. This involves mapping through both the visualElementCommands,
57
+ * visualPageCommands, and ocrCommands.
58
+ */
59
+ for (const command of Object.keys(ocrCommands)) {
60
+ browser.addCommand(command, async function (...args) {
61
+ const returnData = {};
62
+ if (typeof args[0] === 'object' && args[0] !== null) {
63
+ const options = args[0];
64
+ options.contrast = options.contrast || self._ocrContrast;
65
+ args[0] = options;
66
+ }
67
+ for (const browserName of browserNames) {
68
+ const multiremoteBrowser = browser;
69
+ const browserInstance = multiremoteBrowser.getInstance(browserName);
70
+ if (typeof browserInstance[command] === 'function') {
71
+ returnData[browserName] = await browserInstance[command].apply(browserInstance, args);
72
+ }
73
+ else {
74
+ throw new Error(`Command ${command} is not a function on the browser instance ${browserName}`);
75
+ }
76
+ }
77
+ return returnData;
78
+ });
79
+ }
80
+ }
81
+ async #addCommandsToBrowser(currentBrowser) {
82
+ const isTesseractAvailable = isSystemTesseractAvailable();
83
+ const self = this;
84
+ for (const [commandName, command] of Object.entries(ocrCommands)) {
85
+ log.info(`Adding browser command "${commandName}" to browser object`);
86
+ currentBrowser.addCommand(commandName, function (options) {
87
+ return command({
88
+ ...options,
89
+ contrast: options.contrast || self._ocrContrast,
90
+ isTesseractAvailable,
91
+ language: options.language || self._ocrLanguage,
92
+ ocrImagesPath: self._ocrDir,
93
+ });
94
+ });
95
+ }
96
+ }
97
+ }