1
1
package aquality .selenium .elements .interfaces ;
2
2
3
3
import aquality .selenium .browser .AqualityServices ;
4
- import org .opencv .core .Core ;
5
- import org .opencv .core .Mat ;
6
- import org .opencv .core .MatOfByte ;
7
4
import org .opencv .core .Point ;
5
+ import org .opencv .core .*;
8
6
import org .opencv .imgcodecs .Imgcodecs ;
9
7
import org .opencv .imgproc .Imgproc ;
10
8
import org .openqa .selenium .*;
9
+ import org .openqa .selenium .interactions .Locatable ;
11
10
12
11
import java .io .File ;
13
12
import java .util .ArrayList ;
13
+ import java .util .Comparator ;
14
14
import java .util .List ;
15
+ import java .util .stream .Collectors ;
15
16
17
+ /**
18
+ * Locator to search elements by image.
19
+ * Takes screenshot and finds match using openCV.
20
+ * Then finds elements by coordinates using javascript.
21
+ */
16
22
public class ByImage extends By {
17
23
private static boolean wasLibraryLoaded = false ;
18
24
private final Mat template ;
@@ -25,16 +31,31 @@ private static void loadLibrary() {
25
31
}
26
32
}
27
33
34
+ /**
35
+ * Constructor accepting image file.
36
+ *
37
+ * @param file image file to locate element by.
38
+ */
28
39
public ByImage (File file ) {
29
40
loadLibrary ();
30
41
this .template = Imgcodecs .imread (file .getAbsolutePath (), Imgcodecs .IMREAD_UNCHANGED );
31
42
}
32
43
44
+ /**
45
+ * Constructor accepting image file.
46
+ *
47
+ * @param bytes image bytes to locate element by.
48
+ */
33
49
public ByImage (byte [] bytes ) {
34
50
loadLibrary ();
35
51
this .template = Imgcodecs .imdecode (new MatOfByte (bytes ), Imgcodecs .IMREAD_UNCHANGED );
36
52
}
37
53
54
+ @ Override
55
+ public String toString () {
56
+ return "ByImage: " + new Dimension (template .width (), template .height ());
57
+ }
58
+
38
59
@ Override
39
60
public List <WebElement > findElements (SearchContext context ) {
40
61
byte [] screenshotBytes = getScreenshot (context );
@@ -45,33 +66,61 @@ public List<WebElement> findElements(SearchContext context) {
45
66
float threshold = 1 - AqualityServices .getConfiguration ().getVisualizationConfiguration ().getDefaultThreshold ();
46
67
Core .MinMaxLocResult minMaxLoc = Core .minMaxLoc (result );
47
68
48
- if (minMaxLoc .maxVal < threshold ) {
49
- AqualityServices .getLogger ().warn (String .format ("No elements found by image [%s]" , template ));
50
- return new ArrayList <>(0 );
69
+ int matchCounter = (result .width () - template .width () + 1 ) * (result .height () - template .height () + 1 );
70
+ List <Point > matchLocations = new ArrayList <>();
71
+ while (matchCounter > 0 && minMaxLoc .maxVal >= threshold ) {
72
+ matchCounter --;
73
+ Point matchLocation = minMaxLoc .maxLoc ;
74
+ matchLocations .add (matchLocation );
75
+ Imgproc .rectangle (result , new Point (matchLocation .x , matchLocation .y ), new Point (matchLocation .x + template .cols (),
76
+ matchLocation .y + template .rows ()), new Scalar (0 , 0 , 0 ), -1 );
77
+ minMaxLoc = Core .minMaxLoc (result );
51
78
}
52
79
53
- return getElementsOnPoint ( minMaxLoc . maxLoc , context );
80
+ return matchLocations . stream (). map ( matchLocation -> getElementOnPoint ( matchLocation , context )). collect ( Collectors . toList () );
54
81
}
55
82
56
- private List <WebElement > getElementsOnPoint (Point matchLocation , SearchContext context ) {
57
- int centerX = (int )(matchLocation .x + (template .width () / 2 ));
58
- int centerY = (int )(matchLocation .y + (template .height () / 2 ));
59
-
60
- JavascriptExecutor js ;
61
- if (!(context instanceof JavascriptExecutor )) {
62
- AqualityServices .getLogger ().debug ("Current search context doesn't support executing scripts. " +
63
- "Will take browser js executor instead" );
64
- js = AqualityServices .getBrowser ().getDriver ();
83
+ /**
84
+ * Gets a single element on point (find by center coordinates, then select closest to matchLocation).
85
+ *
86
+ * @param matchLocation location of the upper-left point of the element.
87
+ * @param context search context.
88
+ * If the searchContext is Locatable (like WebElement), adjust coordinates to be absolute coordinates.
89
+ * @return the closest found element.
90
+ */
91
+ protected WebElement getElementOnPoint (Point matchLocation , SearchContext context ) {
92
+ if (context instanceof Locatable ) {
93
+ final org .openqa .selenium .Point point = ((Locatable ) context ).getCoordinates ().onPage ();
94
+ matchLocation .x += point .getX ();
95
+ matchLocation .y += point .getY ();
65
96
}
66
- else {
67
- js = (JavascriptExecutor ) context ;
68
- }
69
-
97
+ int centerX = (int ) (matchLocation .x + (template .width () / 2 ));
98
+ int centerY = (int ) (matchLocation .y + (template .height () / 2 ));
70
99
//noinspection unchecked
71
- return (List <WebElement >) js .executeScript ("return document.elementsFromPoint(arguments[0], arguments[1]);" , centerX , centerY );
100
+ List <WebElement > elements = (List <WebElement >) AqualityServices .getBrowser ().executeScript ("return document.elementsFromPoint(arguments[0], arguments[1]);" , centerX , centerY );
101
+ elements .sort (Comparator .comparingDouble (e -> distanceToPoint (matchLocation , e )));
102
+ return elements .get (0 );
103
+ }
104
+
105
+ /**
106
+ * Calculates distance from element to matching point.
107
+ *
108
+ * @param matchLocation matching point.
109
+ * @param element target element.
110
+ * @return distance in pixels.
111
+ */
112
+ protected static double distanceToPoint (Point matchLocation , WebElement element ) {
113
+ org .openqa .selenium .Point elementLocation = element .getLocation ();
114
+ return Math .sqrt (Math .pow (matchLocation .x - elementLocation .x , 2 ) + Math .pow (matchLocation .y - elementLocation .y , 2 ));
72
115
}
73
116
74
- private byte [] getScreenshot (SearchContext context ) {
117
+ /**
118
+ * Takes screenshot from searchContext if supported, or from browser.
119
+ *
120
+ * @param context search context for element location.
121
+ * @return captured screenshot as byte array.
122
+ */
123
+ protected byte [] getScreenshot (SearchContext context ) {
75
124
byte [] screenshotBytes ;
76
125
77
126
if (!(context instanceof TakesScreenshot )) {
0 commit comments