Skip to content

Commit f5f6278

Browse files
committed
Implement ImageComparator with tests, add draft of VisualStateProvider
1 parent e024d1b commit f5f6278

File tree

7 files changed

+244
-1
lines changed

7 files changed

+244
-1
lines changed

src/main/java/aquality/selenium/core/applications/AqualityModule.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import aquality.selenium.core.utilities.IElementActionRetrier;
1313
import aquality.selenium.core.utilities.ISettingsFile;
1414
import aquality.selenium.core.utilities.IUtilitiesModule;
15+
import aquality.selenium.core.visualization.IImageComparator;
16+
import aquality.selenium.core.visualization.IVisualizationModule;
1517
import aquality.selenium.core.waitings.IConditionalWait;
1618
import aquality.selenium.core.waitings.IWaitingsModule;
1719
import com.google.inject.AbstractModule;
@@ -22,7 +24,8 @@
2224
* Describes all dependencies which is registered for the project.
2325
*/
2426
public class AqualityModule<T extends IApplication> extends AbstractModule
25-
implements IConfigurationsModule, IElementsModule, ILocalizationModule, IUtilitiesModule, IWaitingsModule {
27+
implements IConfigurationsModule, IElementsModule, ILocalizationModule, IUtilitiesModule, IWaitingsModule,
28+
IVisualizationModule {
2629

2730
private final Provider<T> applicationProvider;
2831

@@ -49,5 +52,6 @@ protected void configure() {
4952
bind(IConditionalWait.class).to(getConditionalWaitImplementation());
5053
bind(IElementFinder.class).to(getElementFinderImplementation());
5154
bind(IElementFactory.class).to(getElementFactoryImplementation());
55+
bind(IImageComparator.class).to(getImageComparatorImplementation());
5256
}
5357
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package aquality.selenium.core.visualization;
2+
3+
import java.awt.*;
4+
5+
/**
6+
* Compares images with defined threshold.
7+
* Default implementation does resize and gray-scaling to simplify comparison.
8+
*/
9+
public interface IImageComparator {
10+
11+
/**
12+
* Gets the difference between two images as a percentage.
13+
* @param thisOne The first image
14+
* @param theOtherOne The image to compare with
15+
* @param threshold How big a difference will be ignored as a percentage - value between 0 and 1.
16+
* @return The difference between the two images as a percentage - value between 0 and 1.
17+
*/
18+
float percentageDifference(Image thisOne, Image theOtherOne, float threshold);
19+
20+
/**
21+
* Gets the difference between two images as a percentage.
22+
* @param thisOne The first image
23+
* @param theOtherOne The image to compare with
24+
* @return The difference between the two images as a percentage - value between 0 and 1.
25+
*/
26+
float percentageDifference(Image thisOne, Image theOtherOne);
27+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package aquality.selenium.core.visualization;
2+
3+
/**
4+
* Describes implementations of visualization services to be registered in DI container.
5+
*/
6+
public interface IVisualizationModule {
7+
/**
8+
* @return class which implements {@link IImageComparator}
9+
*/
10+
default Class<? extends IImageComparator> getImageComparatorImplementation() {
11+
return ImageComparator.class;
12+
}
13+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package aquality.selenium.core.visualization;
2+
3+
import java.awt.*;
4+
import java.awt.image.BufferedImage;
5+
6+
public class ImageComparator implements IImageComparator {
7+
private static final int DEFAULT_THRESHOLD = 3;
8+
private static final int THRESHOLD_DIVISOR = 255;
9+
private final int comparisonHeight = 16;
10+
private final int comparisonWidth = 16;
11+
12+
public float percentageDifference(Image thisOne, Image theOtherOne, float threshold) {
13+
if (threshold < 0 || threshold > 1) {
14+
throw new IllegalArgumentException(String.format("Threshold should be between 0 and 1, but was [%s]", threshold));
15+
}
16+
17+
int intThreshold = Float.valueOf(threshold * THRESHOLD_DIVISOR).intValue();
18+
return percentageDifference(thisOne, theOtherOne, intThreshold);
19+
}
20+
21+
public float percentageDifference(Image thisOne, Image theOtherOne) {
22+
return percentageDifference(thisOne, theOtherOne, DEFAULT_THRESHOLD);
23+
}
24+
25+
protected float percentageDifference(Image thisOne, Image theOtherOne, int threshold) {
26+
int[][] differences = getDifferences(thisOne, theOtherOne);
27+
28+
int diffPixels = 0;
29+
30+
for (int[] bytes : differences) {
31+
for (int b : bytes) {
32+
if (b > threshold) {
33+
diffPixels++;
34+
}
35+
}
36+
}
37+
38+
return diffPixels / (float) (comparisonWidth * comparisonHeight);
39+
}
40+
41+
protected int[][] getDifferences(Image thisOne, Image theOtherOne) {
42+
int[][] firstGray = getResizedGrayScaleValues(thisOne);
43+
int[][] secondGray = getResizedGrayScaleValues(theOtherOne);
44+
45+
int[][] differences = new int[comparisonWidth][comparisonHeight];
46+
for (int y = 0; y < comparisonHeight; y++) {
47+
for (int x = 0; x < comparisonWidth; x++) {
48+
differences[x][y] = (byte) Math.abs(firstGray[x][y] - secondGray[x][y]);
49+
}
50+
}
51+
52+
return differences;
53+
}
54+
55+
protected int[][] getResizedGrayScaleValues(Image image) {
56+
BufferedImage resizedImage = new BufferedImage(comparisonWidth, comparisonHeight, BufferedImage.TYPE_BYTE_GRAY);
57+
Graphics2D graphics2D = resizedImage.createGraphics();
58+
graphics2D.drawImage(image, 0, 0, comparisonWidth, comparisonHeight, null);
59+
graphics2D.dispose();
60+
int[][] grayScale = new int[comparisonWidth][comparisonHeight];
61+
for (int y = 0; y < comparisonHeight; y++) {
62+
for (int x = 0; x < comparisonWidth; x++) {
63+
int pixel = resizedImage.getRGB(x, y);
64+
int red = (pixel >> 16) & 0xff;
65+
grayScale[x][y] = Math.abs(red);
66+
}
67+
}
68+
69+
return grayScale;
70+
}
71+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package aquality.selenium.core.visualization;
2+
3+
import org.openqa.selenium.Dimension;
4+
import org.openqa.selenium.OutputType;
5+
import org.openqa.selenium.Point;
6+
import org.openqa.selenium.remote.RemoteWebElement;
7+
8+
import javax.imageio.ImageIO;
9+
import java.awt.*;
10+
import java.awt.image.BufferedImage;
11+
import java.io.ByteArrayInputStream;
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.util.function.Supplier;
15+
16+
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
17+
18+
public class VisualStateProvider {
19+
20+
private final Supplier<RemoteWebElement> getElement;
21+
22+
public VisualStateProvider(Supplier<RemoteWebElement> getElement){
23+
this.getElement = getElement;
24+
}
25+
26+
public Dimension getSize() {
27+
return getElement.get().getSize();
28+
}
29+
30+
public Point getLocation() {
31+
return getElement.get().getLocation();
32+
}
33+
34+
public Image getImage() {
35+
byte[] bytes = getElement.get().getScreenshotAs(OutputType.BYTES);
36+
try (InputStream is = new ByteArrayInputStream(bytes)) {
37+
return ImageIO.read(is);
38+
} catch (IOException exception) {
39+
//log
40+
return new BufferedImage(0, 0, TYPE_INT_RGB);
41+
}
42+
}
43+
}

src/test/java/tests/applications/browser/CachedLabel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import aquality.selenium.core.elements.interfaces.IElementFinder;
66
import aquality.selenium.core.localization.ILocalizationManager;
77
import aquality.selenium.core.localization.ILocalizedLogger;
8+
import aquality.selenium.core.visualization.VisualStateProvider;
89
import aquality.selenium.core.waitings.IConditionalWait;
910
import org.openqa.selenium.By;
1011
import tests.applications.ICachedElement;
@@ -58,4 +59,8 @@ public ILocalizedLogger getLocalizedLogger() {
5859
public ILocalizationManager getLocalizationManager() {
5960
return AqualityServices.get(ILocalizationManager.class);
6061
}
62+
63+
public VisualStateProvider visual() {
64+
return new VisualStateProvider(this::getElement);
65+
}
6166
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package tests.visualization;
2+
3+
import aquality.selenium.core.visualization.IImageComparator;
4+
import org.testng.Assert;
5+
import org.testng.annotations.BeforeMethod;
6+
import org.testng.annotations.Test;
7+
import tests.applications.browser.AqualityServices;
8+
import tests.applications.browser.ITheInternetPageTest;
9+
import theinternet.DynamicLoadingForm;
10+
import theinternet.TheInternetPage;
11+
12+
import java.awt.*;
13+
14+
public class ImageComparatorTests implements ITheInternetPageTest {
15+
private final IImageComparator imageComparator = AqualityServices.get(IImageComparator.class);
16+
17+
private void startLoading() {
18+
DynamicLoadingForm.getStartLabel().click();
19+
}
20+
21+
@Override
22+
@BeforeMethod
23+
public void beforeMethod() {
24+
navigate(TheInternetPage.DYNAMIC_LOADING);
25+
}
26+
27+
@Test
28+
public void testGetPercentageDifferenceForSameElement() {
29+
Image firstImage = DynamicLoadingForm.getStartLabel().visual().getImage();
30+
Image secondImage = DynamicLoadingForm.getStartLabel().visual().getImage();
31+
32+
Assert.assertEquals(imageComparator.percentageDifference(firstImage, secondImage), 0);
33+
}
34+
35+
@Test
36+
public void testGetPercentageDifferenceForSameElementWithZeroThreshold() {
37+
Image firstImage = DynamicLoadingForm.getStartLabel().visual().getImage();
38+
Image secondImage = DynamicLoadingForm.getStartLabel().visual().getImage();
39+
40+
Assert.assertEquals(imageComparator.percentageDifference(firstImage, secondImage, 0), 0);
41+
}
42+
43+
@Test
44+
public void testGetPercentageDifferenceForDifferentElements() {
45+
Image firstImage = DynamicLoadingForm.getStartLabel().visual().getImage();
46+
startLoading();
47+
Image secondImage = DynamicLoadingForm.getLoadingLabel().visual().getImage();
48+
49+
Assert.assertNotEquals(imageComparator.percentageDifference(firstImage, secondImage), 0);
50+
}
51+
52+
@Test
53+
public void testGetPercentageDifferenceForDifferentElementsWithFullThreshold() {
54+
final int threshold = 1;
55+
Image firstImage = DynamicLoadingForm.getStartLabel().visual().getImage();
56+
startLoading();
57+
Image secondImage = DynamicLoadingForm.getLoadingLabel().visual().getImage();
58+
59+
Assert.assertEquals(imageComparator.percentageDifference(firstImage, secondImage, threshold), 0);
60+
}
61+
62+
@Test
63+
public void testGetPercentageDifferenceForSimilarElements() throws InterruptedException {
64+
startLoading();
65+
Image firstImage = DynamicLoadingForm.getLoadingLabel().visual().getImage();
66+
Thread.sleep(300);
67+
Image secondImage = DynamicLoadingForm.getLoadingLabel().visual().getImage();
68+
69+
Assert.assertTrue(imageComparator.percentageDifference(firstImage, secondImage, 0) != 0,
70+
"With zero threshold, there should be some difference");
71+
Assert.assertTrue(imageComparator.percentageDifference(firstImage, secondImage, 0.2f) <= 0.3,
72+
"With 0.2f threshold, the difference should be less or equal than 0.3");
73+
Assert.assertTrue(imageComparator.percentageDifference(firstImage, secondImage, 0.4f) <= 0.2,
74+
"With 0.4f threshold, the difference should be less or equal than 0.2");
75+
Assert.assertTrue(imageComparator.percentageDifference(firstImage, secondImage, 0.6f) <= 0.1,
76+
"With 0.6f threshold, the difference should be less or equal than 0.1");
77+
Assert.assertEquals(imageComparator.percentageDifference(firstImage, secondImage, 0.6f), 0,
78+
"With 0.8f threshold, the difference should be 0");
79+
}
80+
}

0 commit comments

Comments
 (0)