Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5e9eca5
Added base class for extension settings.
TMenting Nov 21, 2020
e9b5bc0
Added initial Tesseract initialization and teardown behavior.
TMenting Nov 27, 2020
36ecc75
Added result extraction.
TMenting Dec 8, 2020
dd5a30f
Added log4j facade
TMenting Dec 11, 2020
6ea63ad
Made download link to chromedriver clickable.
TMenting Dec 11, 2020
ac7bd37
Added initial OCR functionality.
TMenting Dec 11, 2020
aeeb193
Enable OCR by default during development.
TMenting Dec 12, 2020
5eb40c2
Rebase fix.
TMenting Dec 12, 2020
00350f6
Initial working text extractor.
TMenting Dec 16, 2020
edd523f
Added workaround for null pointer.
TMenting Dec 29, 2020
77dd386
Added workaround to support webdriver:
TMenting Dec 29, 2020
14d6454
Replaced raw points for TextElements with rectangle class.
TMenting Jan 16, 2021
315f91c
WIP: Initial location matcher implementation.
TMenting Jan 16, 2021
edd8a59
Fixed filtering for a role with different ancestors
TMenting Feb 25, 2021
9d7540b
Fixed dimension calculation for expected text.
TMenting Feb 27, 2021
08d4173
Sort and link the expected text based on their area size. Sorting is
TMenting Feb 27, 2021
9e1c4b7
WIP match content
TMenting Apr 11, 2021
1af3d98
By setting HWND for root element we can request display scale of both
TMenting Apr 11, 2021
8b1c88a
Take only visible web elements into account when extracting the expected
TMenting Apr 28, 2021
3379a46
Fixed parsing screenshots from webdriver protocol.
TMenting Jun 10, 2021
43a7660
Polished matcher algorithm.
TMenting Jun 12, 2021
afd73e7
Improved matcher result
TMenting Jun 23, 2021
5c3bfc1
Fixed rebase issues.
TMenting Jun 23, 2021
a7595fd
Added unit tests.
TMenting Jun 24, 2021
a8fbb59
Added visual validation support for action shots.
TMenting Jun 30, 2021
b7108a9
Fixed flaky test and added suppression for spamming log messages.
TMenting Jul 2, 2021
43eac80
Introduced Location as more print friendly alternative of Rectangle.
TMenting Jul 25, 2021
fef990d
Added verdict support and extended HTML report with validation result.
TMenting Jul 28, 2021
e701dea
Replaced hard-coded threshold with configuration threshold.
TMenting Sep 8, 2021
d557c47
Workaround for supporting Qt desktop applications.
TMenting Sep 15, 2021
0529884
Added ancestors debug information and improved HTML report by listing
TMenting Oct 15, 2021
397e7bd
Fixed extracting application name when SUT takes '\' arguments.
TMenting Oct 15, 2021
6874a9c
Fixed screenshot capture form MS word. Due to visual border effects the
TMenting Oct 15, 2021
d59d7be
Fixed replay for OCR recording.
TMenting Nov 27, 2021
35e9817
Renamed VisualMatcher to VisualMatcherInterface.
TMenting Apr 5, 2022
2758f8e
Merge remote-tracking branch 'upstream/getWidgetsMatchingMultipleAttr…
TMenting Apr 6, 2022
1e49d4d
Fixed incorrect merge conflict.
TMenting Apr 7, 2022
d66fb4a
Disabled the visual validation by default.
TMenting Apr 7, 2022
423f3c3
Added default value for obtaining the handle.
TMenting Apr 7, 2022
4c79ce4
Set to 0 instead of null to bypass a null check.
TMenting Apr 7, 2022
e2e5a61
Merge remote-tracking branch 'upstream/master' into reporting.
TMenting Aug 8, 2022
e446795
Merge branch 'master' into pr/299
ferpasri Sep 27, 2022
0e89fd8
update mockito dependency
ferpasri Sep 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions core/src/org/testar/ProtocolUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,20 @@ public static String getStateshot(State state){
*/
public static AWTCanvas getStateshotBinary(State state) {
Shape viewPort = null;
if (state.childCount() > 0){
viewPort = state.child(0).get(Tags.Shape, null);
if (viewPort != null && (viewPort.width() * viewPort.height() < 1))
viewPort = null;
for (int index = 0; index < state.childCount(); index++){
// While testing Word (2109 build 14430.20270) we noticed that the height of the screenshots was only 5px.
// After investigation, we noticed that root contained 5 children. Four had "MSO_BORDEREFFECT_WINDOW_CLASS"
// and had none children. And only one was called "OpusApp" and contained child elements.
// Ideally this check is more strict; (frameworkId == Win32 and classname != MSO_BORDEREFFECT_WINDOW_CLASS)
// but unfortunately these tags are not available at this generic level. Previous implementation directly
// used the first child. However, it makes more sense to select the widget which contains children.
if (state.child(index).childCount() != 0) {
viewPort = state.child(index).get(Tags.Shape, null);
if (viewPort != null && (viewPort.width() * viewPort.height() < 1)) {
viewPort = null;
}
break;
}
}

//If the state Shape is not properly obtained, or the State has an error, use full monitor screen
Expand All @@ -260,8 +270,8 @@ public static AWTCanvas getStateshotBinary(State state) {
AWTCanvas scrshot = AWTCanvas.fromScreenshot(Rect.from(viewPort.x(), viewPort.y(), viewPort.width(), viewPort.height()), getRootWindowHandle(state), AWTCanvas.StorageFormat.PNG, 1);
return scrshot;
}
public static String getActionshot(State state, Action action){

public static AWTCanvas getActionshot(State state, Action action){
List<Finder> targets = action.get(Tags.Targets, null);
if (targets != null){
Widget w;
Expand All @@ -274,14 +284,14 @@ public static String getActionshot(State state, Action action){
r = new Rectangle((int)s.x(), (int)s.y(), (int)s.width(), (int)s.height());
actionArea = actionArea.union(r);
}
if (actionArea.isEmpty())
if (actionArea.isEmpty()) {
return null;
AWTCanvas scrshot = AWTCanvas.fromScreenshot(Rect.from(actionArea.x, actionArea.y, actionArea.width, actionArea.height), getRootWindowHandle(state),
}
return AWTCanvas.fromScreenshot(Rect.from(actionArea.x, actionArea.y, actionArea.width, actionArea.height), getRootWindowHandle(state),
AWTCanvas.StorageFormat.PNG, 1);
return ScreenshotSerialiser.saveActionshot(state.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"), action.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"), scrshot);
}
return null;
}
}

private static long getRootWindowHandle(State state) {
long windowHandle = 0;
Expand Down
6 changes: 3 additions & 3 deletions core/src/org/testar/monkey/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ public static String readFile(File path) {
}

public static File createTempDir() {
return createTempDir("org.fruit.", Long.toString(System.nanoTime()));
return createTempDir("org.testar.monkey.", Long.toString(System.nanoTime()));
}

public static File createTempDir(String pref, String suff) {
Expand All @@ -562,11 +562,11 @@ public static File createTempDir(String pref, String suff) {
}

public static File createTempFile() {
return createTempFile("org.fruit.", Long.toString(System.nanoTime()), null);
return createTempFile("org.testar.monkey.", Long.toString(System.nanoTime()), null);
}

public static File createTempFile(String content) {
return createTempFile("org.fruit.", Long.toString(System.nanoTime()), content);
return createTempFile("org.testar.monkey.", Long.toString(System.nanoTime()), content);
}

public static File createTempFile(String pref, String suff, String content) {
Expand Down
63 changes: 29 additions & 34 deletions core/src/org/testar/monkey/alayer/AWTCanvas.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
Expand All @@ -74,7 +75,7 @@

public class AWTCanvas implements Image, Canvas {

public static enum StorageFormat{ JPEG, PNG, BMP; }
public enum StorageFormat{ JPEG, PNG, BMP }

public static void saveAsJpeg(BufferedImage image, OutputStream os, double quality) throws IOException{
if(quality == 1){
Expand Down Expand Up @@ -131,12 +132,8 @@ public static AWTCanvas fromScreenshot(Rect r, long windowHandle, StorageFormat
}

public static AWTCanvas fromFile(String file) throws IOException{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File(file)));

try{
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
return fromInputStream(bis);
}finally{
bis.close();
}
}

Expand All @@ -154,9 +151,9 @@ public static AWTCanvas fromInputStream(InputStream is) throws IOException{

private static final long serialVersionUID = -5041497503329308870L;
protected transient BufferedImage img;
private StorageFormat format;
private double quality;
private double x, y;
private final StorageFormat format;
private final double quality;
private final double x, y;
transient Graphics2D gr;
static final Pen defaultPen = Pen.PEN_DEFAULT;
double fontSize, strokeWidth;
Expand Down Expand Up @@ -188,11 +185,8 @@ public AWTCanvas(double x, double y, BufferedImage image, StorageFormat format,
this.format = format;
this.quality = quality;
gr = img.createGraphics();
// gr.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

adjustPen(defaultPen);
//gr.setComposite(AlphaComposite.Clear);

adjustPen(defaultPen);
}

public void begin() {}
Expand All @@ -203,7 +197,16 @@ public void end() {}
public double x(){ return x; }
public double y(){ return y; }
public BufferedImage image(){ return img; }


/**
* @return A deep copy of the image.
*/
public BufferedImage deepCopyImage() {
ColorModel cm = img.getColorModel();
WritableRaster raster = img.copyData(img.getRaster().createCompatibleWritableRaster());
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
}

private void adjustPen(Pen pen){
Double tstrokeWidth = pen.strokeWidth();
if(tstrokeWidth == null)
Expand All @@ -221,7 +224,7 @@ private void adjustPen(Pen pen){
strokePattern = tstrokePattern;
strokeWidth = tstrokeWidth;
strokeCaps = tstrokeCaps;
gr.setStroke(new BasicStroke((float)(double)strokeWidth));
gr.setStroke(new BasicStroke((float)strokeWidth));
}

Color tcolor = pen.color();
Expand All @@ -244,7 +247,7 @@ private void adjustPen(Pen pen){
if(!tfont.equals(font) || !tfontSize.equals(fontSize)){
font = tfont;
fontSize = tfontSize;
gr.setFont(new Font(font, Font.PLAIN, (int)(double)fontSize));
gr.setFont(new Font(font, Font.PLAIN, (int)fontSize));
}

FillPattern tfillPattern = pen.fillPattern();
Expand Down Expand Up @@ -328,26 +331,18 @@ public void saveAsJpeg(OutputStream os, double quality) throws IOException{
}

public void saveAsJpeg(String file, double quality) throws IOException{
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(file)));

try{
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
saveAsJpeg(bos, quality);
}finally{
bos.close();
}
}

public void saveAsPng(OutputStream os) throws IOException{
saveAsPng(img, os);
}

public void saveAsPng(String file) throws IOException{
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(file)));

try{
public void saveAsPng(String file) throws IOException{
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file))) {
saveAsPng(bos);
}finally{
bos.close();
}
}

Expand Down Expand Up @@ -375,7 +370,7 @@ public void paint(Canvas canvas, double x, double y, double width,
double height) {
Assert.notNull(canvas);

int data[] = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
int[] data = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
canvas.image(canvas.defaultPen(), x, y, width, height,
data, img.getWidth(), img.getHeight());
}
Expand All @@ -392,7 +387,7 @@ public void paint(Canvas canvas, Rect srcRect, Rect destRect) {
BufferedImage subImage = new BufferedImage(srcWidth, srcHeight, BufferedImage.TYPE_INT_ARGB);
subImage.getGraphics().drawImage(img.getSubimage(srcX, srcY, srcWidth, srcHeight), 0, 0, srcWidth, srcHeight, null);

int area[] = ((DataBufferInt)subImage.getRaster().getDataBuffer()).getData();
int[] area = ((DataBufferInt)subImage.getRaster().getDataBuffer()).getData();
canvas.image(canvas.defaultPen(), destRect.x(), destRect.y(), destRect.width(), destRect.height(), area, srcWidth, srcHeight);
}

Expand All @@ -412,7 +407,7 @@ public void triangle(Pen pen, double x1, double y1, double x2, double y2,
* @author urueda
*/
public float compareImage(AWTCanvas img) {
//long now = System.currentTimeMillis();
//long now = System.currentTimeMillis();
DataBuffer dbThis = this.img.getData().getDataBuffer(),
dbImg = img.img.getData().getDataBuffer();
int sizeThis = dbThis.getSize(),
Expand All @@ -435,10 +430,10 @@ else if (sizeThis < sizeImg)
float meanSize = (sizeThis + sizeImg) / 2;
float percent = sizeSimilarity - (1.0f - (equalPixels / meanSize));
//System.out.println("Image comparison took : " + (System.currentTimeMillis() - now) + " ms");
return (percent < 0f ? 0f : (percent > 1f ? 1f : percent));
return (percent < 0f ? 0f : Math.min(percent, 1f));
}

public void release() {}

public String toString(){ return "AWTCanvas (width: " + width() + " height: " + height() + ")"; }
}
2 changes: 2 additions & 0 deletions core/src/org/testar/serialisation/ScreenshotSerialiser.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public void run(){
r.scrshot.saveAsPng(r.scrshotPath);
} catch (IOException e) {
LogSerialiser.log("I/O exception saving screenshot <" + r.scrshotPath + ">\n", LogSerialiser.LogLevel.Critical);
} catch (NullPointerException e){
LogSerialiser.log("Screenshot was empty" + r.scrshotPath + ">\n", LogSerialiser.LogLevel.Critical);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions testar/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ dependencies {
implementation jnativehook
implementation 'com.google.guava:guava:26.0-jre'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.14.0'
implementation group: 'org.bytedeco', name: 'tesseract-platform', version: '4.1.1-1.5.4'
implementation group: 'org.bytedeco', name: 'javacv', version: '1.5.5'
implementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.4.30.v20200611'
implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.30.v20200611'
implementation group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '9.4.30.v20200611'
Expand Down
4 changes: 2 additions & 2 deletions testar/resources/log4j2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Logger>
<Logger name="nl.ou.testar" level="DEBUG" additivity="false">
<Logger name="org.testar" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Logger>
Expand All @@ -28,4 +28,4 @@
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
</Configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@
import java.util.Set;

import org.testar.ProtocolUtil;
import org.testar.monkey.alayer.Tags;
import org.testar.serialisation.ScreenshotSerialiser;
import org.testar.simplestategraph.GuiStateGraphWithVisitedActions;
import org.testar.monkey.Util;
import org.testar.monkey.alayer.AWTCanvas;
import org.testar.monkey.alayer.Action;
import org.testar.monkey.alayer.SUT;
import org.testar.monkey.alayer.State;
Expand Down Expand Up @@ -161,7 +164,10 @@ protected boolean executeAction(SUT system, State state, Action action){
//System.out.println("DEBUG: action: "+action.toString());
//System.out.println("DEBUG: action short: "+action.toShortString());
if(action.toShortString().equalsIgnoreCase("LeftClickAt")){
String widgetScreenshotPath = ProtocolUtil.getActionshot(state,action);
String widgetScreenshotPath = ScreenshotSerialiser.saveActionshot(
state.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
action.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
ProtocolUtil.getActionshot(state,action));
Eye eye = new Eye();
try {
//System.out.println("DEBUG: sikuli clicking ");
Expand All @@ -179,8 +185,10 @@ protected boolean executeAction(SUT system, State state, Action action){
}else if(action.toShortString().contains("ClickTypeInto(")){
String textToType = action.toShortString().substring(action.toShortString().indexOf("("), action.toShortString().indexOf(")"));
//System.out.println("parsed text:"+textToType);
String widgetScreenshotPath = ProtocolUtil.getActionshot(state,action);
Util.pause(halfWait);
String widgetScreenshotPath = ScreenshotSerialiser.saveActionshot(
state.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
action.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
ProtocolUtil.getActionshot(state,action)); Util.pause(halfWait);
Eye eye = new Eye();
try {
//System.out.println("DEBUG: sikuli typing ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import java.util.Set;

import org.testar.ProtocolUtil;
import org.testar.monkey.alayer.Tags;
import org.testar.serialisation.ScreenshotSerialiser;
import org.testar.simplestategraph.GuiStateGraphWithVisitedActions;
import org.testar.monkey.Util;
import org.testar.monkey.alayer.Action;
Expand Down Expand Up @@ -157,7 +159,10 @@ protected boolean executeAction(SUT system, State state, Action action){
//System.out.println("DEBUG: action: "+action.toString());
//System.out.println("DEBUG: action short: "+action.toShortString());
if(action.toShortString().equalsIgnoreCase("LeftClickAt")){
String widgetScreenshotPath = ProtocolUtil.getActionshot(state,action);
String widgetScreenshotPath = ScreenshotSerialiser.saveActionshot(
state.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
action.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
ProtocolUtil.getActionshot(state,action));
Screen sikuliScreen = new Screen();
try {
//System.out.println("DEBUG: sikuli clicking ");
Expand All @@ -174,7 +179,10 @@ protected boolean executeAction(SUT system, State state, Action action){
}else if(action.toShortString().contains("ClickTypeInto(")){
String textToType = action.toShortString().substring(action.toShortString().indexOf("("), action.toShortString().indexOf(")"));
//System.out.println("parsed text:"+textToType);
String widgetScreenshotPath = ProtocolUtil.getActionshot(state,action);
String widgetScreenshotPath = ScreenshotSerialiser.saveActionshot(
state.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
action.get(Tags.ConcreteIDCustom, "NoConcreteIdAvailable"),
ProtocolUtil.getActionshot(state,action));
Util.pause(halfWait);
Screen sikuliScreen = new Screen();
try {
Expand Down
17 changes: 17 additions & 0 deletions testar/src/org/testar/Logger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.testar;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;

/**
* Facade for the log4j logger.
* Reduces the need to initialize the logger in every class.
* By wrapping {@code log4j.logger.log} with a tag argument a more uniformed log is realized.
*/
public class Logger {
private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger();

public static void log(Level level, String tag, String message, Object... params) {
LOGGER.log(level, "[" + tag + "] " + message, params);
}
}
5 changes: 3 additions & 2 deletions testar/src/org/testar/OutputStructure.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,10 @@ public static void createOutputSUTname(Settings settings) {
executedSUTname = domain;
}
else if (sutConnectorValue.contains(".exe")) {
int startSUT = sutConnectorValue.lastIndexOf(File.separator)+1;
int endSUT = sutConnectorValue.indexOf(".exe");
String sutName = sutConnectorValue.substring(startSUT, endSUT);
String pathWithoutArguments = sutConnectorValue.substring(0, endSUT);
int startSUT = pathWithoutArguments.lastIndexOf(File.separator)+1;
String sutName = pathWithoutArguments.substring(startSUT, endSUT);
executedSUTname = sutName;
}
else if (sutConnectorValue.contains(".jar")) {
Expand Down
2 changes: 1 addition & 1 deletion testar/src/org/testar/extendedsettings/ExampleSetting.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static ExampleSetting CreateDefault() {

@Override
public int compareTo(ExampleSetting other) {
if (test.contentEquals(other.test)){
if (test.contentEquals(other.test)) {
return 0;
}
return -1;
Expand Down
Loading