Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) Add PixelMatcher to CaptureSupport. #248

Merged
merged 15 commits into from Feb 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion .travis.yml
Expand Up @@ -3,6 +3,12 @@ language: java
jdk:
- oraclejdk8

# install the newest java.
addons:
apt:
packages:
- oracle-java8-installer

# use operating systems.
os:
- linux
Expand Down Expand Up @@ -32,4 +38,4 @@ install: true

# run gradle build.
script:
- ./gradlew build
- ./gradlew build --info
8 changes: 8 additions & 0 deletions build.gradle
Expand Up @@ -110,6 +110,14 @@ subprojects { subproject ->
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}

// use test logging.
test {
testLogging {
events "failed"
exceptionFormat "short"
}
}
}

evaluationDependsOnChildren()
Expand Down
Expand Up @@ -16,8 +16,14 @@
*/
package org.testfx.api;

import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Set;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.stage.Screen;

import com.google.common.base.Predicate;
import org.hamcrest.Matcher;
Expand All @@ -36,6 +42,8 @@ public final class FxAssert {

private static final String EMPTY_STRING = "";

private static final String ERROR_TIMESTAMP_PATTERN = "yyyyMMdd.HHmmss.SSS";

//---------------------------------------------------------------------------------------------
// STATIC FIELDS.
//---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -137,7 +145,8 @@ private static <T> void verifyThatImpl(String reason,
MatcherAssert.assertThat(reason, value, matcher);
}
catch (AssertionError error) {
// TODO: Save screenshot to file.
// TODO: make error capture and assertion throw more reliable.
// captureErrorScreenshot();
throw new AssertionError(error.getMessage());
}
}
Expand Down Expand Up @@ -168,4 +177,26 @@ private static <T extends Node> Matcher<T> toNodeMatcher(Predicate<T> nodePredic
return GeneralMatchers.baseMatcher("applies on Predicate", nodePredicate);
}

private static void captureErrorScreenshot() {
ZonedDateTime errorDateTime = ZonedDateTime.now();

Rectangle2D primaryScreenRegion = Screen.getPrimary().getBounds();
Image errorImage = assertContext().getCaptureSupport().captureRegion(primaryScreenRegion);

String errorTimestamp = formatErrorTimestamp(errorDateTime, ERROR_TIMESTAMP_PATTERN);
String errorImageFilename = "testfx-" + errorTimestamp + ".png";

assertContext().getCaptureSupport().saveImage(errorImage, Paths.get(errorImageFilename));
}

private static String formatErrorTimestamp(ZonedDateTime dateTime,
String dateTimePattern) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimePattern);
return dateTime.format(formatter);
}

// private static ZonedDateTime toZuluTime(ZonedDateTime dateTime) {
// return dateTime.withZoneSameInstant(ZoneId.of(/* UTC */ "Z"));
// }

}
Expand Up @@ -18,6 +18,7 @@

import org.testfx.api.annotation.Unstable;
import org.testfx.service.finder.NodeFinder;
import org.testfx.service.support.CaptureSupport;

@Unstable(reason = "class was recently added")
public class FxAssertContext {
Expand All @@ -28,6 +29,8 @@ public class FxAssertContext {

private NodeFinder nodeFinder = FxService.serviceContext().getNodeFinder();

private CaptureSupport captureSupport = FxService.serviceContext().getCaptureSupport();

//---------------------------------------------------------------------------------------------
// GETTER AND SETTER.
//---------------------------------------------------------------------------------------------
Expand All @@ -40,4 +43,12 @@ public void setNodeFinder(NodeFinder nodeFinder) {
this.nodeFinder = nodeFinder;
}

public CaptureSupport getCaptureSupport() {
return captureSupport;
}

public void setCaptureSupport(CaptureSupport captureSupport) {
this.captureSupport = captureSupport;
}

}
27 changes: 27 additions & 0 deletions subprojects/testfx-core/src/main/java/org/testfx/api/FxRobot.java
Expand Up @@ -25,12 +25,15 @@
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.geometry.VerticalDirection;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.MouseButton;
import javafx.stage.Screen;
import javafx.stage.Window;

import com.google.common.base.Optional;
Expand Down Expand Up @@ -408,6 +411,30 @@ public Node rootNode(Node node) {
return context.getNodeFinder().rootNode(node);
}

//---------------------------------------------------------------------------------------------
// METHODS FOR SCREEN CAPTURING.
//---------------------------------------------------------------------------------------------

@Override
@Unstable(reason = "likely to be refactored with builder pattern")
public Image capture(Screen screen) {
return context.getCaptureSupport().captureRegion(screen.getBounds());
}

@Override
@Unstable(reason = "likely to be refactored with builder pattern")
public Image capture(Bounds bounds) {
Rectangle2D region = new Rectangle2D(bounds.getMinX(), bounds.getMinX(),
bounds.getWidth(), bounds.getHeight());
return context.getCaptureSupport().captureRegion(region);
}

@Override
@Unstable(reason = "likely to be refactored with builder pattern")
public Image capture(Node node) {
return context.getCaptureSupport().captureNode(node);
}

//---------------------------------------------------------------------------------------------
// METHODS FOR INTERACTION AND INTERRUPTION.
//---------------------------------------------------------------------------------------------
Expand Down
Expand Up @@ -45,6 +45,8 @@
import org.testfx.service.locator.PointLocator;
import org.testfx.service.locator.impl.BoundsLocatorImpl;
import org.testfx.service.locator.impl.PointLocatorImpl;
import org.testfx.service.support.CaptureSupport;
import org.testfx.service.support.impl.CaptureSupportImpl;

@Unstable(reason = "class was recently added")
public class FxRobotContext {
Expand Down Expand Up @@ -72,6 +74,8 @@ public class FxRobotContext {
private TypeRobot typeRobot;
private WriteRobot writeRobot;

private CaptureSupport captureSupport;

//---------------------------------------------------------------------------------------------
// CONSTRUCTORS.
//---------------------------------------------------------------------------------------------
Expand All @@ -94,6 +98,8 @@ public FxRobotContext() {
clickRobot = new ClickRobotImpl(mouseRobot, moveRobot, sleepRobot);
dragRobot = new DragRobotImpl(mouseRobot, moveRobot);
scrollRobot = new ScrollRobotImpl(mouseRobot);

captureSupport = new CaptureSupportImpl(baseRobot);
}

//---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -220,4 +226,12 @@ public void setWriteRobot(WriteRobot writeRobot) {
this.writeRobot = writeRobot;
}

public CaptureSupport getCaptureSupport() {
return captureSupport;
}

public void setCaptureSupport(CaptureSupport captureSupport) {
this.captureSupport = captureSupport;
}

}
Expand Up @@ -27,9 +27,11 @@
import javafx.geometry.VerticalDirection;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.MouseButton;
import javafx.stage.Screen;
import javafx.stage.Window;

import com.google.common.base.Predicate;
Expand Down Expand Up @@ -144,6 +146,14 @@ public <T extends Node> PointQuery offset(Predicate<T> predicate,
public <T extends Node> NodeQuery lookup(Predicate<T> predicate);
public NodeQuery from(NodeQuery nodeQuery);

//---------------------------------------------------------------------------------------------
// METHODS FOR SCREEN CAPTURING.
//---------------------------------------------------------------------------------------------

public Image capture(Screen screen);
public Image capture(Bounds bounds);
public Image capture(Node node);

//---------------------------------------------------------------------------------------------
// METHODS FOR INTERACTION AND INTERRUPTION.
//---------------------------------------------------------------------------------------------
Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.testfx.service.finder.impl.NodeFinderImpl;
import org.testfx.service.finder.impl.WindowFinderImpl;
import org.testfx.service.support.CaptureSupport;
import org.testfx.service.support.impl.CaptureSupportImpl;
import org.testfx.service.support.WaitUntilSupport;

@Unstable(reason = "class was recently added")
Expand All @@ -39,7 +40,7 @@ public class FxServiceContext {

private BaseRobot baseRobot = new BaseRobotImpl();

private CaptureSupport captureSupport = new CaptureSupport(baseRobot);
private CaptureSupport captureSupport = new CaptureSupportImpl(baseRobot);

private WaitUntilSupport waitUntilSupport = new WaitUntilSupport();

Expand Down
Expand Up @@ -16,70 +16,32 @@
*/
package org.testfx.service.support;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javafx.embed.swing.SwingFXUtils;
import java.nio.file.Path;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.stage.Screen;
import javafx.scene.shape.Shape;

import org.testfx.api.annotation.Unstable;
import org.testfx.robot.BaseRobot;

@Unstable(reason = "needs more tests")
public class CaptureSupport {

//---------------------------------------------------------------------------------------------
// PRIVATE FIELDS.
//---------------------------------------------------------------------------------------------

private BaseRobot baseRobot;

//---------------------------------------------------------------------------------------------
// CONSTRUCTORS.
//---------------------------------------------------------------------------------------------

public CaptureSupport(BaseRobot baseRobot) {
this.baseRobot = baseRobot;
}
public interface CaptureSupport {

//---------------------------------------------------------------------------------------------
// METHODS.
//---------------------------------------------------------------------------------------------

public void captureScreenToFile(Screen screen, File captureFile) {
Image captureImage = captureScreenToImage(screen);
writeCaptureImageToFile(captureImage, captureFile);
}
Image captureNode(Node node);

public void capturePrimaryScreenToFile(File captureFile) {
captureScreenToFile(Screen.getPrimary(), captureFile);
}
Image captureRegion(Rectangle2D region);

//---------------------------------------------------------------------------------------------
// PRIVATE METHODS.
//---------------------------------------------------------------------------------------------
Image loadImage(Path path);

private Image captureScreenToImage(Screen screen) {
Rectangle2D region = screen.getBounds();
return baseRobot.captureRegion(region);
}
void saveImage(Image image,
Path path);

private Image captureNodeToImage(Node node) {
return node.snapshot(null, null);
}
Image annotateImage(Shape shape,
Image image);

private void writeCaptureImageToFile(Image captureImage, File captureFile) {
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(captureImage, null);
try {
ImageIO.write(bufferedImage, "png", captureFile);
}
catch (IOException exception) {
exception.printStackTrace();
}
}
PixelMatcherResult matchImages(Image image0,
Image image1,
PixelMatcher pixelMatcher);

}
@@ -0,0 +1,40 @@
/*
* Copyright 2013-2014 SmartBear Software
* Copyright 2014-2015 The TestFX Contributors
*
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
* European Commission - subsequent versions of the EUPL (the "Licence"); You may
* not use this work except in compliance with the Licence.
*
* You may obtain a copy of the Licence at:
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
* specific language governing permissions and limitations under the Licence.
*/
package org.testfx.service.support;

import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;

public interface PixelMatcher {

PixelMatcherResult match(Image image0,
Image image1);

boolean matchColors(Color color0,
Color color1);

WritableImage createEmptyMatchImage(Image image0,
Image image1);

Color createMatchColor(Color color0,
Color color1);

Color createNonMatchColor(Color color0,
Color color1);

}