Skip to content
This repository was archived by the owner on Oct 25, 2023. It is now read-only.

Commit c5e6eb1

Browse files
authored
feat: Support multiple image match in findByImage (#449)
1 parent a04ceca commit c5e6eb1

File tree

4 files changed

+63
-25
lines changed

4 files changed

+63
-25
lines changed

lib/basedriver/commands/find.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,7 @@ helpers.findByImage = async function findByImage (b64Template, {
229229
screenHeight);
230230
}
231231

232-
let rect = null;
233-
let b64Matched = null;
234-
let score = 0;
232+
const results = [];
235233
const condition = async () => {
236234
try {
237235
const {b64Screenshot, scale} = await this.getScreenshotForImageFind(screenWidth, screenHeight);
@@ -241,12 +239,19 @@ helpers.findByImage = async function findByImage (b64Template, {
241239
fixImageTemplateScale, ...scale
242240
});
243241

244-
const comparedImage = await this.compareImages(MATCH_TEMPLATE_MODE, b64Screenshot, b64Template, {threshold, visualize});
245-
rect = comparedImage.rect;
246-
b64Matched = comparedImage.visualization;
247-
score = comparedImage.score;
248-
242+
if (multiple) {
243+
results.push(...(await this.compareImages(MATCH_TEMPLATE_MODE,
244+
b64Screenshot,
245+
b64Template,
246+
{threshold, visualize, multiple})));
247+
} else {
248+
results.push(await this.compareImages(MATCH_TEMPLATE_MODE,
249+
b64Screenshot,
250+
b64Template,
251+
{threshold, visualize, multiple}));
252+
}
249253
return true;
254+
250255
} catch (err) {
251256
// if compareImages fails, we'll get a specific error, but we should
252257
// retry, so trap that and just return false to trigger the next round of
@@ -273,29 +278,28 @@ helpers.findByImage = async function findByImage (b64Template, {
273278
}
274279
}
275280

276-
if (!rect) {
281+
if (_.isEmpty(results)) {
277282
if (multiple) {
278283
return [];
279284
}
280285
throw new errors.NoSuchElementError();
281286
}
282287

283-
log.info(`Image template matched: ${JSON.stringify(rect)}`);
284-
if (b64Matched) {
285-
log.info(`Matched base64 data: ${b64Matched.substring(0, 200)}...`);
286-
}
287-
const imgEl = new ImageElement(b64Template, rect, score, b64Matched);
288+
const elements = results.map(({rect, score, visualization}) => {
289+
log.info(`Image template matched: ${JSON.stringify(rect)}`);
290+
return new ImageElement(b64Template, rect, score, visualization);
291+
});
288292

289293
// if we're just checking staleness, return straightaway so we don't add
290294
// a new element to the cache. shouldCheckStaleness does not support multiple
291295
// elements, since it is a purely internal mechanism
292296
if (shouldCheckStaleness) {
293-
return imgEl;
297+
return elements[0];
294298
}
295299

296-
const protocolEl = this.registerImageElement(imgEl);
300+
const registeredElements = elements.map((imgEl) => this.registerImageElement(imgEl));
297301

298-
return multiple ? [protocolEl] : protocolEl;
302+
return multiple ? registeredElements : registeredElements[0];
299303
};
300304

301305
/**

lib/basedriver/commands/images.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,21 @@ const DEFAULT_MATCH_THRESHOLD = 0.4;
2222
* @param {string} secondImage - Base64-encoded image file.
2323
* All image formats, that OpenCV library itself accepts, are supported.
2424
* @param {?Object} options [{}] - The content of this dictionary depends
25-
* on the actual `mode` value. See the documentation on `appium-support`
25+
* on the actual `mode` value.
26+
* For MATCH_TEMPLATE_MODE:
27+
* - visualize: include the visualization of the match in the result
28+
* (default: false)
29+
* - threshold: the image match threshold, higher values
30+
* require a closer image similarity to match
31+
* (default: 0.5)
32+
* - multiple: return multiple matches in the image
33+
* (default: false)
34+
* - matchNeighbourThreshold: if multiple is specified,
35+
* determine the number of pixels within
36+
* which to consider a pixel as part of the
37+
* same match result
38+
* (default: 10)
39+
* See the documentation in the `appium-support`
2640
* module for more details.
2741
* @returns {Object} The content of the resulting dictionary depends
2842
* on the actual `mode` and `options` values. See the documentation on
@@ -34,7 +48,8 @@ const DEFAULT_MATCH_THRESHOLD = 0.4;
3448
commands.compareImages = async function compareImages (mode, firstImage, secondImage, options = {}) {
3549
const img1 = Buffer.from(firstImage, 'base64');
3650
const img2 = Buffer.from(secondImage, 'base64');
37-
let result = {};
51+
let result = null;
52+
3853
switch (_.toLower(mode)) {
3954
case MATCH_FEATURES_MODE.toLowerCase():
4055
result = await imageUtil.getImagesMatches(img1, img2, options);
@@ -45,17 +60,34 @@ commands.compareImages = async function compareImages (mode, firstImage, secondI
4560
case MATCH_TEMPLATE_MODE.toLowerCase():
4661
// firstImage/img1 is the full image and secondImage/img2 is the partial one
4762
result = await imageUtil.getImageOccurrence(img1, img2, options);
63+
64+
if (options.multiple) {
65+
return result.multiple.map(convertVisualizationToBase64);
66+
}
4867
break;
4968
default:
5069
throw new errors.InvalidArgumentError(`'${mode}' images comparison mode is unknown. ` +
5170
`Only ${JSON.stringify([MATCH_FEATURES_MODE, GET_SIMILARITY_MODE, MATCH_TEMPLATE_MODE])} modes are supported.`);
5271
}
53-
if (!_.isEmpty(result.visualization)) {
54-
result.visualization = result.visualization.toString('base64');
55-
}
56-
return result;
72+
73+
return convertVisualizationToBase64(result);
5774
};
5875

76+
/**
77+
* base64 encodes the visualization part of the result
78+
* (if necessary)
79+
*
80+
* @param {OccurenceResult} element - occurrence result
81+
*
82+
**/
83+
function convertVisualizationToBase64 (element) {
84+
if (_.isString(element.visualization)) {
85+
element.visualization = element.visualization.toString('base64');
86+
}
87+
88+
return element;
89+
}
90+
5991
Object.assign(extensions, commands, helpers);
6092
export { commands, helpers, DEFAULT_MATCH_THRESHOLD, MATCH_TEMPLATE_MODE };
6193
export default extensions;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
],
3636
"dependencies": {
3737
"@babel/runtime": "^7.0.0",
38-
"appium-support": "^2.48.0",
38+
"appium-support": "^2.50.0",
3939
"async-lock": "^1.0.0",
4040
"asyncbox": "^2.3.1",
4141
"axios": "^0.x",

test/basedriver/commands/find-specs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ describe('finding elements by image', function () {
7474
});
7575
it('should find image elements happypath', async function () {
7676
const d = new TestDriver();
77-
basicStub(d);
77+
const {compareStub} = basicStub(d);
78+
compareStub.returns([{rect, score}]);
79+
7880
const els = await d.findByImage(template, {multiple: true});
7981
els.should.have.length(1);
8082
basicImgElVerify(els[0], d);

0 commit comments

Comments
 (0)