# HG changeset patch # User shurailine # Date 1513808189 28800 # Wed Dec 20 14:16:29 2017 -0800 # Node ID 24c167fc21401dffc9f18475d1756f54ea62366c # Parent 369b193287fc52f154d3c43a88dfc67e4e64be58 7902086: Add JemmyAWTInput to jemmy/v3 diff --git a/core/JemmyAWTInput/.DS_Store b/core/JemmyAWTInput/.DS_Store new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81229e8d26382bd3469cb3d9bb2de3f1954844ac GIT binary patch literal 6148 zc%1E+K?=e!5JmsAT@>78=`tG+;0;326L<6%lp- diff --git a/core/JemmyAWTInput/build.xml b/core/JemmyAWTInput/build.xml new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/build.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${testlist} + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/JemmyAWTInput/src/org/jemmy/image/AWTImage.java b/core/JemmyAWTInput/src/org/jemmy/image/AWTImage.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/AWTImage.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import org.jemmy.Dimension; +import org.jemmy.JemmyException; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.image.pixel.*; + + +/** + * + * @author shura + */ +public class AWTImage implements Image, WriteableRaster { + + /** + * + */ + public static final String OUTPUT = AWTImage.class.getName() + ".OUTPUT"; + public static final String PNG_FILE = ".png"; + + /** + * Get the value of imageRoot. The field is used to store images by relative + * path within the root. Images should be pointed out by full file names if + * the value is null. + * + * @return the value of imageRoot + */ + public static File getImageRoot() { + ImageStore res = Environment.getEnvironment().getProperty(ImageStore.class); + if(!(res instanceof PNGFileImageStore)) { + throw new IllegalStateException("Unsupported ImageStore: " + res.getClass().getName()); + } + return ((PNGFileImageStore)res).getRoot(); + } + + /** + * Set the value of imageRoot. If null, an image ID should be full path. + * + * @param imageRoot new value of imageRoot + */ + public static void setImageRoot(File imageRoot) { + Environment.getEnvironment().setProperty(ImageStore.class, new PNGFileImageStore(imageRoot)); + } + + /** + * Gets comparator to be used by default. + * + * @see Wrap#waitImage(org.jemmy.image.Image, java.lang.String, + * java.lang.String) + * @see ImageComparator + * @return default comparator + */ + public static ImageComparator getComparator() { + return Environment.getEnvironment().getProperty(ImageComparator.class); + } + + /** + * Sets comparator to be used by default. + * + * @see Wrap#waitImage(org.jemmy.image.Image, java.lang.String, + * java.lang.String) + * @see ImageComparator + * @param comparator + */ + public static void setComparator(ImageComparator comparator) { + Environment.getEnvironment().setProperty(ImageComparator.class, comparator); + } + + static { + Environment.getEnvironment().setPropertyIfNotSet(ImageComparator.class, + new BufferedImageComparator(Environment.getEnvironment())); + Environment.getEnvironment().setPropertyIfNotSet(ImageStore.class, new PNGFileImageStore()); + } + + private BufferedImage image; + + /** + * Creates an instance + * + * @param img + */ + public AWTImage(BufferedImage img) { + this.image = img; + } + + public BufferedImage getTheImage() { + return image; + } + + /** + * Compares using current comparator. + * + * @see AWTImage#getComparator() + * @param img + * @return diff image. + */ + public Image compareTo(Image img) { + return getComparator().compare(this, img); + } + + /** + * Saves to a filesystem. fileName is expected to be a full path, unless + * imageRoot is specified. ".png" extension is added automatically if not + * specified. + * + * @see AWTImage#getImageRoot() + * @param fileName full or relative file name + */ + public void save(String fileName) { + try { + String fullPath = fileName; + File imageRoot = getImageRoot(); + if (imageRoot != null) { + fullPath = imageRoot.getAbsolutePath() + File.separator + fileName; + } + if (!fullPath.toLowerCase().endsWith(PNG_FILE)) { + fullPath += PNG_FILE; + } + new PNGImageSaver().save(image, fullPath); + Environment.getEnvironment().getOutput(OUTPUT).println("Image saved to " + fullPath); + } catch (IOException ex) { + throw new JemmyException("Unable to save image", ex, fileName); + } + } + + public Dimension getSize() { + return new Dimension(image.getWidth(), image.getHeight()); + } + + public void getColors(int x, int y, double[] colors) { + int orig = image.getRGB(x, y); + int ivalue; + for (Raster.Component c : getSupported()) { + switch (c) { + case ALPHA: + ivalue = (orig & 0xFF000000) >>> 0x18; + break; + case RED: + ivalue = (orig & 0xFF0000) >>> 0x10; + break; + case GREEN: + ivalue = (orig & 0xFF00) >>> 0x8; + break; + case BLUE: + ivalue = (orig & 0xFF); + break; + default: + throw new IllegalArgumentException("Unknown color component" + c); + } + colors[PixelImageComparator.arrayIndexOf(getSupported(), c)] = (double) ivalue / 0xFF; + } + } + + public void setColors(int x, int y, double[] colors) { + int rgb = 0; + double value; + int ivalue; + for (Raster.Component c : getSupported()) { + value = colors[PixelImageComparator.arrayIndexOf(getSupported(), c)]; + if (value < 0 || value > 1) { + throw new IllegalArgumentException("Color component value should be within (0, 1). Gotten: " + value); + } + ivalue = (int) Math.round(value * 0xFF); + if (ivalue > 0xFF) { + throw new IllegalStateException("Component value is greater than 0xFF: " + ivalue); + } + switch (c) { + case ALPHA: + rgb |= (ivalue << 0x18); + break; + case RED: + rgb |= (ivalue << 0x10); + break; + case GREEN: + rgb |= (ivalue << 0x8); + break; + case BLUE: + rgb |= ivalue; + break; + default: + throw new IllegalArgumentException("Unknown color component: " + c); + } + } + getTheImage().setRGB(x, y, rgb); + } + + public Component[] getSupported() { + return new Component[]{Component.RED, Component.BLUE, Component.GREEN, Component.ALPHA}; + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/AWTRobotCapturer.java b/core/JemmyAWTInput/src/org/jemmy/image/AWTRobotCapturer.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/AWTRobotCapturer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import org.jemmy.Rectangle; +import org.jemmy.control.Wrap; +import org.jemmy.input.RobotDriver; + + +/** + * Uses java.awt.Robot to capture the images + * @author mrkam, shura + */ +public class AWTRobotCapturer implements ImageCapturer { + static { + try { + Class.forName(AWTImage.class.getName()); + } catch (ClassNotFoundException ex) { + } + } + + public Image capture(Wrap control, Rectangle area) { + Rectangle rect = new Rectangle(); + Rectangle bounds = control.getScreenBounds(); + rect.x = bounds.x + area.x; + rect.y = bounds.y + area.y; + rect.width = area.width; + rect.height = area.height; + return RobotDriver.createScreenCapture(rect); + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/AbstractImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/AbstractImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/AbstractImageComparator.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image; + + +import java.awt.image.BufferedImage; + + +/** + * Common part of most ImageComparators. + * + * @author KAM + * @deprecated Use classes from org.jemmy.image.pixel package instead. + */ +@Deprecated +public abstract class AbstractImageComparator implements ImageComparator { + + /** + * Checks whether images have difference. + * @param image1 First image to compare. + * @param image2 Second image to compare. + * @return true if images have no difference, false otherwise. + */ + public abstract boolean noDifference(BufferedImage image1, BufferedImage image2); + + /** + * {@inheritDoc} + */ + public BufferedImage compare(BufferedImage image1, BufferedImage image2) { + if (noDifference(image1, image2)) { + return null; + } else { + return ImageTool.subtractImage(image1, image2); + } + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/AverageDistanceImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/AverageDistanceImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/AverageDistanceImageComparator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.image; + + +import java.awt.image.BufferedImage; +import org.jemmy.image.pixel.AverageDistanceComparator; + + +/** + * Compares two images calculating average color distance between pixels and + * comparing it to the threshold value. See {@linkplain NaturalImageComparator + * NaturalImageComparator} for color comparison details. + * + * @author KAM + */ +public class AverageDistanceImageComparator extends BufferedImageComparator { + + /** + * Creates comparator with the default sensitivity value = 0.02 + * (around 5 in 0-255 color component value). + * @see #NaturalImageComparator(double) + */ + public AverageDistanceImageComparator() { + this(0.02); + } + + /** + * Creates comparator with the specified sensitivity value + * @param sensitivity Maximum threshold for average 3-D distance between + * colors in 3-D sRGB color space for images to be considered equal. + * Meaningful values lay between 0 and approx 1.733. 0 means colors should + * be equal to pass the comparison, 1.733 (which is more than square root + * of 3) means that comparison will be passed even if all the colors are + * completely different. + */ + public AverageDistanceImageComparator(double sensitivity) { + super(new AverageDistanceComparator(sensitivity)); + } + + public void setSensitivity(double sensitivity) { + ((AverageDistanceComparator)getRasterComparator()).setThreshold(sensitivity); + } + public double getSensitivity() { + return ((AverageDistanceComparator)getRasterComparator()).getThreshold(); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/BufferedImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/BufferedImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/BufferedImageComparator.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.image.BufferedImage; +import org.jemmy.env.Environment; +import org.jemmy.image.pixel.PixelImageComparator; +import org.jemmy.image.pixel.Raster; +import org.jemmy.image.pixel.RasterComparator; +import org.jemmy.image.pixel.WriteableRaster; + +/** + * + * @author shura + */ +public class BufferedImageComparator extends PixelImageComparator { + + public BufferedImageComparator(RasterComparator comparator) { + super(comparator); + } + + public BufferedImageComparator(Environment env) { + super(env); + } + + @Override + protected Image toImage(Raster image) { + if(image instanceof AWTImage) { + return (AWTImage)image; + } else { + throw new IllegalArgumentException("Unrecognized image type" + image.getClass().getName()); + } + } + + @Override + protected Raster toRaster(Image image) { + if(image instanceof AWTImage) { + return (AWTImage)image; + } else { + throw new IllegalArgumentException("Unrecognized image type" + image.getClass().getName()); + } + } + + @Override + protected WriteableRaster createDiffRaster(Raster r1, Raster r2) { + AWTImage img2 = (AWTImage) r2; + AWTImage img1 = (AWTImage) r1; + return new AWTImage(new BufferedImage( + Math.max(img1.getSize().width, img2.getSize().width), + Math.max(img1.getSize().height, img2.getSize().height), + img1.getTheImage().getType())); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/ClasspathImageLoader.java b/core/JemmyAWTInput/src/org/jemmy/image/ClasspathImageLoader.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/ClasspathImageLoader.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import org.jemmy.env.Environment; + + +/** + * ImageLoader implementation which is able to load images through + * a given classloader. + * @author mrkam, shura + */ +public class ClasspathImageLoader implements ImageLoader { + + private String packagePrefix = ""; + private ClassLoader classLoader = getClassLoader(); + + public static final String OUTPUT = AWTImage.class.getName() + ".OUTPUT"; + + /** + * Get the value of classLoader which is used to load images. + * + * @return the value of classLoader + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * {@inheritDoc} + */ + public Image load(String ID) { + String fullId = ((packagePrefix != null) ? packagePrefix : "") + ID; + Environment.getEnvironment().getOutput(ClasspathImageLoader.OUTPUT).println("Image loaded from " + fullId + " by " + classLoader); + return new AWTImage(PNGDecoder.decode(classLoader, fullId)); + } + + /** + * Set the value of classLoader + * + * @param classLoader new value of classLoader + */ + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * TODO: Add JavaDoc + * @param rootPackage + */ + public void setRootPackage(Package rootPackage) { + if (rootPackage != null) { + this.packagePrefix = rootPackage.getName().replace('.', '/') + "/"; + } else { + this.packagePrefix = null; + } + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/ColorImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/ColorImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/ColorImageComparator.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.image.BufferedImage; +import org.jemmy.image.pixel.PixelEqualityRasterComparator; + +/** + * Compares two images with color mapping defined by + * ColorModel implementation. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + * @deprecated Use classes form org.jemmy.image.pixel instead. + */ +@Deprecated +public class ColorImageComparator implements ImageComparator { + + ColorMap leftMap, rightMap; + ImageComparator comparator = null; + + /** + * Creates a comparator with a color maps. Object created by this + * constructor behaves like + * StrictImageComparator. Object created works faster because + * it does not create intermediate images for another comparator. + * + * @param map Map applied to both left and right images during comparision. + */ + public ColorImageComparator(ColorMap map) { + this(map, new StrictImageComparator()); + } + + /** + * Creates a comparator with + * map color mapping. Actual comparision perfomed by + * comparator parameter. + * + * @param map Map applied to both left and right images during comparision. + * @param subComparator comporator to perform a comparision of to images + * with mapped colors. + */ + public ColorImageComparator(ColorMap map, ImageComparator subComparator) { + this(map, map, subComparator); + } + + /** + * Creates a comparator with two color maps. Object created by this + * constructor behaves like + * StrictImageComparator. Object created works faster because + * it does not create intermediate images for another comparator. + * + * @param leftMap Map applied to the left image during comparision. + * @param rightMap Map applied to the right image during comparision. + */ + public ColorImageComparator(ColorMap leftMap, ColorMap rightMap) { + this(leftMap, rightMap, new BufferedImageComparator(new PixelEqualityRasterComparator(0))); + } + + /** + * Creates a comparator with two color maps. Actual comparision perfomed by + * comparator parameter. + * + * @param leftMap Map applied to the left image during comparision. + * @param rightMap Map applied to the right image during comparision. + * @param subComparator comporator to perform a comparision of to images + * with mapped colors. + */ + public ColorImageComparator(ColorMap leftMap, ColorMap rightMap, ImageComparator subComparator) { + this.leftMap = leftMap; + this.rightMap = rightMap; + this.comparator = subComparator; + } + + /** + * Compares images by + * ImageComparator passed into constructor, or itself if no + * ImageComparator was passed, processing both images by + * ColorMap instance before comparision. + */ + @Override + public Image compare(Image image1, Image image2) { + return (comparator.compare( + recolor((AWTImage)image1, leftMap), + recolor((AWTImage)image2, rightMap))); + } + + private AWTImage recolor(AWTImage isrc, ColorMap map) { + BufferedImage src = isrc.getTheImage(); + BufferedImage result = new BufferedImage(src.getWidth(), src.getHeight(), src.getType()); + for (int x = 0; x < src.getWidth(); x++) { + for (int y = 0; y < src.getWidth(); y++) { + result.setRGB(x, y, map.mapColor(src.getRGB(x, y))); + } + } + return new AWTImage(result); + } + + protected final boolean compareColors(int rgb1, int rgb2) { + return (leftMap.mapColor(rgb1) == rightMap.mapColor(rgb2)); + } + + public String getID() { + return ColorImageComparator.class.getName(); + } + + /** + * Interface to map colors during the comparision. + */ + public static interface ColorMap { + + /** + * Maps one color into another. + * + * @param rgb an original color. + * @return a converted color. + */ + public int mapColor(int rgb); + } + + /** + * Turns + * foreground color to white, other - to black. + */ + public static class ForegroundColorMap implements ColorMap { + + int foreground; + + /** + * Constructs a ColorImageComparator$ForegroundColorMap object. + * + * @param foreground Foreground color. + */ + public ForegroundColorMap(int foreground) { + this.foreground = foreground; + } + + public int mapColor(int rgb) { + return ((rgb == foreground) ? 0xffffff : 0); + } + } + + /** + * Turns + * background color to black, left others unchanged. + */ + public static class BackgroundColorMap implements ColorMap { + + int background; + + /** + * Constructs a ColorImageComparator$BackgroundColorMap object. + * + * @param background Background color. + */ + public BackgroundColorMap(int background) { + this.background = background; + } + + public int mapColor(int rgb) { + return ((rgb == background) ? 0 : rgb); + } + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/DiffDialog.form b/core/JemmyAWTInput/src/org/jemmy/image/DiffDialog.form new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/DiffDialog.form @@ -0,0 +1,257 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/core/JemmyAWTInput/src/org/jemmy/image/DiffDialog.java b/core/JemmyAWTInput/src/org/jemmy/image/DiffDialog.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/DiffDialog.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Toolkit; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.image.BufferedImage; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * + * @author shura + */ +public class DiffDialog extends javax.swing.JDialog { + + private final static StrictImageComparator comparator = new StrictImageComparator(); + private double scale = 1.0; + private int imageWidth, imageHeight, scaledWidth, scaledHeight; + private ImagePane left = null, right = null, diff = null; + int status = 0; + + /** Creates new form ImageDiff */ + DiffDialog() { + super((JDialog)null, true); + initComponents(); + leftPane.setLayout(new BorderLayout()); + leftPane.add(new JLabel("Golden"), BorderLayout.NORTH); + rightPane.setLayout(new BorderLayout()); + rightPane.add(new JLabel("Result"), BorderLayout.NORTH); + diffPane.setLayout(new BorderLayout()); + diffPane.add(new JLabel("Diff"), BorderLayout.NORTH); + getContentPane().addComponentListener(new ComponentListener() { + + public void componentResized(ComponentEvent e) { + lrSplit.setDividerLocation(.5); + dcSplit.setDividerLocation(.5); + tbSplit.setDividerLocation(.5); + } + + public void componentMoved(ComponentEvent e) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void componentShown(ComponentEvent e) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void componentHidden(ComponentEvent e) { + throw new UnsupportedOperationException("Not supported yet."); + } + }); + setSize(400, 300); + + // Get the size of the screen + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + + // Determine the new location of the window + int w = getSize().width; + int h = getSize().height; + int x = (dim.width - w) / 2; + int y = (dim.height - h) / 2; + + // Move the window + setLocation(x, y); + } + + void setImages(BufferedImage leftImage, BufferedImage rightImage) { + if (leftImage != null && rightImage != null) { + copyBtn.setEnabled(true); + removeBtn.setEnabled(false); + imageWidth = leftImage.getWidth(); + imageHeight = leftImage.getHeight(); + } else { + if (leftImage == null) { + copyBtn.setEnabled(true); + removeBtn.setEnabled(false); + imageWidth = rightImage.getWidth(); + imageHeight = rightImage.getHeight(); + } else if (rightImage == null) { + copyBtn.setEnabled(false); + removeBtn.setEnabled(true); + imageWidth = leftImage.getWidth(); + imageHeight = leftImage.getHeight(); + } + } + if (left == null) { + left = new ImagePane(leftImage); + } else { + left.setImage(leftImage); + } + leftPane.add(left, BorderLayout.CENTER); + if (right == null) { + right = new ImagePane(rightImage); + } else { + right.setImage(rightImage); + } + rightPane.add(right, BorderLayout.CENTER); + if (diff == null) { + diff = new ImagePane(subtract(leftImage, rightImage)); + } else { + diff.setImage(subtract(leftImage, rightImage)); + } + diffPane.add(diff, BorderLayout.CENTER); + rescaleAll(); + } + + private BufferedImage subtract(BufferedImage left, BufferedImage right) { + if(left != null && right != null) { + return ImageTool.subtractImage(left, right); + } else { + return null; + } + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + tbSplit = new javax.swing.JSplitPane(); + lrSplit = new javax.swing.JSplitPane(); + leftPane = new javax.swing.JPanel(); + rightPane = new javax.swing.JPanel(); + dcSplit = new javax.swing.JSplitPane(); + diffPane = new javax.swing.JPanel(); + controlPane = new javax.swing.JPanel(); + jButton1 = new javax.swing.JButton(); + jButton2 = new javax.swing.JButton(); + copyBtn = new javax.swing.JButton(); + removeBtn = new javax.swing.JButton(); + jButton6 = new javax.swing.JButton(); + jButton7 = new javax.swing.JButton(); + + tbSplit.setDividerLocation(200); + tbSplit.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + + lrSplit.setDividerLocation(250); + + javax.swing.GroupLayout leftPaneLayout = new javax.swing.GroupLayout(leftPane); + leftPane.setLayout(leftPaneLayout); + leftPaneLayout.setHorizontalGroup( + leftPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 250, Short.MAX_VALUE) + ); + leftPaneLayout.setVerticalGroup( + leftPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 200, Short.MAX_VALUE) + ); + + lrSplit.setLeftComponent(leftPane); + + javax.swing.GroupLayout rightPaneLayout = new javax.swing.GroupLayout(rightPane); + rightPane.setLayout(rightPaneLayout); + rightPaneLayout.setHorizontalGroup( + rightPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 293, Short.MAX_VALUE) + ); + rightPaneLayout.setVerticalGroup( + rightPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 200, Short.MAX_VALUE) + ); + + lrSplit.setRightComponent(rightPane); + + tbSplit.setTopComponent(lrSplit); + + javax.swing.GroupLayout diffPaneLayout = new javax.swing.GroupLayout(diffPane); + diffPane.setLayout(diffPaneLayout); + diffPaneLayout.setHorizontalGroup( + diffPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 100, Short.MAX_VALUE) + ); + diffPaneLayout.setVerticalGroup( + diffPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 255, Short.MAX_VALUE) + ); + + dcSplit.setLeftComponent(diffPane); + + jButton1.setMnemonic('+'); + jButton1.setText("+"); + jButton1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(evt); + } + }); + + jButton2.setMnemonic('-'); + jButton2.setText("-"); + jButton2.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton2ActionPerformed(evt); + } + }); + + copyBtn.setText("Copy to golgen"); + copyBtn.setEnabled(false); + copyBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + copyBtnActionPerformed(evt); + } + }); + + removeBtn.setText("Remove from golden"); + removeBtn.setEnabled(false); + removeBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + removeBtnActionPerformed(evt); + } + }); + + jButton6.setText("Next"); + jButton6.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton6ActionPerformed(evt); + } + }); + + jButton7.setText("Exit"); + jButton7.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton7ActionPerformed(evt); + } + }); + + javax.swing.GroupLayout controlPaneLayout = new javax.swing.GroupLayout(controlPane); + controlPane.setLayout(controlPaneLayout); + controlPaneLayout.setHorizontalGroup( + controlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPaneLayout.createSequentialGroup() + .addContainerGap(289, Short.MAX_VALUE) + .addGroup(controlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPaneLayout.createSequentialGroup() + .addComponent(jButton6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButton7)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPaneLayout.createSequentialGroup() + .addComponent(jButton1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButton2)) + .addComponent(copyBtn, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(removeBtn, javax.swing.GroupLayout.Alignment.TRAILING)) + .addContainerGap()) + ); + controlPaneLayout.setVerticalGroup( + controlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPaneLayout.createSequentialGroup() + .addContainerGap() + .addGroup(controlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButton1) + .addComponent(jButton2)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(copyBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(removeBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 107, Short.MAX_VALUE) + .addGroup(controlPaneLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButton7) + .addComponent(jButton6)) + .addContainerGap()) + ); + + dcSplit.setRightComponent(controlPane); + + tbSplit.setRightComponent(dcSplit); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tbSplit, javax.swing.GroupLayout.DEFAULT_SIZE, 549, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tbSplit, javax.swing.GroupLayout.DEFAULT_SIZE, 461, Short.MAX_VALUE) + ); + + pack(); + }// //GEN-END:initComponents + + private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed + scale *= .9; + rescaleAll(); + }//GEN-LAST:event_jButton2ActionPerformed + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + scale *= 1.1; + rescaleAll(); + }//GEN-LAST:event_jButton1ActionPerformed + + private void rescaleAll() { + scaledWidth = (int) (imageWidth * scale); + scaledHeight = (int) (imageHeight * scale); + left.reScale(); + right.reScale(); + diff.reScale(); + getContentPane().repaint(); + } + + private void copyBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyBtnActionPerformed + status = -1; + setVisible(false); + }//GEN-LAST:event_copyBtnActionPerformed + + private void removeBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeBtnActionPerformed + status = 1; + setVisible(false); + }//GEN-LAST:event_removeBtnActionPerformed + + private void jButton6ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton6ActionPerformed + setVisible(false); + }//GEN-LAST:event_jButton6ActionPerformed + + private void jButton7ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton7ActionPerformed + status = -2; + setVisible(false); + }//GEN-LAST:event_jButton7ActionPerformed + + private class ImagePane extends JPanel { + + BufferedImage img; + java.awt.Image scaled; + + public ImagePane(BufferedImage img) { + this.img = img; + } + + @Override + protected void paintComponent(Graphics g) { + if (img != null) { + g.drawImage(scaled, 0, 0, this); + } else { + super.paintComponent(g); + } + } + + void setImage(BufferedImage img) { + this.img = img; + reScale(); + } + + void reScale() { + if (img != null) { + scaled = img.getScaledInstance(scaledWidth, scaledHeight, java.awt.Image.SCALE_DEFAULT); + } + } + } + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel controlPane; + private javax.swing.JButton copyBtn; + private javax.swing.JSplitPane dcSplit; + private javax.swing.JPanel diffPane; + private javax.swing.JButton jButton1; + private javax.swing.JButton jButton2; + private javax.swing.JButton jButton6; + private javax.swing.JButton jButton7; + private javax.swing.JPanel leftPane; + private javax.swing.JSplitPane lrSplit; + private javax.swing.JButton removeBtn; + private javax.swing.JPanel rightPane; + private javax.swing.JSplitPane tbSplit; + // End of variables declaration//GEN-END:variables +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/FilesystemImageLoader.java b/core/JemmyAWTInput/src/org/jemmy/image/FilesystemImageLoader.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/FilesystemImageLoader.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import java.io.File; +import org.jemmy.env.Environment; + + +/** + * This is an implementation of ImageLoader which loads images from filesystem. + * @author mrkam + */ +public class FilesystemImageLoader implements ImageLoader { + + private File imageRoot = null; + + public static final String OUTPUT = AWTImage.class.getName() + ".OUTPUT"; + + public File getImageRoot() { + return imageRoot; + } + + public Image load(String ID) { + String fullPath = ID + (ID.toLowerCase().endsWith(AWTImage.PNG_FILE) ? "" : + AWTImage.PNG_FILE); + if (imageRoot != null) { + fullPath = imageRoot.getAbsolutePath() + File.separator + ID; + } + Environment.getEnvironment().getOutput(FilesystemImageLoader.OUTPUT).println("Image loaded from " + fullPath); + return new AWTImage(PNGDecoder.decode(fullPath)); + } + + public void setImageRoot(File imageRoot) { + this.imageRoot = imageRoot; + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/ImageFinder.java b/core/JemmyAWTInput/src/org/jemmy/image/ImageFinder.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/ImageFinder.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import org.jemmy.Point; + +import java.awt.image.BufferedImage; + +/** + * Interface for all classes performing image lookup. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public interface ImageFinder { + + /** + * Should return location if image lays inside an image represented by this object. + * @param image an image to search. + * @param index an ordinal image location index. If equal to 1, for example, + * second appropriate location will be found. + * @return Image location coordinates if image was found, null otherwise. + */ + public Point findImage(BufferedImage image, int index); +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/ImageTool.java b/core/JemmyAWTInput/src/org/jemmy/image/ImageTool.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/ImageTool.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import javax.imageio.ImageIO; + + +/** + * Contains util methods to work with images. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +class ImageTool { + + /** + * Increases image. + * @param image an image to enlarge. + * @param zoom A scale. + * @return a result image. + */ + public static BufferedImage enlargeImage(BufferedImage image, int zoom) { + int wight = image.getWidth(); + int height = image.getHeight(); + BufferedImage result = new BufferedImage(wight * zoom, + height * zoom, + image.getType()); + int rgb; + for (int x = 0; x < wight; x++) { + for (int y = 0; y < height; y++) { + rgb = image.getRGB(x, y); + for (int i = 0; i < zoom; i++) { + for (int j = 0; j < zoom; j++) { + result.setRGB(x * zoom + i, + y * zoom + j, + rgb); + } + } + } + } + return (result); + } + + /** + * Subtracts second image from first one. + * Could be used to save file difference for future analysis. + * @param minuend an image to subtract from. + * @param deduction an image to subtract. + * @return a result image. + */ + public static BufferedImage subtractImage(BufferedImage minuend, BufferedImage deduction) { + return (subtractImage(minuend, deduction, 0, 0, null)); + } + + /** + * Subtracts second image from first one. + * Could be used to save file difference for future analysis. + * @param minuend an image to subtract from. + * @param deduction an image to subtract. + * @param highlight - a color to highlight the difference. If null, + * color difference is shown. + * @return a result image. + */ + public static BufferedImage subtractImage(BufferedImage minuend, BufferedImage deduction, Color highlight) { + return (subtractImage(minuend, deduction, 0, 0, highlight)); + } + + /** + * Subtracts subimage from image. + * Could be used to save file difference for future analysis. + * @param minuend an image to subtract from. + * @param deduction an image to subtract. + * @param relativeX - deduction-in-minuend X coordinate + * @param relativeY - deduction-in-minuend Y coordinate + * @return a result image. + */ + public static BufferedImage subtractImage(BufferedImage minuend, BufferedImage deduction, int relativeX, int relativeY) { + return subtractImage(minuend, deduction, relativeX, relativeY, null); + } + + /** + * Subtracts subimage from image. + * Could be used to save file difference for future analysis. + * @param minuend an image to subtract from. + * @param deduction an image to subtract. + * @param relativeX - deduction-in-minuend X coordinate + * @param relativeY - deduction-in-minuend Y coordinate + * @param highlight - a color to highlight the difference. If null, + * color difference is shown. + * @return a result image. + */ + public static BufferedImage subtractImage(BufferedImage minuend, BufferedImage deduction, int relativeX, int relativeY, Color highlight) { + int mWidth = minuend.getWidth(); + int mHeight = minuend.getHeight(); + int dWidth = deduction.getWidth(); + int dHeight = deduction.getHeight(); + + int maxWidth = (mWidth > relativeX + dWidth) ? mWidth : (relativeX + dWidth); + int maxHeight = (mHeight > relativeY + dHeight) ? mHeight : (relativeY + dHeight); + + BufferedImage result = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB); + int mColor, dColor; + for (int x = 0; x < maxWidth; x++) { + for (int y = 0; y < maxHeight; y++) { + if (x >= mWidth || + y >= mHeight) { + mColor = 0; + } else { + mColor = minuend.getRGB(x, y); + } + if (x >= dWidth + relativeX || + y >= dHeight + relativeY || + x < relativeX || + y < relativeY) { + dColor = 0; + } else { + dColor = deduction.getRGB(x - relativeX, y - relativeY); + } + result.setRGB(x, y, (mColor != dColor) ? subtractColor((highlight != null) ? highlight.getRGB() : 0, mColor, dColor) : 0); + } + } + return (result); + } + + public static double distance(int rgb1, int rgb2) { + float [] buffer1 = new float[3]; + float [] buffer2 = new float[3]; + Color c1 = new Color(rgb1); + Color c2 = new Color(rgb2); + c1.getRGBColorComponents(buffer1); + c2.getRGBColorComponents(buffer2); + double distSquare = 0; + for (int i = 0; i < 3; i++) { + distSquare += (buffer1[i] - buffer2[i]) * (buffer1[i] - buffer2[i]); + } + return Math.sqrt(distSquare); + } + + private static int subtractColor(int highlight, int m, int d) { + if (highlight == 0) { + float scale = (float) (distance(m, d) / Math.sqrt(3)); + return new Color(scale, scale, scale).getRGB(); + } else { + return highlight; + } + } + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws IOException { + if (args.length != 2) { + usage(); + System.exit(1); + } + compare(new File(args[0]).getAbsoluteFile(), new File(args[1]).getAbsoluteFile()); + } + + private static void compare(File golden, File results) throws IOException { + if (golden.isDirectory()) { + File rfl; + for (File fl : golden.listFiles()) { + compare(fl, new File(results.getAbsolutePath() + File.separator + fl.getName())); + } + for (File fl : results.listFiles()) { + rfl = new File(golden.getAbsolutePath() + File.separator + fl.getName()); + if (!rfl.exists()) { + compare(rfl, fl); + } + } + } else { + DiffDialog dialog = new DiffDialog(); + dialog.setImages(golden.exists() ? ImageIO.read(golden) : null, results.exists() ? ImageIO.read(results) : null); + dialog.setVisible(true); + switch (dialog.status) { + case -2: + System.exit(0); + case -1: + copy(results, golden); + break; + case 1: + golden.delete(); + break; + } + } + } + + private static void usage() { + System.out.println("java -jar JemmyAWTInput.jar "); + } + + private static void copy(File results, File golden) throws FileNotFoundException, IOException { + if (golden.exists()) { + golden.delete(); + } + FileInputStream from = new FileInputStream(results); + FileOutputStream to = new FileOutputStream(golden); + byte[] buffer = new byte[4096]; + int bytesRead; + + while ((bytesRead = from.read(buffer)) != -1) { + to.write(buffer, 0, bytesRead); // write + } + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/NaturalImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/NaturalImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/NaturalImageComparator.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import java.awt.Color; +import java.awt.image.BufferedImage; +import org.jemmy.image.pixel.AverageDistanceComparator; +import org.jemmy.image.pixel.MaxDistanceComparator; + + +/** + * Compares two images naturally + * (i.e. ignoring slight difference between pixel colors). + * + * @author KAM + */ +public class NaturalImageComparator extends BufferedImageComparator { + + /** + * Creates comparator with the default sensitivity value = 0.02 + * (around 5 in 0-255 color component value). + * @see #NaturalImageComparator(double) + */ + public NaturalImageComparator() { + this(0.02); + } + + /** + * Creates comparator with the specified sensitivity value + * @param sensitivity Maximum threshold for 3-D distance between colors + * in 3-D sRGB color space for pixels to be considered equal. + * Meaningful values are between 0 and approx 1.733. 0 means colors should + * be equal to pass the comparison, 1.733 (which is more than square root + * of 3) means that comparison will be passed even if the colors are + * completely different. You could also use {@linkplain + * #findSensitivity(java.awt.image.BufferedImage, java.awt.image.BufferedImage) + * findSensitivity()} method to obtain necessary sensitivity value. + */ + public NaturalImageComparator(double sensitivity) { + super(new MaxDistanceComparator(sensitivity)); + } + public void setSensitivity(double sensitivity) { + ((MaxDistanceComparator)getRasterComparator()).setThreshold(sensitivity); + } + public double getSensitivity() { + return ((MaxDistanceComparator)getRasterComparator()).getThreshold(); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/PNGDecoder.java b/core/JemmyAWTInput/src/org/jemmy/image/PNGDecoder.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/PNGDecoder.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.Color; + +import java.awt.image.BufferedImage; + +import java.io.InputStream; +import java.io.IOException; +import java.io.FileInputStream; + +import org.jemmy.JemmyException; + +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * Allows to load PNG graphical file. + * @author Alexandre Iline + */ +public class PNGDecoder extends Object { + + InputStream in; + + /** + * Constructs a PNGDecoder object. + * @param in input stream to read PNG image from. + */ + public PNGDecoder(InputStream in) { + this.in = in; + } + + byte read() throws IOException { + byte b = (byte)in.read(); + return(b); + } + + int readInt() throws IOException { + byte b[] = read(4); + return(((b[0]&0xff)<<24) + + ((b[1]&0xff)<<16) + + ((b[2]&0xff)<<8) + + ((b[3]&0xff))); + } + + byte[] read(int count) throws IOException { + byte[] result = new byte[count]; + for(int i = 0; i < count; i++) { + result[i] = read(); + } + return(result); + } + + boolean compare(byte[] b1, byte[] b2) { + if(b1.length != b2.length) { + return(false); + } + for(int i = 0; i < b1.length; i++) { + if(b1[i] != b2[i]) { + return(false); + } + } + return(true); + } + + void checkEquality(byte[] b1, byte[] b2) { + if(!compare(b1, b2)) { + throw(new JemmyException("Format error")); + } + } + + /** + * Decodes image from an input stream passed into constructor. + * @return a BufferedImage object + * @throws IOException + */ + public BufferedImage decode() throws IOException { + return decode(true); + } + + /** + * Decodes image from an input stream passed into constructor. + * @return a BufferedImage object + * @param closeStream requests method to close the stream after the image is read + * @throws IOException + */ + public BufferedImage decode(boolean closeStream) throws IOException { + + byte[] id = read(12); + checkEquality(id, new byte[] {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}); + + byte[] ihdr = read(4); + checkEquality(ihdr, "IHDR".getBytes()); + + int width = readInt(); + int height = readInt(); + + BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + byte[] head = read(5); + int mode; + if(compare(head, new byte[]{1, 0, 0, 0, 0})) { + mode = PNGEncoder.BW_MODE; + } else if(compare(head, new byte[]{8, 0, 0, 0, 0})) { + mode = PNGEncoder.GREYSCALE_MODE; + } else if(compare(head, new byte[]{8, 2, 0, 0, 0})) { + mode = PNGEncoder.COLOR_MODE; + } else { + throw(new JemmyException("Format error")); + } + + readInt();//!!crc + + int size = readInt(); + + byte[] idat = read(4); + checkEquality(idat, "IDAT".getBytes()); + + byte[] data = read(size); + + + Inflater inflater = new Inflater(); + inflater.setInput(data, 0, size); + + int color; + + try { + switch (mode) { + case PNGEncoder.BW_MODE: + { + int bytes = (int)(width / 8); + if((width % 8) != 0) { + bytes++; + } + byte colorset; + byte[] row = new byte[bytes]; + for (int y = 0; y < height; y++) { + inflater.inflate(new byte[1]); + inflater.inflate(row); + for (int x = 0; x < bytes; x++) { + colorset = row[x]; + for (int sh = 0; sh < 8; sh++) { + if(x * 8 + sh >= width) { + break; + } + if((colorset & 0x80) == 0x80) { + result.setRGB(x * 8 + sh, y, Color.white.getRGB()); + } else { + result.setRGB(x * 8 + sh, y, Color.black.getRGB()); + } + colorset <<= 1; + } + } + } + } + break; + case PNGEncoder.GREYSCALE_MODE: + { + byte[] row = new byte[width]; + for (int y = 0; y < height; y++) { + inflater.inflate(new byte[1]); + inflater.inflate(row); + for (int x = 0; x < width; x++) { + color = row[x]; + result.setRGB(x, y, (color << 16) + (color << 8) + color); + } + } + } + break; + case PNGEncoder.COLOR_MODE: + { + byte[] row = new byte[width * 3]; + for (int y = 0; y < height; y++) { + inflater.inflate(new byte[1]); + inflater.inflate(row); + for (int x = 0; x < width; x++) { + result.setRGB(x, y, + ((row[x * 3 + 0]&0xff) << 16) + + ((row[x * 3 + 1]&0xff) << 8) + + ((row[x * 3 + 2]&0xff))); + } + } + } + } + } catch(DataFormatException e) { + throw(new JemmyException("ZIP error", e)); + } + + readInt();//!!crc + readInt();//0 + + byte[] iend = read(4); + checkEquality(iend, "IEND".getBytes()); + + readInt();//!!crc + if (closeStream) { + in.close(); + } + + return(result); + } + + /** + * Decodes image from file. + * @param fileName a file to read image from + * @return a BufferedImage instance. + */ + public static BufferedImage decode(String fileName) { + try { + return(new PNGDecoder(new FileInputStream(fileName)).decode()); + } catch(IOException e) { + throw(new JemmyException("IOException during image reading", e)); + } + } + + /** + * Decodes image from input stream + * @param inputStream a file to read image from + * @param closeStream requests method to close the stream after the image is read + * @return a BufferedImage instance. + */ + public static BufferedImage decode(InputStream inputStream, boolean closeStream) { + try { + return(new PNGDecoder(inputStream).decode(closeStream)); + } catch(IOException e) { + throw(new JemmyException("IOException during image reading", e)); + } + } + + /** + * Decodes image from resource. + * @param loader ClassLoader to use to get the resource + * @param resource Image resource name + * @return a BufferedImage instance. + */ + public static BufferedImage decode(ClassLoader loader, String resource) { + try { + InputStream resourceStream = loader.getResourceAsStream(resource); + if (resourceStream == null) { + throw new JemmyException("Resouce '" + resource + "' could not be found!"); + } + return(new PNGDecoder(resourceStream).decode()); + } catch(IOException e) { + throw(new JemmyException("IOException during image reading", e)); + } + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/PNGEncoder.java b/core/JemmyAWTInput/src/org/jemmy/image/PNGEncoder.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/PNGEncoder.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.AWTException; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.Toolkit; + +import java.awt.image.BufferedImage; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.OutputStream; + +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import org.jemmy.control.ScreenArea; + +/** This class allows to encode BufferedImage into B/W, greyscale or true color PNG + * image format with maximum compression.
+ * It also provides complete functionality for capturing full screen, part of + * screen or single component, encoding and saving captured image info PNG file. + * @author Adam Sotona + * @version 1.0 */ +public class PNGEncoder extends Object { + + /** black and white image mode. */ + public static final byte BW_MODE = 0; + /** grey scale image mode. */ + public static final byte GREYSCALE_MODE = 1; + /** full color image mode. */ + public static final byte COLOR_MODE = 2; + + OutputStream out; + CRC32 crc; + byte mode; + + /** + * + * @param file + * @throws java.io.FileNotFoundException + */ + public PNGEncoder(File file) throws FileNotFoundException { + this(new FileOutputStream(file)); + } + /** public constructor of PNGEncoder class with greyscale mode by default. + * @param out output stream for PNG image format to write into + */ + public PNGEncoder(OutputStream out) { + this(out, GREYSCALE_MODE); + } + + /** public constructor of PNGEncoder class. + * @param out output stream for PNG image format to write into + * @param mode BW_MODE, GREYSCALE_MODE or COLOR_MODE + */ + public PNGEncoder(OutputStream out, byte mode) { + crc=new CRC32(); + this.out = out; + if (mode<0 || mode>2) + throw new IllegalArgumentException("Unknown color mode"); + this.mode = mode; + } + + void write(int i) throws IOException { + byte b[]={(byte)((i>>24)&0xff),(byte)((i>>16)&0xff),(byte)((i>>8)&0xff),(byte)(i&0xff)}; + write(b); + } + + void write(byte b[]) throws IOException { + out.write(b); + crc.update(b); + } + + /** main encoding method (stays blocked till encoding is finished). + * @param image BufferedImage to encode + * @throws IOException IOException + */ + public void encode(BufferedImage image) throws IOException { + encode(image, true); + } + + /** main encoding method (stays blocked till encoding is finished). + * @param image BufferedImage to encode + * @param closeStream requests method to close the stream after the image is written + * @throws IOException IOException + */ + public void encode(BufferedImage image, boolean closeStream) throws IOException { + int width = image.getWidth(null); + int height = image.getHeight(null); + final byte id[] = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13}; + write(id); + crc.reset(); + write("IHDR".getBytes()); + write(width); + write(height); + byte head[]=null; + switch (mode) { + case BW_MODE: head=new byte[]{1, 0, 0, 0, 0}; break; + case GREYSCALE_MODE: head=new byte[]{8, 0, 0, 0, 0}; break; + case COLOR_MODE: head=new byte[]{8, 2, 0, 0, 0}; break; + } + write(head); + write((int) crc.getValue()); + ByteArrayOutputStream compressed = new ByteArrayOutputStream(65536); + BufferedOutputStream bos = new BufferedOutputStream( new DeflaterOutputStream(compressed, new Deflater(9))); + int pixel; + int color; + int colorset; + switch (mode) { + case BW_MODE: + int rest=width%8; + int bytes=width/8; + for (int y=0;y> 16) & 0xff); + color+=((pixel >> 8) & 0xff); + color+=(pixel & 0xff); + colorset<<=1; + if (color>=3*128) + colorset|=1; + } + bos.write((byte)colorset); + } + if (rest>0) { + colorset=0; + for (int sh=0; sh> 16) & 0xff); + color+=((pixel >> 8) & 0xff); + color+=(pixel & 0xff); + colorset<<=1; + if (color>=3*128) + colorset|=1; + } + colorset<<=8-rest; + bos.write((byte)colorset); + } + } + break; + case GREYSCALE_MODE: + for (int y=0;y> 16) & 0xff); + color+=((pixel >> 8) & 0xff); + color+=(pixel & 0xff); + bos.write((byte)(color/3)); + } + } + break; + case COLOR_MODE: + for (int y=0;y> 16) & 0xff)); + bos.write((byte)((pixel >> 8) & 0xff)); + bos.write((byte)(pixel & 0xff)); + } + } + break; + } + bos.close(); + write(compressed.size()); + crc.reset(); + write("IDAT".getBytes()); + write(compressed.toByteArray()); + write((int) crc.getValue()); + write(0); + crc.reset(); + write("IEND".getBytes()); + write((int) crc.getValue()); + out.flush(); + if (closeStream) { + out.close(); + } + } + + /** Static method performing screen capture into PNG image format file with given fileName. + * @param rect Rectangle of screen to be captured + * @param fileName file name for screen capture PNG image file */ + public static void captureScreen(Rectangle rect, String fileName) { + captureScreen(rect, fileName, GREYSCALE_MODE); + } + + /** Static method performing screen capture into PNG image format file with given fileName. + * @param rect Rectangle of screen to be captured + * @param mode image color mode + * @param fileName file name for screen capture PNG image file */ + public static void captureScreen(Rectangle rect, String fileName, byte mode) { + try { + BufferedImage capture=new Robot().createScreenCapture(rect); + BufferedOutputStream file=new BufferedOutputStream(new FileOutputStream(fileName)); + PNGEncoder encoder=new PNGEncoder(file, mode); + encoder.encode(capture); + } catch (AWTException awte) { + awte.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + /** Static method performing one component screen capture into PNG image format file with given fileName. + * @param comp Component to be captured + * @param fileName String image target filename */ + public static void captureScreen(ScreenArea comp, String fileName) { + captureScreen(comp, fileName, GREYSCALE_MODE); + } + + /** Static method performing one component screen capture into PNG image format file with given fileName. + * @param comp Component to be captured + * @param fileName String image target filename + * @param mode image color mode */ + public static void captureScreen(ScreenArea comp, String fileName, byte mode) { + captureScreen(Utils.convert(comp.getScreenBounds()), + fileName, mode); + } + + + /** Static method performing whole screen capture into PNG image format file with given fileName. + * @param fileName String image target filename */ + public static void captureScreen(String fileName) { + captureScreen(fileName, GREYSCALE_MODE); + } + + /** Static method performing whole screen capture into PNG image format file with given fileName. + * @param fileName String image target filename + * @param mode image color mode */ + public static void captureScreen(String fileName, byte mode) { + captureScreen(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()), fileName, mode); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/PNGImageLoader.java b/core/JemmyAWTInput/src/org/jemmy/image/PNGImageLoader.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/PNGImageLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.image.BufferedImage; + +import java.io.IOException; +import java.io.InputStream; + + +/** + * Allowes to process PNF image format. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +class PNGImageLoader { + + /** + * Loads an image from a PNG image file. + * @param in + * @throws IOException + */ + public BufferedImage load(InputStream in) throws IOException { + return(new PNGDecoder(in).decode()); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/PNGImageSaver.java b/core/JemmyAWTInput/src/org/jemmy/image/PNGImageSaver.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/PNGImageSaver.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.image.BufferedImage; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + + +/** + * Allows to process PNG image format. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +class PNGImageSaver { + + /** + * Saves an image into a PNG image file. + * @throws IOException + */ + public void save(BufferedImage image, String fileName) throws IOException{ + File f = new File(fileName); + File parent = f.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + new PNGEncoder(new BufferedOutputStream(new FileOutputStream(f)), + PNGEncoder.COLOR_MODE).encode(image); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/ResizeImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/ResizeImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/ResizeImageComparator.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import org.jemmy.Dimension; +import org.jemmy.env.Environment; +import org.jemmy.env.TestOut; +import org.jemmy.image.pixel.Raster; +import org.jemmy.image.pixel.RasterComparator; +import org.jemmy.image.pixel.ResizeComparator; + +/** + * Comparator that makes image sizes equal to compare them with other comparator + * most of them work only for the images with equal dimensions. + * + * It is controlled by three parameters:
  • Resize Mode - defines way of + * resizing images that are being compared. One of the following constants:
    • {@linkplain ResizeMode#NO_RESIZE NO_RESIZE},
    • {@linkplain ResizeMode#PROPORTIONAL_RESIZE PROPORTIONAL_RESIZE},
    • {@linkplain ResizeMode#ARBITRARY_RESIZE ARBITRARY_RESIZE}.
    + * Default value is {@linkplain ResizeMode#PROPORTIONAL_RESIZE}.
  • Resize + * Hint - defines the way of images scaling that is specified when {@linkplain java.awt.Image#getScaledInstance(int, int, int) + * Image.getScaledInstance()} method is invoked. One of the Image.SCALE_XXX is + * expected. Default value is + * {@linkplain java.awt.Image#SCALE_DEFAULT SCALE_DEFAULT}.
  • Proportion + * Distortion Threshold - defines maximum proportion distortion that is used in {@linkplain ResizeMode#PROPORTIONAL_RESIZE + * PROPORTIONAL_RESIZE} mode to deal with cases when rounding and other problems + * could cause sizes to be not ideally proportional.

    + * Threshold value is applied in the following way: {@code Math.abs(image.getSize() * scale + * - size) / image.getSize() > PROPORTION_DISTORTION_THRESHOLD}, where {@code image.getSize()} + * is both image dimensions and {@code size} is target width and height (which + * is defined as mininum of sizes of two images) and scale is the scale factor + * that scales the particular image to fit the width and height. + *

    + * Default value is 0.02 so as much as 2% of width/height could differ (around 5 + * in 0-255 color component values).

  • + * + * @author mrkam + */ +public class ResizeImageComparator extends ResizeComparator { + + /** + * Indentifies output where resize details are printed. Output is disabled + * by default. + * + * @see Environment#getOutput(java.lang.String) + */ + public static final String OUTPUT = ResizeImageComparator.class.getName() + + ".OUTPUT"; + + static { + Environment.getEnvironment().setOutput(OUTPUT, TestOut.getNullOutput()); + } + ResizeMode resizeMode; + int hint; + double propDistThreshold; + + /** + * Resize Modes + * + * @see + * #ResizeImageComparator(org.jemmy.image.ResizeImageComparator.ResizeMode, + * org.jemmy.image.ImageComparator) + * @see + * #ResizeImageComparator(org.jemmy.image.ResizeImageComparator.ResizeMode, + * int, double, org.jemmy.image.ImageComparator) + */ + public static enum ResizeMode { + + /** + * Images are never resized. Original images are always compared. + */ + NO_RESIZE, + /** + * Images are resized only if they have exactly or almost proportional + * sizes which is controlled by {@code proportionDistortion} parameter. + * If images have different proportions no resize is done and original + * images are compared. + * + * @see + * #ResizeImageComparator(org.jemmy.image.ResizeImageComparator.ResizeMode, + * int, double, org.jemmy.image.ImageComparator) + */ + PROPORTIONAL_RESIZE, + /** + * Images are always resized to match both width and height and then + * compared. + */ + ARBITRARY_RESIZE + } + + /** + * Creates ResizeImageComparator with default resize settings:
  • resize + * mode: {@linkplain ResizeMode#PROPORTIONAL_RESIZE ResizeMode.PROPORTIONAL_RESIZE}.
  • + *
  • proportion distortion threshold: 0.02.
  • resize hint: {@linkplain java.awt.Image#SCALE_DEFAULT Image.SCALE_DEFAULT}.
  • + * + * @param subComparator comparator to compare images after resize. + * @see java.awt.Image#getScaledInstance(int, int, int) + * @see ResizeMode + * @see ResizeImageComparator + */ + public ResizeImageComparator(ImageComparator subComparator) { + super(subComparator, ResizeComparator.Mode.LEFT); + this.resizeMode = ResizeMode.PROPORTIONAL_RESIZE; + this.hint = java.awt.Image.SCALE_DEFAULT; + this.propDistThreshold = 0.02; + } + + /** + * Creates ResizeImageComparator with the specified resize mode and default + * settings for other parameters:
  • proportion distortion threshold: + * 0.02.
  • resize hint: {@linkplain java.awt.Image#SCALE_DEFAULT Image.SCALE_DEFAULT}.
  • + * + * @param resizeMode resize mode for this comparator. + * @param subComparator comparator to compare images after resize. + * @see ResizeMode + * @see ResizeImageComparator + */ + public ResizeImageComparator(ResizeMode resizeMode, + ImageComparator subComparator) { + this(subComparator); + this.resizeMode = resizeMode; + } + + /** + * Creates ResizeImageComparator with the following settings:
  • resize + * mode: {@linkplain ResizeMode#PROPORTIONAL_RESIZE ResizeMode.PROPORTIONAL_RESIZE}.
  • + *
  • specified proportion distortion threshold.
  • resize hint: {@linkplain java.awt.Image#SCALE_DEFAULT Image.SCALE_DEFAULT}.
  • + * + * @param propDistThreshold proportion distortion threshold. + * @param subComparator comparator to compare images after resize. + * @see ResizeImageComparator + */ + public ResizeImageComparator(double propDistThreshold, + ImageComparator subComparator) { + this(subComparator); + this.propDistThreshold = propDistThreshold; + } + + /** + * Creates ResizeImageComparator with specified settings. + * + * @param resizeMode Resize mode. + * @param propDistThreshold Proportion distortion threshold. + * @param hint Resize hint. + * @param subComparator comparator to compare images after resize. + * @see ResizeImageComparator + * @see ResizeMode + */ + public ResizeImageComparator(ResizeMode resizeMode, double propDistThreshold, + int hint, ImageComparator subComparator) { + this(subComparator); + this.resizeMode = resizeMode; + this.hint = hint; + this.propDistThreshold = propDistThreshold; + } + + @Override + public Image resize(Image image, Dimension size) { + Dimension isize = getSize(image); + double scalex = (double) size.width / isize.width; + double scaley = (double) size.height / isize.height; + switch (resizeMode) { + case NO_RESIZE: + return image; + case PROPORTIONAL_RESIZE: + if (Math.abs(scalex - scaley) > propDistThreshold) { + return null; + } + case ARBITRARY_RESIZE: + BufferedImage res = new BufferedImage(size.width, size.height, ((AWTImage) image).getTheImage().getType()); + java.awt.Image scaled = ((AWTImage) image).getTheImage().getScaledInstance( + size.width, size.height, hint); + Graphics2D g = res.createGraphics(); + g.drawImage(scaled, 0, 0, null); + g.dispose(); + return new AWTImage(res); + default: + return null; + } + } + @Override + public String getID() { + return ResizeImageComparator.class.getName() + "(" + + getSubComparator().getID() + ")"; + } + + @Override + public Dimension getSize(Image image) { + BufferedImage bi = ((AWTImage)image).getTheImage(); + return new Dimension(bi.getWidth(), bi.getHeight()); + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/RoughImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/RoughImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/RoughImageComparator.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import org.jemmy.image.pixel.PixelEqualityRasterComparator; + +/** + * Compares two images roughly (i.e. not all of the pixel colors should match). + * + * @author Alexandre Iline (alexandre.iline@sun.com), KAM + */ +public class RoughImageComparator extends BufferedImageComparator { + + /** + * Creates a comparator with + * roughness allowed roughness. + * + * @param roughness Allowed comparision roughness. + */ + public RoughImageComparator(double roughness) { + super(new PixelEqualityRasterComparator(roughness)); + } + public void setRoughness(double roughness) { + ((PixelEqualityRasterComparator)getRasterComparator()).setThreshold(roughness); + } + public double getRoughness() { + return ((PixelEqualityRasterComparator)getRasterComparator()).getThreshold(); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/RoughImageFinder.java b/core/JemmyAWTInput/src/org/jemmy/image/RoughImageFinder.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/RoughImageFinder.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import org.jemmy.Point; +import java.awt.image.BufferedImage; + +/** + * Performs "rough" image search. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public class RoughImageFinder implements ImageFinder { + double roughness = .0; + int bigWidth, bigHeight; + int[][] bigPixels; + + /** + * Creates an instance allowing to find an image inside the one + * passed as parameter with some "roughness". + * @param area - Image to search in. + * @param roughness - Allowed + */ + public RoughImageFinder(BufferedImage area, double roughness) { + this.roughness = roughness; + bigWidth = area.getWidth(); + bigHeight = area.getHeight(); + bigPixels = new int[bigWidth][bigHeight]; + for(int x = 0; x < bigWidth; x++) { + for(int y = 0; y < bigHeight; y++) { + bigPixels[x][y] = area.getRGB(x, y); + } + } + } + + /** + * Performs "rough" search. + * @param image an image to search. + * @param index an ordinal image location index. + * @return Point where number of unmatching pixels less or equal to + * image1.getWidth() * image1.getHeight() * roughness + */ + public Point findImage(BufferedImage image, int index) { + int smallWidth = image.getWidth(); + int smallHeight = image.getHeight(); + int[][] smallPixels = new int[smallWidth][smallHeight]; + for(int x = 0; x < smallWidth; x++) { + for(int y = 0; y < smallHeight; y++) { + smallPixels[x][y] = image.getRGB(x, y); + } + } + double maxRoughPixels = (double)(smallWidth * smallHeight) * roughness; + int count = 0; + for(int X = 0; X <= bigWidth - smallWidth; X++) { + for(int Y = 0; Y <= bigHeight - smallHeight; Y++) { + int roughPixels = 0; + for(int x = 0; x < smallWidth; x++) { + for(int y = 0; y < smallHeight; y++) { + if(smallPixels[x][y] != bigPixels[X + x][Y + y]) { + roughPixels++; + if(roughPixels > maxRoughPixels) { + break; + } + } + } + if(roughPixels > maxRoughPixels) { + break; + } + } + if(roughPixels <= maxRoughPixels) { + if(count == index) { + return(new Point(X, Y)); + } + count++; + } + } + } + return(null); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/StrictImageComparator.java b/core/JemmyAWTInput/src/org/jemmy/image/StrictImageComparator.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/StrictImageComparator.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import org.jemmy.image.pixel.PixelEqualityRasterComparator; + +/** + * Compares two images strictly (i.e. all the pixel colors should match). + * + * @author Alexandre Iline (alexandre.iline@sun.com), KAM + */ +public class StrictImageComparator extends BufferedImageComparator { + + public StrictImageComparator() { + super(new PixelEqualityRasterComparator(0)); + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/StrictImageFinder.java b/core/JemmyAWTInput/src/org/jemmy/image/StrictImageFinder.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/StrictImageFinder.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import org.jemmy.Point; +import java.awt.image.BufferedImage; + +/** + * Performs "strict" (i.e. based on all pixels matching) image search. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public class StrictImageFinder implements ImageFinder { + int bigWidth, bigHeight; + int[][] bigPixels; + + /** + * Creates an instance searching subimages insige a parameter image. + * @param area - Image to search in. + */ + public StrictImageFinder(BufferedImage area) { + bigWidth = area.getWidth(); + bigHeight = area.getHeight(); + bigPixels = new int[bigWidth][bigHeight]; + for(int x = 0; x < bigWidth; x++) { + for(int y = 0; y < bigHeight; y++) { + bigPixels[x][y] = area.getRGB(x, y); + } + } + } + + /** + * Searchs for an image inside image passed into constructor. + * @param image an image to search. + * @param index an ordinal image location index. If equal to 1, for example, + * second appropriate location will be found. + * @return Left-up corner coordinates of image location. + */ + public Point findImage(BufferedImage image, int index) { + int smallWidth = image.getWidth(); + int smallHeight = image.getHeight(); + int[][] smallPixels = new int[smallWidth][smallHeight]; + for(int x = 0; x < smallWidth; x++) { + for(int y = 0; y < smallHeight; y++) { + smallPixels[x][y] = image.getRGB(x, y); + } + } + boolean good; + int count = 0; + for(int X = 0; X <= bigWidth - smallWidth; X++) { + for(int Y = 0; Y <= bigHeight - smallHeight; Y++) { + good = true; + for(int x = 0; x < smallWidth; x++) { + for(int y = 0; y < smallHeight; y++) { + if(smallPixels[x][y] != bigPixels[X + x][Y + y]) { + good = false; + break; + } + } + if(!good) { + break; + } + } + if(good) { + if(count == index) { + return(new Point(X, Y)); + } + count++; + } + } + } + return(null); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/Utils.java b/core/JemmyAWTInput/src/org/jemmy/image/Utils.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/Utils.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import org.jemmy.Dimension; +import org.jemmy.Point; +import org.jemmy.Rectangle; + + +/** + * + * @author Alexander Kouznetsov + */ +public class Utils { + + /** + * + * @param r + * @return java.awt.Rectangle + */ + public static java.awt.Rectangle convert(Rectangle r) { + return new java.awt.Rectangle(r.x, r.y, r.width, r.height); + } + + /** + * + * @param r + * @return org.jemmy.Rectangle + */ + public static Rectangle convert(java.awt.Rectangle r) { + return new Rectangle(r.x, r.y, r.width, r.height); + } + + /** + * + * @param p + * @return java.awt.Point + */ + public static java.awt.Point convert(Point p) { + return new java.awt.Point(p.x, p.y); + } + + /** + * + * @param p + * @return org.jemmy.Point + */ + public static Point convert(java.awt.Point p) { + return new Point(p.x, p.y); + } + + /** + * + * @param d + * @return java.awt.Dimension + */ + public static java.awt.Dimension convert(Dimension d) { + return new java.awt.Dimension(d.width, d.height); + } + + /** + * + * @param d + * @return org.jemmy.Dimension + */ + public static Dimension convert(java.awt.Dimension d) { + return new Dimension(d.width, d.height); + } + +} diff --git a/core/JemmyAWTInput/src/org/jemmy/image/package.html b/core/JemmyAWTInput/src/org/jemmy/image/package.html new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/image/package.html @@ -0,0 +1,17 @@ + + + + Image library + + + +

    Image library

    + + Contains classes allowing to compare two images and + to find one image inside another.

    + + @since 9 Nov 2002 + +
    + + diff --git a/core/JemmyAWTInput/src/org/jemmy/input/AWTMap.java b/core/JemmyAWTInput/src/org/jemmy/input/AWTMap.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/AWTMap.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jemmy.JemmyException; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Keyboard.KeyboardButtons; +import org.jemmy.interfaces.Keyboard.KeyboardModifiers; +import org.jemmy.interfaces.Modifier; +import org.jemmy.interfaces.Mouse.MouseButton; +import org.jemmy.interfaces.Mouse.MouseButtons; +import org.jemmy.interfaces.Mouse.MouseModifiers; + + +/** + * Converts + * @author Alexander Kouznetsov + */ +public class AWTMap { + + private static Map int2key = new HashMap(); + private static Map int2modifier = new HashMap(); + private static Map int2button = new HashMap(); + private static Map key2int = new HashMap(); + private static Map modifier2int = new HashMap(); + private static Map button2int = new HashMap(); + + static { + for (KeyboardButtons button : KeyboardButtons.values()) { + String name = button.name(); + try { + int key = KeyEvent.VK_UNDEFINED; + if (name.length() == 2 && name.startsWith("D")) { + // digit + key = KeyEvent.class.getDeclaredField("VK_" + name.substring(1)).getInt(null); + } else { + key = KeyEvent.class.getDeclaredField("VK_" + name).getInt(null); + } + int2key.put(key, button); + key2int.put(button, key); + } catch (NoSuchFieldException ex) { + throw new JemmyException("Unable to recognize key", ex, button); + } catch (SecurityException ex) { + throw new JemmyException("Unable to recognize key", ex, button); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Unable to recognize key", ex, button); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to recognize key", ex, button); + } + } + for (KeyboardModifiers modifier : KeyboardModifiers.values()) { + String name = modifier.name(); + try { + int key = InputEvent.class.getDeclaredField(name).getInt(null); + int2modifier.put(key, modifier); + modifier2int.put(modifier, key); + } catch (NoSuchFieldException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } catch (SecurityException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } + } + for (MouseModifiers modifier : MouseModifiers.values()) { + String name = modifier.name(); + try { + int key = InputEvent.class.getDeclaredField(name).getInt(null); + int2modifier.put(key, modifier); + modifier2int.put(modifier, key); + } catch (NoSuchFieldException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } catch (SecurityException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to recognize modifier", ex, modifier); + } + } + for (MouseButtons button : MouseButtons.values()) { + String name = button.name(); + try { + int key = InputEvent.class.getDeclaredField(name + "_MASK").getInt(null); + int2button.put(key, button); + button2int.put(button, key); + } catch (NoSuchFieldException ex) { + throw new JemmyException("Unable to recognize button", ex, button); + } catch (SecurityException ex) { + throw new JemmyException("Unable to recognize button", ex, button); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Unable to recognize button", ex, button); + } catch (IllegalAccessException ex) { + throw new JemmyException("Unable to recognize button", ex, button); + } + } + } + + /** + * TODO Provide javadoc + * @param button + * @return One of InputEvent.VK_* constants + * @see InputEvent + */ + public int convert(KeyboardButton button) { + try { + return key2int.get(button); + } catch(Exception e) { + throw new JemmyException("Unable to recognize key", e, button); + } + } + + /** + * TODO Provide javadoc + * @param modifiers + * @return + */ + public int convert(Modifier... modifiers) { + int result = 0; + for (Modifier modifier : modifiers) { + try { + result |= modifier2int.get(modifier); + } catch (Exception e) { + throw new JemmyException("Unable to recognize modifier", e, modifier); + } + } + return result; + } + + /** + * TODO Provide javadoc + * @param button + * @return + */ + public int convert(MouseButton button) { + try { + return button2int.get(button); + } catch (Exception e) { + throw new JemmyException("Unable to recognize mouse button", e, button); + } + } + + /** + * TODO Provide javadoc + * @param key + * @return + */ + public KeyboardButton convertKeyboardButton(int key) { + KeyboardButton res = int2key.get(key); + if (res == null) { + throw new JemmyException("Unable to recognize key", key); + } + return res; + } + + /** + * TODO Provide javadoc + * @param modifiers + * @return + */ + public Modifier[] convertModifiers(int modifiers) { + List result = new ArrayList(); + for (int key : int2modifier.keySet()) { + if ((key & modifiers) != 0) { + Modifier res = int2modifier.get(key); + if (res == null) { + throw new JemmyException("Unable to recognize modifiers", modifiers); + } + result.add(res); + } + } + return result.toArray(new Modifier[result.size()]); + } + + /** + * TODO Provide javadoc + * @param button + * @return + */ + public MouseButton convertMouseButton(int button) { + MouseButton res = int2button.get(button); + if (res == null) { + throw new JemmyException("Unable to recognize mouse button", button); + } + return res; + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/AWTRobotInputFactory.java b/core/JemmyAWTInput/src/org/jemmy/input/AWTRobotInputFactory.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/AWTRobotInputFactory.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +import org.jemmy.JemmyException; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.interfaces.ControlInterface; +import org.jemmy.interfaces.ControlInterfaceFactory; +import org.jemmy.interfaces.Drag; +import org.jemmy.interfaces.Keyboard; +import org.jemmy.interfaces.Mouse; +import org.jemmy.interfaces.TypeControlInterface; +import org.jemmy.operators.AWTScreen; +import org.jemmy.operators.Screen; + +/** + * + * @author shura + */ +public class AWTRobotInputFactory implements ControlInterfaceFactory { + + /** + * Set this Environment property to true or false to run java.awt.Robot in + * other or the same JVM + */ + public static final String OTHER_VM_PROPERTY = "awt.robot.othervm"; + + /** + * Set this Environment property to the name of the host where other JVM runs. + * 'localhost' by default + */ + public static final String OTHER_VM_HOST_PROPERTY = "awt.robot.othervm.host"; + + /** + * Set this Environment property to override the port which is used to + * connect to other JVM + */ + public static final String OTHER_VM_PORT_PROPERTY = "awt.robot.othervm.port"; + + /** + * Set this Environment property to to the maximum time of waiting for the + * client to connect to the JVM where Robot is running. It also waits the same + * amount of ms for the next connection after the previous terminates. + * Default is 15 min. + */ + public static final String OTHER_VM_CONNECTION_TIMEOUT_PROPERTY + = "awt.robot.othervm.connection.timeout"; + + /** + * The name of the timeout that is used by default as the delay time for + * java.awt.Robot + * @see java.awt.Robot#setAutoDelay(int) + */ + public static final String ROBOT_DELAY_TIMEOUT_NAME = "RobotDriver.DelayTimeout"; + + /** + * Set this Environment property to the maximum number of pixels between + * mouse positions during movement + */ + public static final String ROBOT_MOUSE_SMOOTHNESS_PROPERTY = "awt.robot.mouse.smoothness"; + + /** + * Specifies whether to run java.awt.Robot in other JVM + * @param runInOtherJVM if true then java.awt.Robot will run in other JVM + */ + public static void runInOtherJVM(boolean runInOtherJVM) { + RobotExecutor.get().setRunInOtherJVM(runInOtherJVM); + } + + /** + * Returns runInOtherJVM setting + * @return if true then java.awt.Robot is running in other JVM + */ + public static boolean isRunInOtherJVM() { + return RobotExecutor.get().isRunInOtherJVM(); + } + + /** + * Specifies mouse movements smoothness + * @param mouseSmoothness the maximum number of pixels between + * mouse positions during movement + * @see #ROBOT_MOUSE_SMOOTHNESS_PROPERTY + */ + public static void setMouseSmoothness(int mouseSmoothness) { + if(mouseSmoothness <= 0) { + throw new IllegalArgumentException("Mouse smoothness should be greater than zero."); + } + RobotDriver.setMouseSmoothness(mouseSmoothness); + } + + /** + * Gets the mouse movements smoothness + * @return the maximum number of pixels between + * mouse positions during movement + * @see #ROBOT_MOUSE_SMOOTHNESS_PROPERTY + */ + public static int getMouseSmoothness() { + return RobotDriver.getMouseSmoothness(); + } + + static { + if(Screen.SCREEN == null) { + Screen.setSCREEN(new AWTScreen(Environment.getEnvironment())); + } + } + + public AWTMap getAwtMap() { + return RobotExecutor.get().getAWTMap(); + } + + public void setAwtMap(AWTMap awtMap) { + RobotExecutor.get().setAWTMap(awtMap); + } + + public INTERFACE create(Wrap control, Class interfaceClass) { + if(Mouse.class.isAssignableFrom(interfaceClass)) { + return (INTERFACE) new MouseImpl(control); + } else if(Keyboard.class.isAssignableFrom(interfaceClass)) { + return (INTERFACE) new KeyboardImpl(control); + } else if(Drag.class.isAssignableFrom(interfaceClass)) { + return (INTERFACE) new DragImpl(control); + } + throw new JemmyException(AWTRobotInputFactory.class.getName() + " does not support " + interfaceClass.getName()); + } + + public > INTERFACE create(Wrap control, Class interfaceClass, Class type) { + throw new JemmyException(AWTRobotInputFactory.class.getName() + " does not support " + interfaceClass.getName()); + } + + @Override + public String toString() { + return getClass().getName() + "[otherVM=" + isRunInOtherJVM() + ", mouseSmoothness=" + getMouseSmoothness() + "]"; + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/ClassReference.java b/core/JemmyAWTInput/src/org/jemmy/input/ClassReference.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/ClassReference.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * + * Allows access to classes by reflection. + * + * @author Alexandre Iline (alexandre.iline@sun.com) + */ +public class ClassReference { + + private Class cl; + private Object instance; + + /** + * Constructor. + * @param o Object to work with. + */ + public ClassReference(Object o) { + super(); + instance = o; + cl = o.getClass(); + } + + /** + * Contructor. + * The object created by this constructor can be used + * to access static methods and fields only. + * + * @param className name of class + * @exception ClassNotFoundException + */ + public ClassReference(String className) + throws ClassNotFoundException { + super(); + cl = Class.forName(className); + instance = null; + } + + /** + * Executes class's main(java.lang.String[]) method + * with a zero-length java.lang.String array + * as a parameter. + * + * @exception NoSuchMethodException + * @exception InvocationTargetException + */ + public void startApplication() + throws InvocationTargetException, NoSuchMethodException { + String[] params = new String[0]; + startApplication(params); + } + + /** + * Executes class's main(java.lang.String[]) method. + * + * @param params The java.lang.String array to pass + * to main(java.lang.String[]). + * @exception NoSuchMethodException + * @exception InvocationTargetException + */ + public void startApplication(String[] params) + throws InvocationTargetException, NoSuchMethodException { + String[] real_params; + if (params == null) { + real_params = new String[0]; + } else { + real_params = params; + } + String[][] methodParams = {real_params}; + Class[] classes = {real_params.getClass()}; + try { + invokeMethod("main", methodParams, classes); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + /** + * Locates method by name and parameter types and executes it. + * + * @param method_name Name of method. + * @param params Method parameters. + * @param params_classes Method parameters types. + * @return the return value from an invocation of the Method.
    + * If method_name method is void, null is returned.
    + * If method_name method returns a primitive type, then + * return wrapper class instance. + * @throws InvocationTargetException when the invoked method throws an exception. + * @throws NoSuchMethodException when the method cannot be found. + * @throws IllegalAccessException when access to the class or method is lacking. + * @throws SecurityException if access to the package or method is denied. + */ + public Object invokeMethod(String method_name, Object[] params, Class[] params_classes) + throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + if (params == null) { + params = new Object[0]; + } + if (params_classes == null) { + params_classes = new Class[0]; + } + Method method = cl.getMethod(method_name, + params_classes); + return (method.invoke(instance, params)); + } + + /** + * Locates constructor by parameter types and creates an instance. + * + * @param params An array of Method parameters. + * @param params_classes An array of Method parameter types. + * @return a new class instance. + * @throws InvocationTargetException when the invoked constructor throws an exception. + * @throws NoSuchMethodException when the constructor cannot be found. + * @throws IllegalAccessException when access to the class or constructor is lacking. + * @throws InstantiationException when the constructor is for an abstract class. + * @throws SecurityException if access to the package or constructor is denied. + */ + public Object newInstance(Object[] params, Class[] params_classes) + throws InvocationTargetException, NoSuchMethodException, + IllegalAccessException, InstantiationException { + if (params == null) { + params = new Object[0]; + } + if (params_classes == null) { + params_classes = new Class[0]; + } + Constructor constructor = cl.getConstructor(params_classes); + return (constructor.newInstance(params)); + } + + /** + * Returns the field value. + * @param field_name The name of the field. + * @return the field value + * @see #setField + * @throws NoSuchFieldException when the field cannot be found. + * @throws IllegalAccessException when access to the class or constructor is lacking. + * @throws SecurityException if access to the package or field is denied. + */ + public Object getField(String field_name) + throws NoSuchFieldException, IllegalAccessException { + return (cl.getField(field_name).get(instance)); + } + + /** + * Change a field's value. + * + * @param field_name The name of the field. + * @param newValue The fields new value. + * @see #getField + * @throws NoSuchFieldException when the field cannot be found. + * @throws IllegalAccessException when access to the class or constructor is lacking. + * @throws SecurityException if access to the package or field is denied. + */ + public void setField(String field_name, Object newValue) + throws NoSuchFieldException, IllegalAccessException { + cl.getField(field_name).set(instance, newValue); + } + + /** + * Returns all superclasses. + * @return an array of superclasses, starting with the reference class + * and ending with java.lang.Object. + */ + public Class[] getClasses() { + Class cls = cl; + int count = 0; + do { + count++; + cls = cls.getSuperclass(); + } while (cls != null); + Class[] result = new Class[count]; + cls = cl; + for (int i = 0; i < count; i++) { + result[i] = cls; + cls = cls.getSuperclass(); + } + return (result); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/DragImpl.java b/core/JemmyAWTInput/src/org/jemmy/input/DragImpl.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/DragImpl.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.awt.event.InputEvent; +import org.jemmy.control.*; +import org.jemmy.Point; +import org.jemmy.action.Action; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.interfaces.Mouse.MouseButton; +import org.jemmy.interfaces.Drag; +import org.jemmy.interfaces.Modifier; +import org.jemmy.interfaces.Mouse; +import org.jemmy.interfaces.Mouse.MouseButtons; +import org.jemmy.interfaces.Showable; + + +/** + * + * @author shura + */ +public class DragImpl implements Drag { + + /** + * + */ + public static final int DND_POINTS = 10; + + static { + Environment.getEnvironment().setTimeout(BEFORE_DRAG_TIMEOUT); + Environment.getEnvironment().setTimeout(BEFORE_DROP_TIMEOUT); + Environment.getEnvironment().setTimeout(IN_DRAG_TIMEOUT); + } + + private Wrap source; + + /** + * + * @param source + */ + public DragImpl(Wrap source) { + this.source = source; + } + + /** + * + * @param targetPoint + */ + public void dnd(Point targetPoint) { + dnd(source, targetPoint); + } + + /** + * + * @param target + * @param targetPoint + */ + public void dnd(Wrap target, Point targetPoint) { + dnd(source.getClickPoint(), target, targetPoint); + } + + /** + * + * @param point + * @param target + * @param targetPoint + */ + public void dnd(Point point, Wrap target, Point targetPoint) { + dnd(point, target, targetPoint, MouseButtons.BUTTON1); + } + + /** + * + * @param point + * @param target + * @param targetPoint + * @param button + */ + public void dnd(Point point, Wrap target, Point targetPoint, MouseButton button) { + dnd(point, target, targetPoint, button, new Modifier[]{}); + } + + /** + * + * @param point + * @param target + * @param targetPoint + * @param button + * @param modifiers + */ + public void dnd(Point pointParam, final Wrap target, final Point targetPoint, final MouseButton button, final Modifier... modifiers) { + final Point point = pointParam == null ? source.getClickPoint() : pointParam; + source.getEnvironment().getExecutor().execute(target.getEnvironment(), false, new Action() { + public void run(Object... parameters) { + if(source.is(Showable.class)) ((Showable)source.as(Showable.class)).shower().show(); + source.mouse().move(point); + source.mouse().press(button, modifiers); + source.getEnvironment().getTimeout(BEFORE_DRAG_TIMEOUT.getName()).sleep(); + Point intermediatePoint = new Point(); + int xDistance = target.getScreenBounds().x + targetPoint.x - source.getScreenBounds().x - point.x; + int yDistance = target.getScreenBounds().y + targetPoint.y - source.getScreenBounds().y - point.y; + int startX = point.x + source.getScreenBounds().x; + int startY = point.y + source.getScreenBounds().y; + int endX = startX + xDistance; + int endY = startY + yDistance; + for(int i = 0; i < DND_POINTS + 1; i++) { + intermediatePoint.x = startX + xDistance * i / DND_POINTS - source.getScreenBounds().x; + intermediatePoint.y = startY + yDistance * i / DND_POINTS - source.getScreenBounds().y; + source.mouse().move(intermediatePoint); + source.getEnvironment().getTimeout(IN_DRAG_TIMEOUT.getName()).sleep(); + } + source.mouse().move(new Point(endX - source.getScreenBounds().x, endY - source.getScreenBounds().y)); + //target.mouse().move(targetPoint); + source.getEnvironment().getTimeout(BEFORE_DROP_TIMEOUT.getName()).sleep(); + target.mouse().release(button, modifiers); + } + + @Override + public String toString() { + return "grag'n'drop from " + point + " to " + targetPoint + " of " + target.getClass() + " with mouse button " + button + " with " + modifiers + " modifiers"; + } + + }, button, modifiers); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/KeyboardImpl.java b/core/JemmyAWTInput/src/org/jemmy/input/KeyboardImpl.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/KeyboardImpl.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +import org.jemmy.action.Action; +import org.jemmy.control.Wrap; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Keyboard.KeyboardModifier; +import org.jemmy.interfaces.Keyboard; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.interfaces.Focusable; +import org.jemmy.interfaces.Modifier; + +/** + * KeyDriver + * + * @author Alexandre Iline(alexandre.iline@sun.com) + */ +public class KeyboardImpl implements Keyboard { + + CharBindingMap map; + Environment env; + Wrap target; + RobotDriver robotDriver; + private boolean detached; + /** + * Constructs a KeyRobotDriver object. + * @param target + */ + public KeyboardImpl(Wrap target) { + //TODO: super(target.getEnvironment().getTimeout(RobotDriver.ROBOT_DELAY_TIMEOUT_NAME)); + robotDriver = new RobotDriver(target.getEnvironment()); + this.env = target.getEnvironment(); + this.map = target.getEnvironment().getBindingMap(); + this.target = target; + } + + static { + //TODO: Environment.getEnvironment().setTimeout(new Timeout(RobotDriver.ROBOT_DELAY_TIMEOUT_NAME, 10)); + Environment.getEnvironment().setTimeout(new Timeout(PUSH.getName(), 100)); + Environment.getEnvironment().setBindingMap(new DefaultCharBindingMap()); + } + + private void runAction(Action action) { + if(detached) { + target.getEnvironment().getExecutor().executeDetached(target.getEnvironment(), false, action); + } else { + target.getEnvironment().getExecutor().execute(target.getEnvironment(), false, action); + } + } + + /** + * + * @return Environment + */ + public Environment getEnvironment() { + return env; + } + + /** + * + * @param kbdButton + * @param modifiers + * @param pushTime + */ + public void pushKey(final KeyboardButton kbdButton, final Modifier modifiers[], final Timeout pushTime) { + runAction(new Action() { + public void run(Object... parameters) { + if(target.is(Focusable.class)) target.as(Focusable.class).focuser().focus(); + pressKey(kbdButton, modifiers); + pushTime.sleep(); + releaseKey(kbdButton, modifiers); + } + @Override + public String toString() { + return "push " + kbdButton + " key with " + modifiers + " modifiers"; + } + }); + } + + /** + * + * @param keyChar + * @param pushTime + */ + @Override + public void typeChar(char keyChar, Timeout pushTime) { + pushKey(pushTime, map.getCharKey(keyChar), map.getCharModifiers(keyChar)); + } + + /** + * Press the keyboard key specified by kbdButton preceding with + * pressing of modifier buttons specified by modifiers + * @param kbdButton one of InputEvent.VK_* constants + * @param modifiers combination of InputEvent.*_DOWN_MASK constants + * @see java.awt.event.InputEvent + */ + @Override + public void pressKey(final KeyboardButton kbdButton, final Modifier... modifiers) { + runAction(new Action() { + public void run(Object... parameters) { + robotDriver.pressKey(kbdButton, modifiers); + } + @Override + public String toString() { + return "press " + kbdButton + " key with " + modifiers + " modifiers"; + } + }); + } + + /** + * Release the keyboard key specified by kbdButton and then release + * all the modifier keys specified by modifiers + * @param kbdButton one of InputEvent.VK_* constants + * @param modifiers combination of InputEvent.*_DOWN_MASK constants + * @see java.awt.event.InputEvent + */ + @Override + public void releaseKey(final KeyboardButton kbdButton, final Modifier... modifiers) { + runAction(new Action() { + public void run(Object... parameters) { + robotDriver.releaseKey(kbdButton, modifiers); + } + @Override + public String toString() { + return "press " + kbdButton + " key with " + modifiers + " modifiers"; + } + }); + } + + /** + * + * @param kbdButton + */ + @Override + public void pressKey(KeyboardButton kbdButton) { + pressKey(kbdButton, new Modifier[]{}); + } + + /** + * + * @param kbdButton + */ + @Override + public void releaseKey(KeyboardButton kbdButton) { + releaseKey(kbdButton, new Modifier[]{}); + } + + /** + * + * @param kbdButton + * @param modifiers + */ + @Override + public void pushKey(KeyboardButton kbdButton, Modifier... modifiers) { + pushKey(kbdButton, modifiers, getEnvironment().getTimeout(PUSH.getName())); + } + + /** + * + * @param kbdButton + */ + @Override + public void pushKey(KeyboardButton kbdButton) { + pushKey(kbdButton, new Modifier[]{}); + } + + /** + * + * @param keyChar + */ + @Override + public void typeChar(char keyChar) { + typeChar(keyChar, getEnvironment().getTimeout(PUSH.getName())); + } + + @Override + public Keyboard detached() { + detached = true; + return this; + } + + @Override + public void pushKey(Timeout pushTime, KeyboardButton key, Modifier... modifiers) { + pushKey(key, modifiers, pushTime); + } +} \ No newline at end of file diff --git a/core/JemmyAWTInput/src/org/jemmy/input/MouseImpl.java b/core/JemmyAWTInput/src/org/jemmy/input/MouseImpl.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/MouseImpl.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +import org.jemmy.Rectangle; +import org.jemmy.Point; +import java.util.Arrays; +import org.jemmy.action.Action; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.interfaces.Modifier; +import org.jemmy.interfaces.Mouse; +import static org.jemmy.interfaces.Mouse.CLICK; +import org.jemmy.interfaces.Showable; + +/** + * + * @author shura + */ +public class MouseImpl implements Mouse { + + private Wrap target; + private RobotDriver robotDriver; + private boolean detached = false; + + static { + if (Environment.getEnvironment().getTimeout(CLICK) == null) { + Environment.getEnvironment().setTimeout(MouseImpl.CLICK); + } + } + + /** + * + * @param target + */ + public MouseImpl(Wrap target) { + this.target = target; + this.robotDriver = new RobotDriver(new Timeout("", 10)); + } + + public Mouse detached() { + this.detached = true; + return this; + } + + private void runAction(Action action) { + if (detached) { + target.getEnvironment().getExecutor().executeDetached(target.getEnvironment(), false, action); + } else { + target.getEnvironment().getExecutor().execute(target.getEnvironment(), false, action); + } + } + + /** + * + */ + @Override + public void press() { + press(MouseButtons.BUTTON1); + } + + /** + * + * @param button + */ + @Override + public void press(MouseButton button) { + press(button, new Modifier[]{}); + } + + /** + * + * @param button + * @param modifiers + */ + @Override + public void press(final MouseButton button, final Modifier... modifiers) { + runAction(new Action() { + + public void run(Object... parameters) { + robotDriver.pressMouse(button, modifiers); + } + + @Override + public String toString() { + return "pressing mouse button " + button + " with " + modifiers + " modifiers"; + } + }); + } + + /** + * + */ + public void release() { + release(MouseButtons.BUTTON1); + } + + /** + * + * @param button + */ + @Override + public void release(MouseButton button) { + release(button, new Modifier[]{}); + } + + /** + * + * @param button + * @param modifiers + */ + @Override + public void release(final MouseButton button, final Modifier... modifiers) { + runAction(new Action() { + + public void run(Object... parameters) { + robotDriver.releaseMouse(button, modifiers); + } + + @Override + public String toString() { + return "releasing mouse button " + button + " with " + modifiers + " modifiers"; + } + }); + } + + /** + * + */ + public void move() { + move(target.getClickPoint()); + } + + /** + * + * @param p + */ + public void move(final Point p) { + runAction(new Action() { + + public void run(Object... parameters) { + robotDriver.moveMouse(getAbsolute(target, p)); + } + + @Override + public String toString() { + return "moving mouse to " + p; + } + }); + } + + /** + * + */ + public void click() { + this.click(1); + } + + /** + * + * @param count + */ + public void click(int count) { + this.click(count, null); + } + + /** + * + * @param count + * @param p Point to click, if null {@linkplain Wrap#getClickPoint() + * Wrap.getClickPoint()} method is invoked to get the point to click. + */ + public void click(int count, Point p) { + this.click(count, p, MouseButtons.BUTTON1); + } + + /** + * + * @param count + * @param p Point to click, if null {@linkplain Wrap#getClickPoint() + * Wrap.getClickPoint()} method is invoked to get the point to click. + * @param button + */ + @Override + public void click(int count, Point p, MouseButton button) { + click(count, p, button, new Modifier[] {}); + } + + /** + * + * @param count + * @param p Point to click, if null {@linkplain Wrap#getClickPoint() + * Wrap.getClickPoint()} method is invoked to get the point to click. + * @param button + * @param modifiers + */ + @Override + public void click(final int count, final Point p, final MouseButton button, final Modifier... modifiers) { + runAction(new Action() { + + public void run(Object... parameters) { + if (target.is(Showable.class)) { + target.as(Showable.class).shower().show(); + } + robotDriver.clickMouse(getAbsolute(target, + p == null ? target.getClickPoint() : p), + count, button, target.getEnvironment().getTimeout(CLICK), modifiers); + } + + @Override + public String toString() { + return "clicking " + button + " mouse button " + count + " times at " + p + " with " + Arrays.toString(modifiers) + " modifiers"; + } + }); + } + + static Point getAbsolute(Wrap target, Point p) { + Rectangle screenBounds = target.getScreenBounds(); + return new Point(p.x + screenBounds.x, p.y + screenBounds.y); + } + + private void turn(final Point p, final int amount, final Modifier... modifiers) { + runAction(new Action() { + + public void run(Object... parameters) { + if (target.is(Showable.class)) { + target.as(Showable.class).shower().show(); + } + robotDriver.turnWheel(getAbsolute(target, + p == null ? target.getClickPoint() : p), + amount, modifiers); + } + + @Override + public String toString() { + return "turning wheel to " + amount + " with " + Arrays.toString(modifiers) + " modifiers"; + } + }); + } + + public void turnWheel(Point point, final int amount, Modifier... modifiers) { + turn(point, amount, modifiers); + } + + public void turnWheel(Point point, final int amount) { + turn(point, amount, new Modifier[]{}); + } + + public void turnWheel(final int amount) { + turn(null, amount, new Modifier[]{}); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/RobotDriver.java b/core/JemmyAWTInput/src/org/jemmy/input/RobotDriver.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/RobotDriver.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import org.jemmy.Point; +import java.awt.event.InputEvent; +import java.util.HashMap; +import org.jemmy.Rectangle; +import org.jemmy.env.Timeout; +import org.jemmy.env.Environment; +import org.jemmy.image.Image; +import static org.jemmy.input.AWTRobotInputFactory.*; +import org.jemmy.interfaces.Button; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Keyboard.KeyboardButtons; +import org.jemmy.interfaces.Keyboard.KeyboardModifiers; +import org.jemmy.interfaces.Modifier; +import org.jemmy.interfaces.Mouse.MouseButton; +import org.jemmy.interfaces.Mouse.MouseButtons; +import org.jemmy.interfaces.Mouse.MouseModifiers; + + +/** + * @author Alexandre Iline(alexandre.iline@sun.com), KAM + */ +public class RobotDriver { + + private static boolean haveOldPos = false; + private static int smoothness; + private static double oldX; + private static double oldY; + + static { + Environment.getEnvironment().setTimeout( + new Timeout(ROBOT_DELAY_TIMEOUT_NAME, 10)); + Environment.getEnvironment().setPropertyIfNotSet( + AWTRobotInputFactory.ROBOT_MOUSE_SMOOTHNESS_PROPERTY, + new Integer(Integer.MAX_VALUE).toString()); + smoothness = Integer.parseInt( + (String)Environment.getEnvironment().getProperty( + AWTRobotInputFactory.ROBOT_MOUSE_SMOOTHNESS_PROPERTY)); + } + + /** + * Sets mouse smoothness + * @param mouseSmoothness the maximum distance in pixels between + * mouse positions during movement + * @see #moveMouse(Point) + */ + public static void setMouseSmoothness(int mouseSmoothness) { + smoothness = mouseSmoothness; + } + + /** + * Gets mouse smoothness + * @return the maximum distance in pixels between + * mouse positions during movement + * @see #setMouseSmoothness(int) + * @see #moveMouse(Point) + */ + public static int getMouseSmoothness() { + return smoothness; + } + + /** + * Constructs a RobotDriver object. + * @param autoDelay Time for Robot.setAutoDelay(long) method. + */ + public RobotDriver(Timeout autoDelay) { + RobotExecutor.get().setAutoDelay(autoDelay); + } + + /** + * Constructs a RobotDriver object. + * @param env Environment with ROBOT_DELAY_TIMEOUT_NAME timeout + * @see AWTRobotInputFactory#ROBOT_DELAY_TIMEOUT_NAME + */ + public RobotDriver(Environment env) { + this(env.getTimeout(ROBOT_DELAY_TIMEOUT_NAME)); + } + + /** + * Capture an image of specified rectangular area of screen + * @param screenRect area on screen that will be captured + * @return image of specified rectangular area of screen + */ + public static Image createScreenCapture(Rectangle screenRect) { + return RobotExecutor.get().createScreenCapture(screenRect); + } + + /** + * Presses mouse button specified by mouseButton preceding pressing of + * modifier keys or buttons specified by modifiers + * @param mouseButton One of MouseEvent.BUTTON*_MASK + * @param modifiers Combination of InputEvent.*_DOWN_MASK + * @see java.awt.event.InputEvent + * @see java.awt.event.MouseEvent + */ + public void pressMouse(MouseButton mouseButton, Modifier... modifiers) { + pressModifiers(modifiers); + makeAnOperation("mousePress", + new Object[]{mouseButton}, + new Class[]{MouseButton.class}); + } + + /** + * Releases mouse button specified by mouseButton then releasing + * modifier keys or buttons specified by modifiers + * @param mouseButton One of MouseEvent.BUTTON*_MASK + * @param modifiers Combination of InputEvent.*_DOWN_MASK + * @see java.awt.event.InputEvent + * @see java.awt.event.MouseEvent + */ + public void releaseMouse(MouseButton mouseButton, Modifier... modifiers) { + makeAnOperation("mouseRelease", + new Object[]{mouseButton}, + new Class[]{MouseButton.class}); + releaseModifiers(modifiers); + } + + /** + * Moves mouse to the specified mouse. When previous mouse location is + * remembered mouse moved smoothly between the points according to + * mouse smoothness parameter. Otherwise it jumps to the specified point + * @param point Position on the screen where to move mouse + * @see #setMouseSmoothness(int) + * @see #getMouseSmoothness() + */ + public void moveMouse(Point point) { + double targetX = point.x; + double targetY = point.y; + if (haveOldPos && (oldX != targetX || oldY != targetY)) { + double currX = oldX; + double currY = oldY; + double hyp = Math.sqrt((targetX - currX) * (targetX - currX) + + (targetY - currY) * (targetY - currY)); + double steps = Math.ceil(hyp / Math.min(hyp, smoothness)); + double vx = (targetX - currX) / steps; + double vy = (targetY - currY) / steps; + assert (long)vx * vx + (long)vy * vy <= (long)smoothness * smoothness; + while (Math.round(currX) != Math.round(targetX) || + Math.round(currY) != Math.round(targetY)) { + currX += vx; + currY += vy; + makeAnOperation("mouseMove", new Object[]{ + new Integer((int) Math.round(currX)), + new Integer((int) Math.round(currY))}, + new Class[]{Integer.TYPE, Integer.TYPE}); + } + } else { + makeAnOperation("mouseMove", + new Object[]{new Integer(point.x), new Integer(point.y)}, + new Class[]{Integer.TYPE, Integer.TYPE}); + } + haveOldPos = true; + oldX = targetX; + oldY = targetY; + } + + /** + * Clicks the mouse button specified by mouseButton at the specified point + * specified number of times preceding it by pressing the modifiers key or + * buttons and ending by releasing them. The last click is as long as + * mouseClick timeout + * @param point Screen location where to click mouse + * @param clickCount Number of clicks + * @param mouseButton One of MouseEvent.BUTTON*_MASK + * @param modifiers Combination of InputEvent.*_DOWN_MASK + * @param mouseClick Timeout of the last click + * @see java.awt.event.InputEvent + * @see java.awt.event.MouseEvent + */ + public void clickMouse(Point point, int clickCount, MouseButton mouseButton, Timeout mouseClick, Modifier... modifiers) { + pressModifiers(modifiers); + moveMouse(point); + makeAnOperation("mousePress", new Object[]{mouseButton}, new Class[]{MouseButton.class}); + for (int i = 1; i < clickCount; i++) { + makeAnOperation("mouseRelease", new Object[]{mouseButton}, new Class[]{MouseButton.class}); + makeAnOperation("mousePress", new Object[]{mouseButton}, new Class[]{MouseButton.class}); + } + mouseClick.sleep(); + makeAnOperation("mouseRelease", new Object[]{mouseButton}, new Class[]{MouseButton.class}); + releaseModifiers(modifiers); + } + + /** + * @deprecated Implementation doesn't seem to be correct as it ignores mouseButton and modifiers + * @param point + * @param mouseButton One of MouseEvent.BUTTON*_MASK + * @param modifiers + */ + public void dragMouse(Point point, int mouseButton, int modifiers) { + moveMouse(point); + } + + /** + * Performs drag and drop from startPoint to endPoint using specified + * mouseButton and modifiers to perform it. + * @param startPoint Screen coordinates of drag start point + * @param endPoint Screen coordinates of drag end point + * @param mouseButton One of MouseEvent.BUTTON*_MASK + * @param modifiers Combination of InputEvent.*_DOWN_MASK + * @param before Timeout between pressing mouse at the startPoint and + * mouse move + * @param after Timeout between mouse move to the endPoint and mouse + * release + */ + public void dragNDrop(Point startPoint, Point endPoint, MouseButton mouseButton, Modifier modifiers[], Timeout before, Timeout after) { + moveMouse(startPoint); + pressMouse(mouseButton, modifiers); + before.sleep(); + moveMouse(endPoint); + after.sleep(); + releaseMouse(mouseButton, modifiers); + } + + /** + * Presses a key. + * @param kbdButton Key code (KeyEventVK_* field. + * @param modifiers a combination of InputEvent.*_MASK fields. + */ + public void pressKey(KeyboardButton kbdButton, Modifier... modifiers) { + pressModifiers(modifiers); + makeAnOperation("keyPress", + new Object[]{kbdButton}, + new Class[]{KeyboardButton.class}); + } + + /** + * Releases a key. + * @param kbdButton Key code (KeyEventVK_* field. + * @param modifiers a combination of InputEvent.*_MASK fields. + */ + public void releaseKey(KeyboardButton kbdButton, Modifier... modifiers) { + makeAnOperation("keyRelease", + new Object[]{kbdButton}, + new Class[]{KeyboardButton.class}); + releaseModifiers(modifiers); + } + + /** + * Turns the wheel. + * @param p + * @param amount Either positive or negative + * @param modifiers + */ + public void turnWheel(Point p, int amount, Modifier... modifiers) { + pressModifiers(modifiers); + moveMouse(p); + java.awt.Robot r = null; + makeAnOperation("mouseWheel", + new Object[]{amount}, + new Class[]{Integer.TYPE}); + releaseModifiers(modifiers); + } + + /** + * Performs a single operation. + * @param method a name of java.awt.Robot method. + * @param params method parameters + * @param paramClasses method parameters classes + */ + public void makeAnOperation(final String method, final Object[] params, final Class[] paramClasses) { + RobotExecutor.get().makeAnOperation(method, params, paramClasses); + } + + final static int SHIFT_MASK = InputEvent.SHIFT_DOWN_MASK | InputEvent.SHIFT_MASK; + final static int ALT_GRAPH_MASK = InputEvent.ALT_GRAPH_DOWN_MASK | InputEvent.ALT_GRAPH_MASK; + final static int ALT_MASK = InputEvent.ALT_DOWN_MASK | InputEvent.ALT_MASK; + final static int META_MASK = InputEvent.META_DOWN_MASK | InputEvent.META_MASK; + final static int CTRL_MASK = InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK; + + /** + * Presses modifiers keys by robot. + * @param modifiers a combination of InputEvent.*_MASK fields. + */ + protected void pressModifiers(Modifier... modifiers) { + for (Modifier modifier : modifiers) { // TODO: ALT_GRAPH_MASK? + if (modifier == KeyboardModifiers.ALT_DOWN_MASK) { + pressKey(KeyboardButtons.ALT); + } else if (modifier == KeyboardModifiers.CTRL_DOWN_MASK) { + pressKey(KeyboardButtons.CONTROL); + } else if (modifier == KeyboardModifiers.META_DOWN_MASK) { + pressKey(KeyboardButtons.META); + } else if (modifier == KeyboardModifiers.SHIFT_DOWN_MASK) { + pressKey(KeyboardButtons.SHIFT); + } + } + } + + /** + * Releases modifiers keys by robot. + * @param modifiers a combination of InputEvent.*_MASK fields. + */ + protected void releaseModifiers(Modifier... modifiers) { + for (Modifier modifier : modifiers) { // TODO: ALT_GRAPH_MASK? + if (modifier == KeyboardModifiers.ALT_DOWN_MASK) { + releaseKey(KeyboardButtons.ALT); + } else if (modifier == KeyboardModifiers.CTRL_DOWN_MASK) { + releaseKey(KeyboardButtons.CONTROL); + } else if (modifier == KeyboardModifiers.META_DOWN_MASK) { + releaseKey(KeyboardButtons.META); + } else if (modifier == KeyboardModifiers.SHIFT_DOWN_MASK) { + releaseKey(KeyboardButtons.SHIFT); + } + } + } + /** + * If java.awt.Robot is running in other JVM, it shutdowns that JVM + * @see AWTRobotInputFactory#runInOtherJVM(boolean) + */ + public static void exit() { + RobotExecutor.get().exit(); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/RobotExecutor.java b/core/JemmyAWTInput/src/org/jemmy/input/RobotExecutor.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/RobotExecutor.java @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.awt.EventQueue; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OptionalDataException; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.jemmy.JemmyException; +import org.jemmy.Rectangle; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.image.AWTImage; +import org.jemmy.image.Image; +import org.jemmy.image.PNGDecoder; +import org.jemmy.image.PNGEncoder; +import org.jemmy.timing.State; +import org.jemmy.timing.Waiter; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Mouse.MouseButton; + +/** + * + * @author КАМ + */ +class RobotExecutor { + + private static RobotExecutor instance; + private AWTMap awtMap = null; + + public static RobotExecutor get() { + if (instance == null) { + instance = new RobotExecutor(); + } + return instance; + } + + /** + * A reference to the robot instance. + */ + protected ClassReference robotReference = null; + protected Timeout autoDelay; + private boolean inited = false; + private boolean runInOtherJVM = false; + private boolean ready = false; + private boolean connectionEstablished = false; + private ObjectOutputStream outputStream; + private ObjectInputStream inputStream; + private Socket socket; + private int connectionPort; + private String connectionHost; + public static final int CONNECTION_TIMEOUT = Integer.parseInt( + (String)Environment.getEnvironment().getProperty( + AWTRobotInputFactory.OTHER_VM_CONNECTION_TIMEOUT_PROPERTY, + Integer.toString(60000 * 15))); // 15 min + + public RobotExecutor() { + } + + void setAWTMap(AWTMap awtMap) { + this.awtMap = awtMap; + } + + AWTMap getAWTMap() { + if (awtMap == null) { + awtMap = new AWTMap(); + } + return awtMap; + } + + private void ensureInited() { + if (!inited) { + runInOtherJVM = Boolean.parseBoolean((String)Environment.getEnvironment() + .getProperty(AWTRobotInputFactory.OTHER_VM_PROPERTY, + Boolean.toString(runInOtherJVM))); + inited = true; + } + } + + public Image createScreenCapture(Rectangle screenRect) { + Object result = makeAnOperation("createScreenCapture", new Object[] { + new java.awt.Rectangle(screenRect.x, screenRect.y, screenRect.width, + screenRect.height) }, + new Class[] { java.awt.Rectangle.class }); + if (result.getClass().isAssignableFrom(BufferedImage.class)) { + return new AWTImage(BufferedImage.class.cast(result)); + } else { + throw new JemmyException("Screen capture (" + result + + ") is not a BufferedImage"); + } + } + + public Object makeAnOperation(String method, Object[] params, Class[] paramClasses) { + ensureInited(); + if (runInOtherJVM) { + return makeAnOperationRemotely(method, params, paramClasses); + } else { + return makeAnOperationLocally(method, params, paramClasses); + } + } + + public void exit() { + ensureInited(); + if (runInOtherJVM) { + ensureConnection(); + try { + outputStream.writeObject("exit"); + connectionEstablished = false; + deleteProperties(); + } catch (IOException ex) { + throw new JemmyException("Failed to invoke exit", ex); + } + } + } + + private Object makeAnOperationLocally(String method, Object[] params, Class[] paramClasses) { + if (robotReference == null) { + initRobot(); + } + try { + convert(method, params, paramClasses); + Object result = robotReference.invokeMethod(method, params, paramClasses); + synchronizeRobot(); + return result; + } catch (InvocationTargetException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (IllegalStateException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (NoSuchMethodException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (IllegalAccessException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } + } + + private int convert(Object obj) { + if (MouseButton.class.isAssignableFrom(obj.getClass())) { + return awtMap.convert((MouseButton)obj); + } else if (KeyboardButton.class.isAssignableFrom(obj.getClass())) { + return awtMap.convert((KeyboardButton)obj); + } else { + throw new JemmyException("Unable to recognize object", obj); + } + } + + private static final Set convertables = new HashSet(Arrays.asList(new String[] {"mousePress", "mouseRelease", "keyPress", "keyRelease"})); + + private void convert(String method, Object[] params, Class[] paramClasses) { + if (convertables.contains(method)) + for (int i = 0; i < params.length; i++) { + params[i] = new Integer(convert(params[i])); + paramClasses[i] = Integer.TYPE; + } + } + + public static void main(String[] args) { + System.setProperty("apple.awt.UIElement", "true"); + if (args.length != 0 && args.length != 1) { + System.err.println("Usage: java ... [-D" + + Environment.JEMMY_PROPERTIES_FILE_PROPERTY + "=" + + "<.jemmy.properties full path>]" + + " RobotExecutor [connectionPort]"); + System.exit(-1); + } + if (args.length == 1) { + Environment.getEnvironment().setProperty( + AWTRobotInputFactory.OTHER_VM_PORT_PROPERTY, args[0]); + } + RobotExecutor re = new RobotExecutor(); + try { + re.server(); + } catch (Exception ex) { + ex.printStackTrace(System.err); + System.err.flush(); + System.exit(-1); + } + } + + private File props; + + private void deleteProperties() { + if (props != null) { + props.delete(); + props = null; + } + } + + private void prepareProperties() { + deleteProperties(); + try { + props = File.createTempFile(".jemmy.othervm.", ".properties"); + props.deleteOnExit(); + PrintWriter fw = new PrintWriter(props); + for(Field f : AWTRobotInputFactory.class.getDeclaredFields()) { + if ((f.getModifiers() & Modifier.FINAL) != 0 && + (f.getModifiers() & Modifier.STATIC) != 0 && + f.getType().equals(String.class) && + f.getName().startsWith("OTHER_VM_") && + Environment.getEnvironment().getProperty((String)f.get(null)) != null) { + fw.println(f.get(null) + "=" + Environment.getEnvironment().getProperty((String)f.get(null))); + } + } + fw.close(); + } catch (IllegalArgumentException ex) { + throw new JemmyException("Failed to create temporary properties file: " + props.getAbsolutePath(), ex); + } catch (IllegalAccessException ex) { + throw new JemmyException("Failed to create temporary properties file: " + props.getAbsolutePath(), ex); + } catch (IOException ex) { + throw new JemmyException("Failed to create temporary properties file: " + props.getAbsolutePath(), ex); + } + + } + + private void startServer() { + try { + prepareProperties(); + ProcessBuilder pb = new ProcessBuilder("java", + //"-Xrunjdwp:transport=dt_socket,suspend=y,server=y,address=8000", + "-cp", System.getProperty("java.class.path"), + "-D" + Environment.JEMMY_PROPERTIES_FILE_PROPERTY + + "=" + props.getAbsolutePath(), + RobotExecutor.class.getName(), + Integer.toString(connectionPort)); + // TODO: Improve output +// System.out.println("Starting server"); +// System.out.println("Command: " + pb.command()); +// System.out.flush(); + pb.redirectErrorStream(true); + final Process p = pb.start(); + new Thread() { + + @Override + public void run() { + BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); + while (true) { + try { + String line = br.readLine(); + if (line == null) { + break; + } + System.out.println("SERVER: " + line); + } catch (IOException ex) { + throw new JemmyException("Exception during other JVM output processing", ex); + } + } + } + }.start(); + } catch (IOException ex) { + throw new JemmyException("Failed to start other JVM", ex); + } + } + + public void ensureConnection() { + ensureInited(); + if (runInOtherJVM && !connectionEstablished) { + initClientConnection(); + } + } + + private void initClientConnection() { + connectionHost = (String)Environment.getEnvironment().getProperty( + AWTRobotInputFactory.OTHER_VM_HOST_PROPERTY, "localhost"); + connectionPort = Integer.parseInt((String)Environment.getEnvironment() + .getProperty(AWTRobotInputFactory.OTHER_VM_PORT_PROPERTY, + "53669")); + try { + try { + socket = new Socket(connectionHost, connectionPort); + } catch (IOException ex) { + if ("localhost".equalsIgnoreCase(connectionHost) + || "127.0.0.1".equals(connectionHost)) { + // TODO Improve check for localhost + startServer(); + Environment.getEnvironment().getTimeout(""); + Timeout waitTime = new Timeout("connection wait time", 5 * 60000); + socket = new Waiter(waitTime).ensureState(new State() { + Exception ex; + public Socket reached() { + Socket socket = null; + try { + socket = new Socket(connectionHost, connectionPort); + } catch (UnknownHostException ex1) { + ex = ex1; + } catch (Exception ex1) { + ex = ex1; + } + return socket; + } + + @Override + public String toString() { + if (ex != null) { + // TODO: Provide better mechanics for exception handling + Logger.getLogger(RobotExecutor.class.getName()) + .log(Level.INFO, null, ex); + } + return "Waiting for connection to be established " + + "with other JVM (" + connectionHost + + ":" + connectionPort + ", exception: " + ex + ")"; + } + }); + } else { + throw new JemmyException("Failed to establish socket " + + "connection with other JVM (" + connectionHost + + ":" + connectionPort + ")", ex); + } + } + outputStream = new ObjectOutputStream(socket.getOutputStream()); + inputStream = new ObjectInputStream(socket.getInputStream()); + + connectionEstablished = true; + ready = true; + + System.out.println("Connection established!"); + setAutoDelay(autoDelay); + } catch (IOException ex) { + throw new JemmyException("Failed to establish socket connection " + + "with other JVM (" + connectionHost + ":" + connectionPort + + ")", ex); + } + } + + public synchronized Object getProperty(String name) { + ensureConnection(); + try { + outputStream.writeObject("getProperty"); + outputStream.writeObject(name); + Object result = inputStream.readObject(); + String response = (String)(inputStream.readObject()); + if (!"OK".equals(response)) { + throw new JemmyException("Remote operation didn't succeed"); + } + return result; + } catch (ClassNotFoundException ex) { + throw new JemmyException("Socket communication with other JVM failed", ex); + } catch (OptionalDataException ex) { + throw new JemmyException("Socket communication with other JVM " + + "failed: OptionalDataException eof = " + ex.eof + ", " + + "length = " + ex.length, ex); + } catch (IOException ex) { + throw new JemmyException("Socket communication with other JVM failed", ex); + } + } + + private synchronized Object makeAnOperationRemotely(String method, Object[] params, Class[] paramClasses) { + ensureConnection(); + try { + outputStream.writeObject("makeAnOperation"); + outputStream.writeObject(method); + outputStream.writeObject(params); + outputStream.writeObject(paramClasses); + Object result; + String response = (String)(inputStream.readObject()); + if ("image".equals(response)) { + result = PNGDecoder.decode(inputStream, false); + } else { + if (!"OK".equals(response)) { + throw new JemmyException("Remote operation didn't succeed"); + } + result = inputStream.readObject(); + } + return result; + } catch (ClassNotFoundException ex) { + throw new JemmyException("Socket communication with other JVM failed", ex); + } catch (OptionalDataException ex) { + throw new JemmyException("Socket communication with other JVM " + + "failed: OptionalDataException eof = " + ex.eof + ", " + + "length = " + ex.length, ex); + } catch (IOException ex) { + throw new JemmyException("Socket communication with other JVM failed", ex); + } + } + + private void server() { + System.out.println("Robot ready!"); + System.out.flush(); + ServerSocket sc; + connectionPort = Integer.parseInt((String)Environment.getEnvironment() + .getProperty(AWTRobotInputFactory.OTHER_VM_PORT_PROPERTY, + "53669")); + while(true) { + Thread watchdog = new Thread("RobotExecutor.server watchdog") { + + @Override + public void run() { + try { + Thread.sleep(CONNECTION_TIMEOUT); + System.out.println("Exiting server as there is no " + + "connection for " + CONNECTION_TIMEOUT / 60000.0 + + " minutes"); + System.out.flush(); + System.exit(0); + } catch (InterruptedException ex) { + // Ignoring exception as it is okay + } + } + + }; + watchdog.start(); + System.out.println("Waiting for incoming connection for up to " + + CONNECTION_TIMEOUT / 60000.0 + " minutes"); + try { + sc = new ServerSocket(connectionPort); + socket = sc.accept(); + watchdog.interrupt(); + } catch (IOException ex) { + throw new JemmyException("Can't establish connection with client", ex); + } + System.out.println("Connection established!"); + try { + inputStream = new ObjectInputStream(socket.getInputStream()); + outputStream = new ObjectOutputStream(socket.getOutputStream()); + while(true) { + String command = (String)inputStream.readObject(); + if ("exit".equals(command)) { + System.exit(0); + } + if ("getProperty".equals(command)) { + String property = (String)inputStream.readObject(); + outputStream.writeObject(Environment.getEnvironment().getProperty(property)); + outputStream.writeObject("OK"); + } + if ("makeAnOperation".equals(command)) { + String method = (String)inputStream.readObject(); + Object[] params = (Object[])inputStream.readObject(); + Class[] paramClasses = (Class[])inputStream.readObject(); + Object result = makeAnOperationLocally(method, params, + paramClasses); + if (result instanceof BufferedImage) { + outputStream.writeObject("image"); + BufferedImage image = BufferedImage.class.cast(result); + new PNGEncoder(outputStream, PNGEncoder.COLOR_MODE) + .encode(image, false); + } else { + outputStream.writeObject("OK"); + outputStream.writeObject(result); + } + } + } + } catch (ClassNotFoundException ex) { + throw new JemmyException("Socket communication with other " + + "JVM failed", ex); + } catch (IOException ex) { + Logger.getLogger(RobotExecutor.class.getName()) + .log(Level.SEVERE, null, ex); + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException ex) { + Logger.getLogger(RobotExecutor.class.getName()).log( + Level.SEVERE, "Exception during socket closing", ex); + } + } + if (sc != null) { + try { + sc.close(); + } catch (IOException ex) { + Logger.getLogger(RobotExecutor.class.getName()).log( + Level.SEVERE, "Exception during server socket " + + "closing", ex); + } + } + } + } + } + + private void initRobot() { + // need to init Robot in dispatch thread because it hangs on Linux + // (see http://www.netbeans.org/issues/show_bug.cgi?id=37476) + if (EventQueue.isDispatchThread()) { + doInitRobot(); + } else { + try { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + doInitRobot(); + } + }); + } catch (InterruptedException ex) { + throw new JemmyException("Failed to initialize robot", ex); + } catch (InvocationTargetException ex) { + throw new JemmyException("Failed to initialize robot", ex); + } + } + } + + private void doInitRobot() { + try { + ClassReference robotClassReverence = new ClassReference("java.awt.Robot"); + robotReference = new ClassReference(robotClassReverence.newInstance(null, null)); + if (awtMap == null) { + awtMap = new AWTMap(); + } + setAutoDelay(autoDelay); + ready = true; + } catch (InvocationTargetException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (IllegalStateException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (NoSuchMethodException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (IllegalAccessException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (ClassNotFoundException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } catch (InstantiationException e) { + throw (new JemmyException("Exception during java.awt.Robot accessing", e)); + } + } + + /** + * Calls java.awt.Robot.waitForIdle() method. + */ + protected void synchronizeRobot() { + ensureInited(); + if (!runInOtherJVM) { + // TODO: It looks like this method is rudimentary + if (!EventQueue.isDispatchThread()) { + if (robotReference == null) { + initRobot(); + } + try { + robotReference.invokeMethod("waitForIdle", null, null); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public void setAutoDelay(Timeout autoDelay) { + this.autoDelay = autoDelay; + if (ready) { + makeAnOperation("setAutoDelay", new Object[]{new Integer((int) ((autoDelay != null) ? autoDelay.getValue() : 0))}, new Class[]{Integer.TYPE}); + } + } + + public boolean isRunInOtherJVM() { + ensureInited(); + return runInOtherJVM; + } + + public void setRunInOtherJVM(boolean runInOtherJVM) { + if (inited && this.runInOtherJVM && this.connectionEstablished && !runInOtherJVM) { + shutdownConnection(); + } + this.runInOtherJVM = runInOtherJVM; + inited = true; + ready = false; + } + + private void shutdownConnection() { + try { + outputStream.writeObject("exit"); + socket.close(); + connectionEstablished = false; + } catch (IOException ex) { + throw new JemmyException("Failed to shutdown connection", ex); + } + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/Version.java b/core/JemmyAWTInput/src/org/jemmy/input/Version.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/Version.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + +/** + * + * @author shura + */ +public class Version extends org.jemmy.Version { + + public static final Version VERSION = new Version(); + + /** + * + */ + public Version() { + super(Version.class.getPackage().getName()); + } + /** + * + * @param args + */ + public static void main(String[] args) { + System.out.println("JemmyAWTInput version: " + VERSION.getVersion()); + } +} diff --git a/core/JemmyAWTInput/src/org/jemmy/input/jemmy.properties b/core/JemmyAWTInput/src/org/jemmy/input/jemmy.properties new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/input/jemmy.properties @@ -0,0 +1,3 @@ +version.major=0 +version.minor=9 +version.mini=5 diff --git a/core/JemmyAWTInput/src/org/jemmy/operators/AWTScreen.java b/core/JemmyAWTInput/src/org/jemmy/operators/AWTScreen.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/src/org/jemmy/operators/AWTScreen.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.operators; + + +import java.awt.Toolkit; +import org.jemmy.Rectangle; +import org.jemmy.env.Environment; + +/** + * + * @author shura + */ +public class AWTScreen extends Screen { + + /** + * + */ + public AWTScreen() { + this(Environment.getEnvironment()); + } + + /** + * + * @param env + */ + public AWTScreen(Environment env) { + super(env); + } + + @Override + public Rectangle getScreenBounds() { + java.awt.Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + return new Rectangle(0, 0, size.width, size.height); + } + +} diff --git a/core/JemmyAWTInput/test/TEST.ROOT b/core/JemmyAWTInput/test/TEST.ROOT new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/TEST.ROOT @@ -0,0 +1,2 @@ +TestNG.dirs = . + diff --git a/core/JemmyAWTInput/test/org/jemmy/image/AreaChart_a.png b/core/JemmyAWTInput/test/org/jemmy/image/AreaChart_a.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..970f2fdbc1298ce464e14f1e7701fbd1137f6d08 GIT binary patch literal 5871 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 Q00Vyq?Hp}s`+P3_3x^O*`~Uy| diff --git a/core/JemmyAWTInput/test/org/jemmy/image/AreaChart_a_res.png b/core/JemmyAWTInput/test/org/jemmy/image/AreaChart_a_res.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4f453dc09f30748d856280a8f139617005c33205 GIT binary patch literal 6394 zc$}?Rd0f-SwvVMsl^SfVO9g_J3JM5Tlr?}>>jvTi7-S0yvWOuBh=eUs5vU?Y#juA( z3WzLGS!IiW7#0DQeT@)8gs>z;2!RCNB*9zneZBv@&*Kw5GryTLbI$pmWzNj)GZx3z zuiLf`0)ebQas0?R2xQeH_z(T=8!&Qv7d;#T*;;+#$RR6S@8p089nGqwafN%Lb*-&) zMBatSxu(KBjW_G}S+hTVxH9h4>Mx`TT`yL< z?-cDA%m(p<)6lO>@0j%7WFd5YUwy zLYs;_ygvFw9_46kX$QX>do|e4t+$M3QpLZmz|Dal)wlT*wthB>FsshHD6n5BEX3;Y$@dkyC=sWda%Q*r zCvzi4FdY5fuu#vgHT239iwK5}UGXi3cV_W=v!lrv#tzfpe&`$x6^RH-LRJYq#Gjdw zIuH<0F>W4Z)H;7yj4$etjb-d-+Y3W)XEhL4x}Oz>o_#sLhc_xu*s7%zGM;TFD<8AD zovBTjtXiz$&T|=ZJY19!53HPQ;<_d8p7e`t1sq3Tqw$ut;ThzqND9@_M2EYyNQ#_# zF|0uqPBdxL`%j$CP)B1&Sf-S~WMADaU8TIY^4q*hgt9oryn|zx2_%K#jL8{TNcl91C%m@D}ncC5+gBE2fHaccajq4KYej$WZSzTSm&>;D)=WYrpwAB>pLEtaMCFsxH;tY1C0sQE6~_QC@EJ;JAzFQr$M+iaR#O zTCxW#{01M-n5&Z-UG@!aCtJvW|Gkw+(2MvXhsK2b>A)nV%zgg+Q)Hd@{Z>{`P*Bd2 z_hinW9Sk2x;X|5+>3U+Kr*PH_IuhJ!`wF= zJ$tEV^J&cMcMG-75wp%^f6k*f#5TB`Q(nGzZzd(4`&58BL~L61vxmcxA1Q%%G!mOt zf4t4HF^D&hLEb*IB%|Yafciej9$J5#?SxN90#XRC3CO?!o}epR*dm-MRlNVhO8cO* zdZn#<`?+Lh?1WLEJru&;3yYP3jK)lF@@lZL`*j6#=M2SK5D)*fVgAtSlnmzGq-S#Hp2^?kUI`AFr=J zzAyfJ#Kzl4bQ2Fy_prqXQ{e5r{zdU=2`gs$Iy#Wv+wivysN6J z8e>2EJ(G7of?Jj(=0+^nc#37Z$FoBl*l}Wx>kj7YnRZCrn zcldvBjrJ|m3?ai}RnkIUP2fy* z|F5pW01*J}B!%D*dpwfQYyMD^8 z9Y4ukDA&t3L6+*j`$g7ggU^}LxU%3Yr!YRT$u`8+2PvWnH%isQ6@A>|WQlSxr}`-q zp-wuP)i$&}y5W{H&n}`t)VjAgWBk61!4pk&nxX+X7soqtYMq%&OG{~KX-@^st_8P` z?65zj8ts_%>``WR02{`5`pVd%*9#`nL*(R*H=O)|796@I>}!=7dMaF(%x_HF7V zluP(TG?S$5Xmrlm2bjZkPM^dp6`<}kG_T*RWqct>GB_(8~zc81^bh3bD`M~t3%?2-l8}T?2h7R9&-KEbjYMALu26?zA z3LA~a!fDOZJ5>Ee4^2bs7(RQaAN$1711zRG6c~1FJu#tU^*1~_y_mq|Xt3|vIqTfU z$(%U)OOwFl?z3A(OeGrxa7E2`jPp#m7v7EBZ&beA!0>tIO|%m;6@44_-fl5PVTxm- zMr^8n47XsytG3#*LW1?w_*65hExRR_@7s7I%lM+wX8wpR@b@h$HNrQ>V%CuZh_snq zbJm@?7j*b^qC4KxvI0N;raN==LJ+q8hN32k)_0zH78l%>d8#Q^dwG^Z2oWt{dF4+U z$YWa`9RnfkJ|v;CWIl(t#y2tXi+%LD+1bg#%06N7+mV(aQTOKbfRgH^8__P(31@{xGzI-^4M6TG8Uzvhi53zyNgkY*zP1Xe@h;9 zO&V~FR~k0GH~b8hVvwgzMW0{4z7_I*b7DVO7`l^=@X#Y2drp}F0g+_jo7q69ca5W8 zS_-uzCOg|oRB3NzpdR~7v#v%7qWJF%x`<7OLW~EY>MZW+wqK`d_e4KyPB+DdmIYNX zQ|3mvxUP$N-0#J~i0j=rsZY(@FEQ2KKj#wY$5X0@f#RI1lb#e{tJnLed1Z58q%<9d zZm>2W>MaX(1?Abm?RM~R?7YT+ig;EDQw?OF)Zbv}=VA($u((0&f4J3hw%+(Yl-Ng~ zD1wAJDRHQ%9)!nEQXk^NUbA_Vs%wrx0j61s`@0!?Qhi|P3B$MPZr~Vjub?>-dZTFy z2dJvzy>G)|vrm&~(?Vf_JxDX?{$sg3PwB?bTPO`6Il0e;kZ{&s$c#bh1Q5>;0I<+3 znFX~DCcA=cT;Og3cy9L)0`0jeSvUc1b=q6 zS)ZD1r5@c7?8oL8fc*8=6MxFf1#oA2v>uO7NLZ+r$M4<0e?LR`Z?zT6gsQ5y54hsv z8C<}4c2*51JoIN)$&3rm${}Fv)x<49B`z47oq2TWH!Fdq+m)oT`!a4Pu?uLrM{kPv z$h>K#|8P;X+UZd*%!`|@`UPlu)zJ|Cnie!mExrn>@zXjvn+VdQXo+CScfSpi_ zZV0HfsQ$J1kwoq10f|Au9!R~bKRN^ACt=o8LQ3YFHNG!z+m~O$ZzAS&J`-$2dHV8l zaQ1(cQ$CpMit`n~83*@D1fdEr{>UvwwMY+B8rzkpVdNVf8&Kg9zjWRty89l$4V!4l zkIdGf^sfRduv-B{mwZ8<0X{Kjz3rm!qPlb~qu79$C_Wlt=)29nu_dQZxq$RGBUYh( zcD_BMIkIqtpktjb3nM-XQxjf|rfZv@N*a=UIw5z?Yzy(aG92<{Pv;VWeb*>_DkKO3n*;g{?@BlhdPoci}D9M^)BI zV0u+`vOlkhOxU4!?K;i#qo`Z5*&()9kI+d=ubR<$Q8IvG`>+UDUzCZ#7j&Jy1_bLZ zofl)gr7SxBG+b`AMDR6lOSx=w=OHZ|1}f{xo5l750q4i|>J3JC&8UA7@g%%!>Hgv*qU9D zVwwE}mE7ns=;(~{Qp(JwkwQpYZMR&-d$8|Hy@ZQ{=aqZ#KDQrGHmjXN7gYK#h`dj0-*vvQa zPy_Eb@wnFg*Kh?r6TL;A5fKqt$aUg6_Sto;&6ufWbB^K2!E9X4joR`c{*+ou@C+yt zbhN1F{xdH3TaysM>W@1Qc;k&sPjm>wciMt7miSk6!$^f=)^>{r(u>0IERO70t7^fW z%&8X_v@3R8asNjw1Bug~1E(mUpU81nBAWdKDmfw#2RA2Zr05r=iycSm@CUj2(&ZUA ziGS~^V=P%nmUSb3la{m63CYz;651G0Mb4pqAg8xhxo@%w*s@S-3f!+b`h^y3zlQ*J_E#M>?3WH8yt_f#g89^FfU9j57Dems zwsBAILTcq=m3$z1ZH7lqMQr}^>hX&4)vL#K+A8o%^}0(WQf!`Idh_*l16O`^Q(rMk z4s`)sL>fsN{HXzjN>E^C`=nxh|Kdfv4-6+7FfzVQ1n+WACgT^z?iYH4BfGt??{9jd zeN>KZiIr&LU)DNJ+;wt-P9+SKVU>$sRozk4JOM5AnhKC>nq)S`nl5}w&pMsbS!URb zW(wYUM6_LnWu0^0!$Bl|KB=buS!942etlTpxP2Jp zqqrcjU)3!v|Hb8|LN5#R*$TnmP(FX7Ci%~g$^?d$R#%1<`0RgMS`H@OnUGGID!4c?U* z05&lZ%J|Zp9QJb>OhHMh!*IWhqbXGS6uVuk7}OZ9%?XVI+rKG0TMR-IT|xtb{B|NHRH)hwQXt?Y|B^oR=Ye+oQx4N+#6giAZ~T`xHy zAt5O#DK2iSva+(>w?j>}r)u(Q*4LtHo3j<+(02cwu&Y5sHDREBgPG!kak%;J=g!Br z9Gai;jxKO(U6aQ0dHJCxsj-@IP{|lS*;}-ot8(mKlI@O~yqoW|9V@n4!Ms`+A2F$p zKg!>lof#Q%vAHd~Y@zwXP7)~G%wn~|5+l$<2B$gtXDD^^v+S0Be{mk*EI(NDn$Pa3}}#A;eHxfTFk$I;+H#Ho3G)ZRM{=#A~Y=OUn#* b%#yq-U(sUi#r<0$;P1pA7Dw`bce?pMAZSB! diff --git a/core/JemmyAWTInput/test/org/jemmy/image/AverageDistanceImageComparatorTest.java b/core/JemmyAWTInput/test/org/jemmy/image/AverageDistanceImageComparatorTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/image/AverageDistanceImageComparatorTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import javax.imageio.ImageIO; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.AssertJUnit.assertEquals; + + +/** + * + * @author KAM + */ +public class AverageDistanceImageComparatorTest { + + public AverageDistanceImageComparatorTest() { + } + + static final int NUMBER_OF_IMAGES = 3; + static BufferedImage[] images; + + @BeforeClass + public static void setUpClass() throws Exception { + images = new BufferedImage[NUMBER_OF_IMAGES]; + for(int i = 0; i < NUMBER_OF_IMAGES; i++) { + images[i] = ImageIO.read(AverageDistanceImageComparatorTest.class + .getResource("image" + (i + 1) + ".jpg")); + } + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @BeforeMethod + public void setUp() { + } + + @AfterMethod + public void tearDown() { + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare1() { + System.out.println("compare1"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image1.createGraphics(); + g.setColor(new Color(0.5f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + BufferedImage image2 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image2.createGraphics(); + g.setColor(new Color(0.52f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNull(instance.compare(new AWTImage(image1), new AWTImage(image2))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare2() { + System.out.println("compare2"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image1.createGraphics(); + g.setColor(new Color(0.5f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + BufferedImage image3 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image3.createGraphics(); + g.setColor(new Color(0.51f, 0.51f, 0.51f)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare3Pos() { + System.out.println("compare3Pos"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image1.createGraphics(); + g.setColor(new Color(0.5f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + BufferedImage image3 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image3.createGraphics(); + g.setColor(new Color(0.5f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.setColor(new Color(0.6f, 0.6f, 0.6f)); + g.fillRect(3, 3, 3, 3); + g.dispose(); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare3Neg() { + System.out.println("compare3Neg"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image1.createGraphics(); + g.setColor(new Color(0.5f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + BufferedImage image3 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image3.createGraphics(); + g.setColor(new Color(0.5f, 0.5f, 0.5f)); + g.fillRect(0, 0, 10, 10); + g.setColor(new Color(0.6f, 0.6f, 0.6f)); + g.fillRect(3, 3, 3, 4); + g.dispose(); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNotNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare4() { + System.out.println("compare4"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 11, BufferedImage.TYPE_INT_RGB); + BufferedImage image3 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNotNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare5() { + System.out.println("compare5"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + BufferedImage image3 = new BufferedImage(11, 10, BufferedImage.TYPE_INT_RGB); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNotNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare6() { + System.out.println("compare6"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image1.createGraphics(); + g.setColor(new Color(0, 0, 0)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + BufferedImage image3 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image3.createGraphics(); + g.setColor(new Color(255, 255, 255)); + g.fillRect(0, 0, 10, 10); + g.dispose(); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNotNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + */ + @Test + public void testCompare7() { + System.out.println("compare7"); + Graphics2D g; + + BufferedImage image1 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image1.createGraphics(); + g.setColor(new Color(0.51f, 0.51f, 0.51f)); + g.fillRect(0, 0, 10, 5); + g.setColor(new Color(0.49f, 0.49f, 0.49f)); + g.fillRect(0, 5, 10, 5); + g.dispose(); + + BufferedImage image3 = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); + g = image3.createGraphics(); + g.setColor(new Color(0.49f, 0.49f, 0.49f)); + g.fillRect(0, 5, 10, 5); + g.setColor(new Color(0.51f, 0.51f, 0.51f)); + g.fillRect(0, 0, 10, 5); + g.dispose(); + + AverageDistanceImageComparator instance = new AverageDistanceImageComparator(); + assertNull(instance.compare(new AWTImage(image1), new AWTImage(image3))); + } + + /** + * Test of compare method, of class AverageDistanceImageComparator. + * @throws IOException + */ + @Test + public void testCompare() throws IOException { + System.out.println("compare"); + boolean[][][] expected = { + // NO_RESIZE + { + { true, false, false }, + { false, true, false }, + { false, false, true } + }, + // PROPORTIONAL_RESIZE + { + { true, true, false }, + { true, true, false }, + { false, false, true } + }, + // ARBITRARY_RESIZE + { + { true, true, true }, + { true, true, true }, + { true, true, true } + } + }; + for(int i = 0; i < NUMBER_OF_IMAGES; i++) { + BufferedImage image1 = images[i]; + for(int j = i; j < NUMBER_OF_IMAGES; j++) { + System.out.println("Comparing " + i + " to " + j); + BufferedImage image2 = images[j]; + System.out.println("\nimage " + i + " " + image1.getWidth() + + "x" + image1.getHeight()); + System.out.println("image " + j + " " + image2.getWidth() + "x" + + image2.getHeight()); + for(ResizeImageComparator.ResizeMode resizeMode : + ResizeImageComparator.ResizeMode.values()) { + + System.out.println("\n " + resizeMode); + AWTImage.setComparator(new ResizeImageComparator(resizeMode, + new AverageDistanceImageComparator(0.024))); + Image awtImage1 = new AWTImage(image1); + Image awtImage2 = new AWTImage(image2); + boolean expResult = expected[resizeMode.ordinal()][i][j]; + Image diff = awtImage1.compareTo(awtImage2); + boolean result = diff == null; + if (diff != null) { + diff.save("diff" + i + j + resizeMode + ".png"); + } + assertEquals("Failed comparison for image " + i + " with " + + "image " + j + ", resizeMode = " + resizeMode, + expResult, result); + } + } + } + } + +} \ No newline at end of file diff --git a/core/JemmyAWTInput/test/org/jemmy/image/CheckBox_a_start.png b/core/JemmyAWTInput/test/org/jemmy/image/CheckBox_a_start.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ad66643e7729e255c2cff6b5fe41adeef83e3b89 GIT binary patch literal 663 zc%17D@N?(olHy`uVBq!ia0vp^T|lhG!2~29{|vdyz`zve>EaktaqI1!{n^5WGRHo8 z`vkZ8xlY-};kDdpwS){$iVlmDq3QIvXxFv-v){h|){`qFc<}7Wy}K1GEVp(q{_h}Y zwd3l^M~j@=7sXrH@w2m?WdCJpWRq`npSd!&czez3*n4&b^SkBueLI^|_C5YXkF}|V zk-_g@myYLO%==Qg_4jA~?5WT9&0VoR`a75@-()AGjuT9+O%8|C-k zcU7IQ=zQMtsNOmMd!687*7G-4K7H}~RNCf!@AD(VR$uj68e}!MPwc$zKF0GemdhW> zIM_UYs{+S7^`MOrJz~{u>yOQMxX=Id_cwn2RjQ#9Z5NYXxu~5xeEj_Xig`z~%x3HN zZ*^NNxZlq0bRaFjDWKS^X(E9a*bm10XxH-iP5wj_Zy&73~%3|N!@QdWq}9Fu&oOr_G z!aYD+j$cxS$UDDmjGUp+KZ`y7ku5}|HuIQ{&jk0g&DDRlfu)|WD>)4jnZICmS|Z4i z)|a+GboqC9f_3rF6A%VV$=N8@K+NHNxeKC(|3e`}WJ<|dh&kW2%V+mXKGEw2If&`x z-lUz@drQ_#J^fTc4Cr=Y&(O7Dl?_W`EaktaqI1!^WK*WWsZM* z|7;H1T&1oahnFfD4-JYYZE=$6HMsNrcF^HX@{hKkee--yDbKFbSNCi=Y)V&kAOF|L zVNHIehG%YNC{ytcXbST&+N|NnvG@((2c?b^F_vwM%4 zP4j#+tNytkgwAswt#7-R|3}qLc;4j8{)?Y$&p!KW-~Usa+!hCFO}+H8WJ^WbPuYrN z*XxBBbMn`%4b_@@)kRCJ`{JXEjT3)pUsxaeukLp{yXmA=aqHc?SN8eLY1g;^|2T5u zoy*~C!^HWvMy=f@#J%`Q=G5hSt%*MlD5$q^ep*o(=k@*UA(qN3Hu_5{Hyb$0nVv6x z*%vuOp?}u%nI`FCY9|&yt5mvc#x49L@UxD$aav-NP1d~Qm$rbFq1iXj4@c`LCCTUu&$lz zz7TT@GR*QIBHFR05H&uZyTDekr_Y)Wak<518HoNfUenJ&M2h5~s!voN0b5(_ttG-G z&RaCU8sta*eS!ikjn&gj9|jc5S3Qci0y#M?$g(a@A#*DG_4RkGS1U{`+9|U>e(R~` z)7>+D)aIZ6J#Fgg)bz<-ODifjhRVhL&CQmJo0DHI_wRP+wzmwrX5C6_w>~HWCOrmE LS3j3^P6 wrap = new ScreenRectangle(Environment.getEnvironment(), new Rectangle(0, 0, 220, 220)); + AWTImage img = (AWTImage) new AWTRobotCapturer().capture(wrap, new Rectangle(0, 0, 200, 200)); + File imgFile = new File(System.getProperty("user.dir") + File.separator + "out.png"); + new PNGSaver(new FileOutputStream(imgFile), PNGSaver.COLOR_MODE).encode(img); + AWTImage loaded = new AWTImage(new PNGImageLoader().load(new FileInputStream(imgFile))); + new PNGSaver(new FileOutputStream(new File(System.getProperty("user.dir") + File.separator + "loaded.png")), PNGSaver.COLOR_MODE).encode(loaded); + AWTImage diff = (AWTImage) img.compareTo(loaded); + if(diff != null) { + new PNGSaver(new FileOutputStream(new File(System.getProperty("user.dir") + File.separator + "diff.png")), PNGSaver.COLOR_MODE).encode(diff); + } + assertNull(diff); + } +} diff --git a/core/JemmyAWTInput/test/org/jemmy/image/ScreenAreaImageTest.java b/core/JemmyAWTInput/test/org/jemmy/image/ScreenAreaImageTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/image/ScreenAreaImageTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + +import java.awt.EventQueue; +import java.awt.Frame; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.jemmy.Rectangle; +import org.jemmy.TimeoutExpiredException; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.operators.AWTScreen; +import org.jemmy.operators.Screen; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.fail; + +/** + * + * @author shura + */ +public class ScreenAreaImageTest { + + public ScreenAreaImageTest() { + } + + static ImageLoader loader; + + @BeforeClass + public static void setUpClass() throws Exception { + Screen.setSCREEN(new AWTScreen()); + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + File tmpDump = null; + File tmpDumpPart = null; + Wrap area; + Frame frm; + Rectangle part = new Rectangle(10, 10, 80, 80); + + @BeforeMethod + public void setUp() throws IOException, InterruptedException { + Environment.getEnvironment().setImageCapturer(new AWTRobotCapturer()); + loader = new FilesystemImageLoader(); + frm = new Frame("some frame"); + frm.setVisible(true); + frm.setSize(100, 100); + frm.setLocation(100, 100); + area = new Wrap(Environment.getEnvironment(), "screen area") { + + @Override + public Rectangle getScreenBounds() { + return new Rectangle(100, 100, 100, 100); + } + }; + // Added timeout to prevent failures due to Windows Vista Visual Effects + Thread.sleep(1000); + tmpDump = File.createTempFile("screen", ".png"); + tmpDumpPart = File.createTempFile("screenPart", ".png"); + area.getScreenImage().save(tmpDump.getAbsolutePath()); + area.getScreenImage(part).save(tmpDumpPart.getAbsolutePath()); + try { + Thread.sleep(2000); + } catch (InterruptedException ex) { + Logger.getLogger(ScreenImageTest.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @AfterMethod + public void tearDown() { + frm.setVisible(false); + } + + @Test + public void compareFull() { + try { + area.waitImage(loader.load(tmpDump.getAbsolutePath()), null, null); + } catch(TimeoutExpiredException e) { + fail(e.getLocalizedMessage()); + throw e; + } + } + + @Test + public void comparePart() { + try { + area.waitImage(loader.load(tmpDumpPart.getAbsolutePath()), part, null, null); + } catch(TimeoutExpiredException e) { + fail(e.getLocalizedMessage()); + throw e; + } + } + + @Test + public void compareNegative() throws InterruptedException, InvocationTargetException { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm.setVisible(false); + } + }); + Thread.sleep(100); + try { + area.waitImage(loader.load(tmpDump.getAbsolutePath()), null, null); + // TODO: Test unstable. Sometimes passes + fail(); + } catch(TimeoutExpiredException e) { + } + } +} diff --git a/core/JemmyAWTInput/test/org/jemmy/image/ScreenImageTest.java b/core/JemmyAWTInput/test/org/jemmy/image/ScreenImageTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/image/ScreenImageTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image; + + +import java.awt.EventQueue; +import java.awt.Frame; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import org.jemmy.TimeoutExpiredException; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.operators.AWTScreen; +import org.jemmy.operators.Screen; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; +import static org.testng.AssertJUnit.assertEquals; + +/** + * + * @author shura + */ +public class ScreenImageTest { + public static final String GOLDEN = "golden.png"; + + public ScreenImageTest() { + } + + static FilesystemImageLoader loader; + + @BeforeClass + public static void setUpClass() throws Exception { + Environment.getEnvironment().setImageCapturer(new AWTRobotCapturer()); + File workdir = new File(System.getProperty("user.dir") + File.separator + + "build" + File.separator + + "test" + File.separator + + "results"); + workdir.mkdirs(); + loader = new FilesystemImageLoader(); + loader.setImageRoot(workdir); + AWTImage.setImageRoot(workdir); + Screen.setSCREEN(new AWTScreen()); + Screen.SCREEN.getEnvironment().setTimeout(new Timeout(Wrap.WAIT_STATE_TIMEOUT.getName(), 10000)); + System.out.println("Saving data to " + AWTImage.getImageRoot().getAbsolutePath()); + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @BeforeMethod + public void setUp() throws IOException { + Screen.SCREEN.getScreenImage().save(GOLDEN); + } + + @AfterMethod + public void tearDown() { + } + + /** + * This test usually fails because it compares the whole screen where + * changes usually happen between screenshots. + */ + @Test + public void compareFull() { + try { + Screen.SCREEN.waitImage(loader.load(GOLDEN), "positive.png", "positive_diff.png"); + } catch(TimeoutExpiredException e) { + e.printStackTrace(); + fail("compareFull failed, see positive.png and positive_diff.png for details"); + } + } + + @Test + public void compareNegative() throws InterruptedException, InvocationTargetException { + final Frame frm = new Frame("some frame"); + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm.setSize(100, 100); + frm.setVisible(true); + } + }); + //note - if you will be running test from netbeans you would also get a difference + //from JUnit execution progress. Which is fine :) + try { + Screen.SCREEN.waitImage(loader.load(GOLDEN), "negative.png", "negative_diff.png"); + fail("compareFull failed, see negative.png and negative_diff.png for details"); + } catch(TimeoutExpiredException e) { + } + frm.setVisible(false); + assertTrue(new File(AWTImage.getImageRoot(), "negative_diff.png").exists()); + AWTImage res = (AWTImage) loader.load("negative.png"); + AWTImage diff = (AWTImage) loader.load("negative_diff.png"); + assertEquals(res.getTheImage().getWidth(), diff.getTheImage().getWidth()); + assertEquals(res.getTheImage().getHeight(), diff.getTheImage().getHeight()); + } + +} diff --git a/core/JemmyAWTInput/test/org/jemmy/image/comparator/ComparatorTest.java b/core/JemmyAWTInput/test/org/jemmy/image/comparator/ComparatorTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/image/comparator/ComparatorTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.image.comparator; + +import java.awt.Color; +import java.io.File; +import java.awt.image.BufferedImage; +import java.io.FileNotFoundException; +import java.io.IOException; +import org.jemmy.image.*; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; + +/** + * + * @author shura + */ +public class ComparatorTest { + + public ComparatorTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @BeforeMethod + public void setUp() { + } + + @AfterMethod + public void tearDown() { + } + + // TODO add test methods here. + // The methods must be annotated with annotation @Test. For example: + // + @Test + public void average() { + BufferedImage golden = + PNGDecoder.decode(getClass().getClassLoader(). + getResourceAsStream("org/jemmy/image/comparator/golden-averagedistance.png"), true); + BufferedImage actual = + PNGDecoder.decode(getClass().getClassLoader(). + getResourceAsStream("org/jemmy/image/comparator/actual-averagedistance.png"), true); + assertNotNull(new AverageDistanceImageComparator(.001).compare(new AWTImage(golden), new AWTImage(actual))); + } + + @Test + public void strict() { + BufferedImage golden = + PNGDecoder.decode(getClass().getClassLoader(). + getResourceAsStream("org/jemmy/image/comparator/golden-strict.png"), true); + BufferedImage actual = + PNGDecoder.decode(getClass().getClassLoader(). + getResourceAsStream("org/jemmy/image/comparator/actual-strict.png"), true); + assertNotNull(new StrictImageComparator().compare(new AWTImage(golden), new AWTImage(actual))); + } + + @Test + public void strict_hyperlink() throws FileNotFoundException, IOException { + BufferedImage golden = + PNGDecoder.decode(getClass().getClassLoader(). + getResourceAsStream("org/jemmy/image/comparator/golden-hyperlink.png"), true); + BufferedImage actual = + PNGDecoder.decode(getClass().getClassLoader(). + getResourceAsStream("org/jemmy/image/comparator/actual-hyperlink.png"), true); + BufferedImage diff = ((AWTImage)new StrictImageComparator().compare(new AWTImage(golden), new AWTImage(actual))).getTheImage(); + //assertNull(diff); + //barbashov sucks! + //he submits similar images + new PNGEncoder(new File("/tmp/aaa.png")).encode(diff); + assertNotNull(diff); + for (int x = 0; x < diff.getWidth(); x++) { + for (int y = 0; y < diff.getHeight(); y++) { + /* + if(new Color(golden.getRGB(x, y)).getAlpha() != 255) { + System.out.println("Haha!"); + } + if(new Color(actual.getRGB(x, y)).getAlpha() != 255) { + System.out.println("Haha!"); + } + * + */ + Color color = new Color(diff.getRGB(x, y)); + if (color.getRed() != 0 || color.getGreen() != 0 || color.getBlue() != 0) { + return; + } + } + } + fail("There got to be non black pixels."); + } +} diff --git a/core/JemmyAWTInput/test/org/jemmy/image/comparator/actual-averagedistance.png b/core/JemmyAWTInput/test/org/jemmy/image/comparator/actual-averagedistance.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..085e3e393b62512f7abde232de7cd86efdc68dbf GIT binary patch literal 1354 zc%17D@N?(olHy`uVBq!ia0y~yVAKI&4kn<;H+e}V1_o9^PZ!6Kid%2*9`s5MmpSlI zVpD){6Zd@u4VO&?x#CJ3QYYMaNNm+TE;U?$sy}j@AOJ<$@9JMxFkN@*e zlj*O;yYFUw%_uM5laQS9XVc9$SF)ZzEz+Ma|Ewr;EpU9rUN^Z8r1 ze*JR&@@Y%d+b)4+E;qENKUS8}(b3V1JL(>x^UakbSn13=w#=1O%g97-&=0}qHB ze<)Ed>1L9%bTWVakkjSGM$?KX9G5Q&-+XxL>jd^4%G0_9l$2s(WB-0TR=4+GTIS15 zA*ZT#&6;bwI%HmcZm)Qau~A**`uF;}do`!(ZT^#%zFuqU=Cx~|KW2USpt`#HoY4`t zi2v*M{mI$+r8x6hhT3-}^ZPT`|B1MH@Af|JLx2DNdVT%$^yy3c<ytj9@z4huX zw{I)8u;;t4Z@<2Fe{}2g)(iKc(0`qv&*cAwEF_kd4)7rWhoxfKo4|C%}Mf3MuUWap(g$@+M!@26wFE`N27-@5#- zN!0SoHNVT2Ue`|lxwqc_`rEL>&mHW0+4wsQTs~>tI(VY_+Wa|lEKV21xA-x32)J+z vBvxV>Xe`Jyz_^KnaR9L+mPGJp|3B7HmX=w!JdI8Q%SZ-KS3j3^P6BlhX6$<>lwko%{E?`MWo7Y$`u3(Pv<|F|%>&(r$76Ygex> zeY|t$&Q+^cIV}u0C3ySRtxF=p5ex?oOGo(n`gV7BYin!ojLG}{?r!z>cfQvbeS34W z7>F%iyno+sJLO$KPSLL#e}Dhd($d=6+HJo~4Gb>qNh>HY$eU+Xs#O;4?d^RhFoBW5 z+H6BsR#uWUFF*hL+uQZ?#JE~-_!kzuxUleWJO6RVoRu|p^C5=)_|0|g&>^SX+_zh^ zuTPsct?cbB(ez`PmoHyVd^=^)0VamBiiERg&zhQ;eEItIsGL{)>^XCCa&u#2W7n=) zbxJQm->mZV0Vye|uFF=}L&C!PdV5os%g$9|VJNFhIO_PP^2>{h$BrFaxpL)(ZC0Vt z(X-D#|Nj2|{I>x)pQbgMT3Fn;c(L&DHi?L@U%xVOGgL${Y42{^yL$EN?c2>|%gf8n zO-*I3%W{Gf?%G;%ule@v+r)_zzg(R;bLOj8uX4U-S61%aw5cdD(Glone%FMh>MX@O zV~UH5%jUN(T)42StLuj6Y^K-mPDUmiz1QVi_Tj<7lDhc#_&LJq$3A$OKVHqwCzFwr zBLWQnKHfFG)22`NzQ6OvldD&+_I@wvc-(6o9v0@c=P@(G0Y45M1_l-}h6Z4O2t+V2 zfVhpU3>%V}UVq7bx$UEUKO=(|m(F}9h68_j)_nRIyzhr*PpoF?rzbHHJAXR-w^8L{ znBtufx>ru>9`D!u_UFeA{aNq2T-Hu}-XDcMKT=n=uYJkioo(JOzh5YAJ+}_Sf;mjt zyLT^q^y61h@V}spN_&yKT-&v^7CnC|_}kAit$zF1!0L513xnLL16}ie8Swm{z{953 z{!I8}a>3jE9{WusSKapr+Zp%I>gJibo??6DYV~Wr-2K&8n!lF+(U~3jViC6SYv14a zJ9E7dgU0Lwx4xO*5Qs2rt~%e|df)l&?1#6!B7P~qc@-aj`PL2r(fR#q>(pjWl~wur z$Hk)ZrC&lT`;MlkB8m(F%NoC)+G+5mHpp-8e6{{l*AMmDo0ngD^L5FhzWUiQ`&BnE zH2jZGu>BF-(rmmiS>@%bFB4B$W&V18p+w2jQbIme6d0DN;t|Z#4=4Y8((>Sp{>j$h zmz4*6u6h3Set7d@O2OA-a}QKKx%y;E(w8$c&o-8>4?oV#kW-zI`&(Lu-=AN#J7{C? zrrRc8YA44$oM%1x$;D*{X6=@Mp`Ie7U?WNqt)p^q#5bu?IVj?r)Tk7Quwxp6&L%Yl5}izM*wr&i YtQ9&KHswh$u+_)l>FVdQ&MBb@03)0cdH?_b diff --git a/core/JemmyAWTInput/test/org/jemmy/image/comparator/actual-strict.png b/core/JemmyAWTInput/test/org/jemmy/image/comparator/actual-strict.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fc66202bd77e8547f0b4acce14ff2f2ddc131835 GIT binary patch literal 2236 zc$~#nYc!kL8cwPD>J~9kLaf#3%(HlWKb!|D-no*Z9?t%$lr|xOTxV2Ix z(>5(ZY0*K_ejS%qtDTAxl;)^Q^P@XPxIwUR%$Bo`Nf5H{*2 zT{U>VG&Z2f)0IQIf#(a-Mvvqdu7<6pM9JEjd-;!L^GTTm{HN2cYZoW*LB>#BMf@}d zacXMDhX1^nQdN1;LFZ&MP3FZHWjZcju6coYJ%c%id77Ie+0nzFVmHyW zL_xbE>;pZT3tbIG{1kSrg-#3Cxuuzs#vA<<#{`l}F=lhvjuduy;KacGVo##n;sMH> z_1ZFJ=$Gy?3qhPna2wb^G{z3ow+>bho0Fa}j5uq18V{Fs&NQ1tefL{LC4WO`h|E&1 zh(q8=bI5C0#`X5s6v9-W_2&^TU_bww-P%5A9}dxxO1V4}*!-V18o~EvVC1ixF%t#Lllay*P&GCOZ{|&`j!%^%Nf< zIJ7C?kiGk`buXN(2&tnAtl75<^`9!cM*FC2u7yyDdU@mb`s`*j~^mT?uuFa!h54iRoXtW}4s^ zf6kDyA5cv3&@xXvBEySM@)vb(-59sgJ!|V_!%<3^Ux=AE2;`p?Ca;zzdu4YY~WZirP zp#MdO7=uaB*i8ky$sGFjqDGuxlJ(b$i;@-|$~C||m)ZWq^Y%vfiVKl!S24*7pK%c% zK7+(oKJ1%gD(Z0nvnU$>s}zzYlq+v^)0FfL^yERuII1^?)V3DazHFYT0fr!2PUC7< z?tZJ8Q=F%-+?S!f@ssl)DpTZuc{HI zWrAidd3(L9-~;3{UsB@1M?LZ>98Cr9w@hX^5Sk_W|AZk$86m)&{4cLP?2nxsW04g zaZAo!y!bfY!LT1S`(-y7jVm;1rvSS`Zw7q5N0Ue@?_PTPqd1pNMQD6aXVWs^(FnQlW1Lk zMIlK(>&RQy*OcW{7#Z(>oATQdGdWSzlo!2%TUG%B-G?0wFmHZ;Z%?69rNF@L^9;tw z1){S_-5XvRO*<{zc6+`gK+XZ&BoF%?skbBCanbbvIiI^OUgj146oOdHZ1+t^W@cFO zld7$*1EVpCwJS?AsvqiYbSYpJw_|hu2k-$!Hwdh1>lj~g(ItVH_Kxx6D78J%xVXnZ z@*2sd4UomAbOxKNIYPWsr()DW7~*w!e>Yhh2*r+N+$*07*HL+djjS^(5%eBMf%(*g z8v)BU3XuD7#+wfbc7v>py<9 z>qL5G+2o@gy)%xTyWCXqdG+em%ms1UK|Aly@!?y0?X&W~klz8jBnc?M-MIM;S}t1OXs%fi};{%=;HZ{JDK{G#X1BgDc3xd`0=HpqUS?v@`L!}&Vo+G zEgz@quRKt7rtR><<%wR6#|7{6yK?XfuIpB^JruhB`tMub{Tp`Zxd4TxRnC)pxb5=G zn%7I*XPhq*baHM9own3nqI>gf9os_@I!*$L7P72=44CuHou+F%I3&0$fhuM6{ybMnuuX-9qmCqO+YaB% zG4s*>aDK-5*fs6TIJ%SO$|bJf9<{dWrQo{jdC|?wI6m+3>ECeqX5RL>UpP)*;=OZ0 zByn2kOTlB0R-L+}+;dckMX>2|-~oQ)4`s?FolJT%3%kD-{N&&V>g{%~C}1{^(K>Xz z->v(7yX(etpEuPRMTtrq_Rf7S*DDtP^V;i0I-ggaN;=}^lf2x0@j-2+9uKjTcGoUl zKR)~Z-Mc@hJY33A%c1}b*@46z0t1a*I0hOku?!>@{KetWqTqy#AN>Et8??7oLW$+h RBw!iE;OXk;vd$@?2>=cMrqKWZ diff --git a/core/JemmyAWTInput/test/org/jemmy/image/comparator/golden-hyperlink.png b/core/JemmyAWTInput/test/org/jemmy/image/comparator/golden-hyperlink.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..38b146e8ed0544b7c5c715f4747166e6dad3ddc1 GIT binary patch literal 2586 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 diff --git a/core/JemmyAWTInput/test/org/jemmy/image/image2.jpg b/core/JemmyAWTInput/test/org/jemmy/image/image2.jpg new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1df6c80ee50d45e7be53e734fdd42b46d6dc3733 GIT binary patch literal 13809 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 OZ|x;|pgTg1AN?<3E_-SK diff --git a/core/JemmyAWTInput/test/org/jemmy/image/test_image.png b/core/JemmyAWTInput/test/org/jemmy/image/test_image.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1c89d15ed7c67c5ae225d5645d82e21163d80c09 GIT binary patch literal 716 zc$@*u0yF)IP)w@I2vP9zrxk&843(#*wSlV(ncB+12P{o0|nW-y1Dw275z4HmWI zfI@QFBHI1bv-`H**ZZ1wPWT+&p1wZc=lOo#=ktBu$H}jw!O42PE*6W|*Vi9d;PH6w z@9!6j#okMWPM^=$ZnuB3px5hx0JVEB75mv}G^W$(>+9BTG-p2bMbh* z|E^-ATNF)#lY?bPEyD`t%*;%vM zB*EcusL^Ny0s)?*(TD^uE-upPGzOti$Zof9NQFheZ~_FT)XU4ucsz~+9Ad7nu1Gal z1Cd&-c6xeBE&TBCfWhqRTp_R+`Xrf5eq;ba+>=n^Tdoj$ ya503zQmIs)1({3+ghRL(9?^CGi2lSKkJR6U7Qs8H?*GUD0000000pP0ssI28ac)+0007PNkl+z+E(A%*0=Ur>+JLN_tEimyWLu?RyZ8~CV)^VRIk_1=d)N9 zLS8Hun@*>%0$3~-AV8#872@vR zvl$4WeYlrIBKcYn4973;d8cr*+5BPx1aT`weg0$u1X|&4w=+0*h|9tI=rm zdVRm&C&uM+snu##DpkAPW;l{Xv)T0f{nzW2udq}q!CkFZ4~Iiy+-|o;~`0RpYi>2z{IbJ2LcUbHkKP%4$gXt&!D87w-Tj^T)B zE|&`igY+3D5(y0aa=DDWa64o&87#8dti$0TM#zK6%jM$nc#zh+GaQe{KbQc4Rw$Rt z>1eq0bH@Uj6|K%f<(p@jwpe+`<= zX2Yo9TZ9}toepI1Ck-6q@i^+!U@(xOLS~i8WP%Y-Zqi`tpxa49qB-9t4c_=@V&pHM z&p%Uu5w{uW7x5b(x3)+mQYaJ}0w@-XAc*+Z_V_pN$NzeIc!mD}hGjy}Y%kh?00000 LNkvXXu0mjf>`^+| diff --git a/core/JemmyAWTInput/test/org/jemmy/image/tool/data/golden/mess.png b/core/JemmyAWTInput/test/org/jemmy/image/tool/data/golden/mess.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4f6b329cea852010ef903b3d94f3d8745b930d0 GIT binary patch literal 663 zc$@*30%-k-P)DOSvzN^OA#D5;#R3_3af#ay#)e2MQMXZPhzMpdMypeGX`9fb7G3ulj+0|f zGR|@q=SeJlUWfdi?|sjAF5aoUc`kT;S3sxJF`LcT*Vm7akGtLOp9~iHD3wZCt=3#F z_d(^9_j~gBJQ-eHT{)f3(P*S3c|p?GbBxDh4Dm~g#iHGA*J`zoGOE>TtJPXAm*sNV zZnxL#^-n6NG#U+JAs>x z_jQN?b~X0tArOAML7iQQl@ke<|tAZc)f2D-ew)a(fa z0^&L~7#=8tjYBbCS*RTOafC6$1S5Lx@!TM!;S=~Fd97AUS4qQvmef5G3WcaRl+t?Q zIyGzr&IJ=?P|R0;s>oQaR`dCsd`O?6Ck>y#MM;T7LK^eDGX1{N`3%Ue`T=9N6BQ879I|V xPbw0=?Lp7O!^7?E?V)Lk#iEkr1xYGzod4y(B@a>=r+5GW002ovPDHLkV1ngaJ1_tM diff --git a/core/JemmyAWTInput/test/org/jemmy/image/tool/data/golden/missed.png b/core/JemmyAWTInput/test/org/jemmy/image/tool/data/golden/missed.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1a88f7f59467777469b9e49788a76e3fd0b85148 GIT binary patch literal 1053 zc$@(p1mgRNP);i8|z#l6WB{Q6eQt#DhqZluJaC++H}!xW|yYcyPbfk&N4mO9>$* zI_G27X*KrVGn_FzIQ#c7t^HeT|Mps+^;_TH-sACGx6d~Z9l@7UzQ4bpnwn~EZZ0b; zD=8^yY-}7KAHTV|(HXgo^3KjqZEbCTfB)Xz-u3nM-QC^M(b3S*P<3_n*4CDe$qkf2 zTvJnHwOY^5&reQHtTv;;<-x%LUG4>KFc=h#?@t+_sH>~n-Q6X5`Th0UaFX3*^zH4f z8W!f|UNi@UqK$ypK>Sf!<<;o;$- zp`pda#Y;;|o12^DHH)-ZERLce-(g{4pFvqP4i68#y}iGl@&^OHqD;A4US8hV*x;9< z_ruN#3kyZp+S(e$&`A^pg`l7yqtO@;5HL76C_I;!mnkVJBxY=EtXeTnPfz3G;^^h~ z?Cea@yRx#9o}TUmsKJ3biCkY_@9*#L>+36L_)8O<+MMVjx zkB<+#`1JJD+1aU)%TQ+Y%*+gTswv~7YkPZJld`a^tgMHJ2kA%0v$M0AJqa$K#3nNX zM`-9}&0^1JD|&GPB{4D41NT?3hXK8=H}w03($@fgLFUyuCA`6pS|ho>JrsbK8Z6cR{JJpDL#s|5#fWBH6y!5 z`SI~lF$T}*=qSWuVq&zbUu$dY{QNv`#{&Zcd3kx1K1gvcdV70u&d$!Zx3@c~e&~t4 zVxSV#D^|tk$uUVuNz_;xb8~Z=iXnEfrKLqmuNezf*KOvY&Od=!NI`@ zl)CLavTKw%sg;$L%!*GodBk}NpwUC5Lp_a`H#rp3Vl%VK*dNlFp!V6Sel}QTva?ly zrmzrJj@jSekBp2Y3(CvO)s#^*P8`{qkdRi9;o`O-!KI`8ik{q(Ubs#ql4Gn0BS}1hW|M}D@|Ht+R X#2yoCGtekb00000NkvXXu0mjffUF4L diff --git a/core/JemmyAWTInput/test/org/jemmy/image/tool/data/golden/pass.png b/core/JemmyAWTInput/test/org/jemmy/image/tool/data/golden/pass.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..09d0573e0c16bc1ea1c362732c4301e85e2575c0 GIT binary patch literal 905 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 Qb^rhX07*qoM6N<$g3>5CU;qFB diff --git a/core/JemmyAWTInput/test/org/jemmy/image/tool/data/results/pass.png b/core/JemmyAWTInput/test/org/jemmy/image/tool/data/results/pass.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..09d0573e0c16bc1ea1c362732c4301e85e2575c0 GIT binary patch literal 905 zc$@)=19tq0P)000pP0ssI28ac)+0009}NklE~P+Ck`2lyE%m6%*nYk=ey@IGZ`<3r3YPi|I98a z7?+GF84tUXuUdU?9L{%XsIfh&zC@+Ck#jkURzsRI2=AtBm4XNH~>i|{l5%I zNcMuk;OXh<;o;#AmHdV@M>#oDOZ?r z&=MXWAD5PvW@ct8L7=Rzt`-y&SS*(9?QLPC&I=0*b8~b3e!tE_q0GMg!m{yE73GGG@PBC5kXE)jxbhMRuUPOqM{<5g&bWjm(%Ig z=CHG~Gnq`X>FkSgg@J(ql)=Hls;Vks1P{nWB2iyo&$g!IaA#*H5C{+oR6|2UZnqny zrltlkfEO1RanKT)&1P*h%JwU1!ObKKTduRPy}iA!uTQ5+fmC0CQ(;tJZE9+Y@*(H9 ztCXZIFE5Xbi~we{*+c@FG*m2QGdnxGxw-kDgj!vRMt>n;aSI(n_D-n_HDPvkw&L8| z+e=3<8gCO)hd!SVPBC3X0%^a6g#~tHa&nSPY6&R@{=JhBH?~RbmTn<2G&VMB31?p5(U3If&7|RpPvcRF zK(4H;gk(VhZ&((O#{;B+pZJzfzLu62NSK^UOG{#+qHyYwh5p3B`q8j&ZBKfVK$1y^ z!_nN_{6Q8JPL(%r@Sb;Hzj^<;G$0{gPmg<~r(dLJ>3=x9a7lsR#{7Evo0QgK*A1rg f^F + */ +public class AWTMapTest { + + public AWTMapTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @BeforeMethod + public void setUp() { + } + + @AfterMethod + public void tearDown() { + } + + private final KeyboardButton [] jemmyKeyboardButtons = new KeyboardButton [] { A, ADD, D5, F5, NUMPAD5, OPEN_BRACKET }; + private final int [] awtKeyboardButtons = new int [] { VK_A, VK_ADD, VK_5, VK_F5, VK_NUMPAD5, VK_OPEN_BRACKET }; + /** + * Test of convert method, of class AWTMap. + */ + @Test + public void testConvert_KeyboardKeyboardButton() { + System.out.println("convert"); + for(int i = 0; i < jemmyKeyboardButtons.length; i++) { + int result = new AWTMap().convert(jemmyKeyboardButtons[i]); + assertEquals("Failed check for " + jemmyKeyboardButtons[i], awtKeyboardButtons[i], result); + } + } + + private final Modifier[][] jemmyModifierCombinations = new Modifier [][] { + { KeyboardModifiers.SHIFT_DOWN_MASK }, + { KeyboardModifiers.CTRL_DOWN_MASK, KeyboardModifiers.SHIFT_DOWN_MASK }, + { KeyboardModifiers.CTRL_DOWN_MASK, KeyboardModifiers.ALT_DOWN_MASK, KeyboardModifiers.SHIFT_DOWN_MASK }, + { MouseModifiers.BUTTON1_DOWN_MASK }, + { MouseModifiers.BUTTON1_DOWN_MASK, KeyboardModifiers.SHIFT_DOWN_MASK }, + }; + private final int[] awtModifierCombinations = new int [] { + SHIFT_DOWN_MASK, + CTRL_DOWN_MASK | SHIFT_DOWN_MASK, + CTRL_DOWN_MASK | ALT_DOWN_MASK | SHIFT_DOWN_MASK, + BUTTON1_DOWN_MASK, + BUTTON1_DOWN_MASK | SHIFT_DOWN_MASK + }; + + /** + * Test of convert method, of class AWTMap. + */ + @Test + public void testConvert_ModifierArr() { + System.out.println("convert"); + for(int i = 0; i < jemmyModifierCombinations.length; i++) { + Modifier[] modifiers = jemmyModifierCombinations[i]; + int expResult = awtModifierCombinations[i]; + int result = new AWTMap().convert(modifiers); + assertEquals("Failed check for " + Arrays.toString(modifiers), expResult, result); + } + } + + private final MouseButton [] jemmyMouseButtons = new MouseButton [] { MouseButtons.BUTTON1 }; + private final int [] awtMouseButtons = new int [] { BUTTON1_MASK }; + + /** + * Test of convert method, of class AWTMap. + */ + @Test + public void testConvert_MouseMouseButton() { + System.out.println("convert"); + for(int i = 0; i < jemmyMouseButtons.length; i++) { + MouseButton button = jemmyMouseButtons[i]; + int expResult = awtMouseButtons[i]; + int result = new AWTMap().convert(button); + assertEquals("Failed check for " + button, expResult, result); + } + } + + /** + * Test of convertKeyboardButton method, of class AWTMap. + */ + @Test + public void testConvertKeyboardButton() { + System.out.println("convertKeyboardButton"); + for (int i = 0; i < awtKeyboardButtons.length; i++) { + int key = awtKeyboardButtons[i]; + KeyboardButton expResult = jemmyKeyboardButtons[i]; + KeyboardButton result = new AWTMap().convertKeyboardButton(key); + assertEquals("Failed check for " + expResult, expResult, result); + } + } + + /** + * Test of convertModifiers method, of class AWTMap. + */ + @Test + public void testConvertModifiers() { + System.out.println("convertModifiers"); + for (int i = 0; i < awtModifierCombinations.length; i ++) { + int modifiers = awtModifierCombinations[i]; + Modifier[] expResult = jemmyModifierCombinations[i]; + Modifier[] result = new AWTMap().convertModifiers(modifiers); + assertEquals("Failed check with " + Arrays.toString(expResult), new HashSet(Arrays.asList(expResult)), new HashSet(Arrays.asList(result))); + } + } + + /** + * Test of convertMouseButton method, of class AWTMap. + */ + @Test + public void testConvertMouseButton() { + System.out.println("convertMouseButton"); + for (int i = 0; i < awtMouseButtons.length; i++) { + int button = awtMouseButtons[i]; + MouseButton expResult = jemmyMouseButtons[i]; + MouseButton result = new AWTMap().convertMouseButton(button); + assertEquals("Check failed with " + expResult, expResult, result); + } + } + + @Test + public void testGetException() { + try { + new AWTMap().convert(new KeyboardButton() {}); + fail("No JemmyException"); + } catch(JemmyException e) { + } catch(Exception e) { + fail("Not a JemmyException"); + } + try { + new AWTMap().convert(new MouseButton() {}); + fail("No JemmyException"); + } catch(JemmyException e) { + } catch(Exception e) { + fail("Not a JemmyException"); + } + try { + new AWTMap().convert(new Modifier() {}, new Modifier() {}); + fail("No JemmyException"); + } catch(JemmyException e) { + } catch(Exception e) { + fail("Not a JemmyException"); + } + try { + new AWTMap().convertKeyboardButton(-1); + fail("No JemmyException"); + } catch(JemmyException e) { + } catch(Exception e) { + fail("Not a JemmyException"); + } + try { + new AWTMap().convertMouseButton(-1); + fail("No JemmyException"); + } catch(JemmyException e) { + } catch(Exception e) { + fail("Not a JemmyException"); + } + } + +} \ No newline at end of file diff --git a/core/JemmyAWTInput/test/org/jemmy/input/RobotDriver2Test.java b/core/JemmyAWTInput/test/org/jemmy/input/RobotDriver2Test.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/input/RobotDriver2Test.java @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.awt.AWTException; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.Robot; +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Queue; +import java.util.Random; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.jemmy.Point; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.image.AWTImage; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Modifier; +import org.jemmy.interfaces.Mouse.MouseButton; +import org.jemmy.interfaces.Mouse.MouseButtons; +import org.jemmy.timing.State; +import org.jemmy.timing.Waiter; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + + +/** + * + * @author Alexander Kouznetsov + */ +public class RobotDriver2Test { + + final static Timeout TIMEOUT = new Timeout("Wait for state to be reached", 10000); + final static Timeout DELTA_TIMEOUT = new Timeout("Delta timeout of wait for state to be reached", 1000); + + public RobotDriver2Test() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + File workdir = new File(System.getProperty("user.dir") + File.separator + + "build" + File.separator + + "test" + File.separator + + "results"); + workdir.mkdirs(); + AWTImage.setImageRoot(workdir); + } + + @AfterClass + public static void tearDownClass() throws Exception { + RobotDriver.exit(); + } + + Frame frm; + Button btn; + Wrap area; + RobotDriver instance; + Robot rb; + Queue queue = new ConcurrentLinkedQueue(); + static Random r = new Random(); + + @BeforeMethod + public void setUp() throws InterruptedException, AWTException, InvocationTargetException { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm = new Frame("some frame"); + frm.setSize(100, 100); + frm.setLocation(100, 100); + btn = new Button("some button"); + MouseAdapter m = new MouseAdapter() { + + @Override + public void mousePressed(MouseEvent e) { + System.out.println("mousePressed event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + @Override + public void mouseReleased(MouseEvent e) { + System.out.println("mouseReleased event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + @Override + public void mouseMoved(MouseEvent e) { + System.out.println("mouseMoved event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + @Override + public void mouseClicked(MouseEvent e) { + System.out.println("mouseClicked event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + @Override + public void mouseDragged(MouseEvent e) { + System.out.println("mouseDragged event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + }; + btn.addMouseListener(m); + btn.addMouseMotionListener(m); + btn.addKeyListener(new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent e) { + System.out.println("keyPressed event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + @Override + public void keyReleased(KeyEvent e) { + System.out.println("keyReleased event triggered: " + e); + System.out.flush(); + queue.add(e); + } + + }); + frm.add(btn, BorderLayout.SOUTH); + frm.doLayout(); + instance = new RobotDriver(Environment.getEnvironment()); + frm.setVisible(true); + btn.requestFocusInWindow(); + } + }); + + rb = new Robot(); + rb.waitForIdle(); + + RobotExecutor.get().setRunInOtherJVM(true); + + } + + @AfterMethod + public void tearDown() throws InterruptedException, InvocationTargetException { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm.setVisible(false); + } + }); + } + +// /** +// * Test of createScreenCapture method, of class RobotDriver. +// */ +// @Test +// public void testCreateScreenCaptureLocally() throws AWTException, InterruptedException { +// System.out.println("testCreateScreenCaptureLocally"); +// Thread.sleep(3000); +// Rectangle screenRect = new Rectangle(100, 100, 100, 100); +// RobotExecutor.get().setRunInOtherJVM(false); +// Image expResult = new AWTImage(new Robot().createScreenCapture(new java.awt.Rectangle(100, 100, 100, 100))); +// Image result = RobotDriver.createScreenCapture(screenRect); +// Image diff = expResult.compareTo(result); +// if (diff != null) { +// diff.save("testCreateScreenCaptureLocally.png"); +// fail(); +// } +// } +// +// /** +// * Test of createScreenCapture method, of class RobotDriver. +// */ +// @Test +// public void testCreateScreenCaptureRemotely() throws AWTException, InterruptedException { +// System.out.println("testCreateScreenCaptureRemotely"); +// Thread.sleep(3000); +// Rectangle screenRect = new Rectangle(100, 100, 100, 100); +// RobotExecutor.get().setRunInOtherJVM(true); +// Image expResult = new AWTImage(new Robot().createScreenCapture(new java.awt.Rectangle(100, 100, 100, 100))); +// Image result = RobotDriver.createScreenCapture(screenRect); +// RobotDriver.createScreenCapture(screenRect); +// Image diff = expResult.compareTo(result); +// if (diff != null) { +// diff.save("testCreateScreenCaptureRemotely.png"); +// fail(); +// } +// } +// +// /** +// * Test of createScreenCapture method, of class RobotDriver. +// */ +// @Test +// public void testCreateScreenCaptureRemotely2() throws AWTException, InterruptedException { +// System.out.println("testCreateScreenCaptureRemotely2"); +// Thread.sleep(3000); +// Rectangle screenRect = new Rectangle(100, 100, 100, 100); +// RobotExecutor.get().setRunInOtherJVM(true); +// Image expResult = new AWTImage(new Robot().createScreenCapture(new java.awt.Rectangle(100, 100, 100, 100))); +// Image result = RobotDriver.createScreenCapture(screenRect); +// Image diff = expResult.compareTo(result); +// if (diff != null) { +// diff.save("testCreateScreenCaptureRemotely2.png"); +// fail(); +// } +// } + + /** + * Test of all RobotDriver methods invoked multiple times + * @throws InterruptedException + */ +// @Test + public void testAll() throws InterruptedException { + for(int i = 0; i < 10; i++) { + testPressMouse(); + } + } + + public void test() throws InterruptedException { +// testPressMouse(); + testPressKey(); + } + + @Test + public void test0() throws InterruptedException { + test(); + } + + @Test + public void test1() throws InterruptedException { + test(); + } + + @Test + public void test2() throws InterruptedException { + test(); + } + + @Test + public void test3() throws InterruptedException { + test(); + } + + @Test + public void test4() throws InterruptedException { + test(); + } + + @Test + public void test5() throws InterruptedException { + test(); + } + + @Test + public void test6() throws InterruptedException { + test(); + } + + @Test + public void test7() throws InterruptedException { + test(); + } + + @Test + public void test8() throws InterruptedException { + test(); + } + + @Test + public void test9() throws InterruptedException { + test(); + } + + protected static final int[] MODIFIERS = new int[] { + InputEvent.SHIFT_DOWN_MASK, + InputEvent.CTRL_DOWN_MASK, + InputEvent.ALT_DOWN_MASK, + InputEvent.SHIFT_MASK, + InputEvent.CTRL_MASK, + InputEvent.ALT_MASK, +// MouseEvent.ALT_GRAPH_DOWN_MASK, +// MouseEvent.META_DOWN_MASK + }; + + public static int getModifiers() { + int modifiersMask = r.nextInt(1 << MODIFIERS.length/2); + int m = 0; + System.out.print("Modifiers:"); + for (int i = 0; i < MODIFIERS.length/2; i++) { + if ((modifiersMask & (1 << i)) != 0 ) { + m |= MODIFIERS[i]; + System.out.print(" " + i); + } + } + System.out.println(""); + return m; + } + + protected static HashMap normalizeMap = new HashMap(); + static { + normalizeMap.put(InputEvent.SHIFT_MASK,InputEvent.SHIFT_DOWN_MASK); + normalizeMap.put(InputEvent.CTRL_MASK,InputEvent.CTRL_DOWN_MASK); + normalizeMap.put(InputEvent.ALT_MASK,InputEvent.ALT_DOWN_MASK); + normalizeMap.put(InputEvent.META_MASK,InputEvent.META_DOWN_MASK); + } + + protected static HashSet normalize(HashSet modifiers) { + HashSet normalized = new HashSet(); + for (Integer mod : modifiers) { + Integer n = normalizeMap.get(mod); + if (n != null) { + normalized.add(n); + } else { + normalized.add(mod); + } + } + return normalized; + } + + protected static HashSet modifiers2Set(int mods) { + HashSet set = new HashSet(); + for (int i = 0; i < MODIFIERS.length; i++) { + if ((mods & MODIFIERS[i]) > 0) { + set.add(MODIFIERS[i]); + } + } + return set; + } + + protected static boolean compareModifiers(int m1, int m2) { + return normalize(modifiers2Set(m1)).equals(normalize(modifiers2Set(m2))); + } + /** + * Test of pressMouse method, of class RobotDriver. + */ + public void testPressMouse() throws InterruptedException { + System.out.println("pressMouse"); + Thread.sleep(3000); + final int[] MOUSE_BUTTONS_1 = new int[] { + MouseEvent.BUTTON1_MASK, + MouseEvent.BUTTON2_MASK, + MouseEvent.BUTTON3_MASK + }; + final int[] MOUSE_BUTTONS_2 = new int[] { + MouseEvent.BUTTON1, + MouseEvent.BUTTON2, + MouseEvent.BUTTON3 + }; + int bIndex = r.nextInt(MOUSE_BUTTONS_1.length); + final int mouseButton1 = MOUSE_BUTTONS_1[bIndex]; + final int mouseButton2 = MOUSE_BUTTONS_2[bIndex]; + System.out.print("Button: " + bIndex + " Modifier:"); + final int modifiers = getModifiers(); + queue.clear(); + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Pressing mouse"); + instance.moveMouse(new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2)); + AWTMap map = new AWTMap(); + MouseButton button = map.convertMouseButton(mouseButton1); + Modifier[] converted_modifiers = map.convertModifiers(modifiers); + instance.pressMouse(button, converted_modifiers); + instance.releaseMouse(button, converted_modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + while(true) { + InputEvent e = queue.poll(); + if (e != null) { + if (e instanceof MouseEvent) { + MouseEvent me = (MouseEvent) e; + if (me.getID() == MouseEvent.MOUSE_PRESSED && me.getButton() == mouseButton2 && (compareModifiers(me.getModifiers(), modifiers))) { + return true; + } + if (me.getID() == MouseEvent.MOUSE_PRESSED) { + System.out.println("Wrong combination of button and modifiers triggered:"); + System.out.println("me.getModifiers() = " + Integer.toString(me.getModifiers(), 2) + ", modifiers = " + Integer.toString(modifiers, 2)); + System.out.println("expected: " + new MouseEvent( + me.getComponent(), MouseEvent.MOUSE_PRESSED, + me.getWhen(), modifiers, me.getX(), me.getY(), me.getClickCount(), me.isPopupTrigger(), mouseButton2)); + System.out.println(" got: " + me); + } + } + } else { + break; + } + } + return null; + } + + }); + + System.out.println("PASSED"); + + } + +// /** +// * Test of releaseMouse method, of class RobotDriver. +// */ +// @Test +// public void testReleaseMouse() throws InterruptedException { +// System.out.println("releaseMouse"); +// Thread.sleep(3000); +// int mouseButton = MouseEvent.BUTTON2_MASK; +// int modifiers = MouseEvent.CTRL_DOWN_MASK; +// mouseReleased = false; +// java.awt.Point locationOnScreen = btn.getLocationOnScreen(); +// instance.moveMouse(new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2)); +// System.out.println("Pressing mouse"); +// instance.pressMouse(mouseButton, modifiers); +// System.out.println("Releasing mouse"); +// instance.releaseMouse(mouseButton, modifiers); +// +// rb.waitForIdle(); +// new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ +// +// public Boolean reached() { +// return mouseReleased ? true: null; +// } +// +// }); +// assertTrue(mouseReleased); +// } +// +// /** +// * Test of moveMouse method, of class RobotDriver. +// */ +// @Test +// public void testMoveMouse() throws InterruptedException { +// System.out.println("moveMouse"); +// Thread.sleep(3000); +// mouseMoved = false; +// java.awt.Point locationOnScreen = btn.getLocationOnScreen(); +// System.out.println("Moving mouse"); +// Point startPoint = new Point(locationOnScreen.x, locationOnScreen.y); +// Point endPoint = new Point(locationOnScreen.x + btn.getWidth(), locationOnScreen.y + btn.getHeight()); +// double steps = 5; //Math.max(btn.getWidth(), btn.getHeight()); +// double dx = (endPoint.x - startPoint.x) / steps; +// double dy = (endPoint.y - startPoint.y) / steps; +// for(int i = 0; i < steps; i++) { +// Point point = new Point(startPoint.x + dx * i, startPoint.y + dy * i); +// instance.moveMouse(point); +// Thread.sleep(100); +// } +// +// rb.waitForIdle(); +// new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ +// +// public Boolean reached() { +// return mouseMoved ? true: null; +// } +// +// }); +// assertTrue(mouseMoved); +// } +// +// /** +// * Test of clickMouse method, of class RobotDriver. +// */ +// @Test +// public void testClickMouse() throws InterruptedException { +// System.out.println("clickMouse"); +// Thread.sleep(3000); +// mouseClicked = false; +// java.awt.Point locationOnScreen = btn.getLocationOnScreen(); +// Point point = new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2); +// int clickCount = 1; +// int mouseButton = MouseEvent.BUTTON3_MASK; +// int modifiers = InputEvent.ALT_DOWN_MASK; +// Timeout mouseClick = new Timeout("mouseClick", 100); +// System.out.println("Clicking mouse"); +// instance.clickMouse(point, clickCount, mouseButton, modifiers, mouseClick); +// +// rb.waitForIdle(); +// new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ +// +// public Boolean reached() { +// return mouseClicked ? true: null; +// } +// +// }); +// assertTrue(mouseClicked); +// } +// +// /** +// * Test of dragNDrop method, of class RobotDriver. +// */ +// @Test +// public void testDragNDrop() throws InterruptedException { +// System.out.println("dragNDrop"); +// java.awt.Point locationOnScreen = btn.getLocationOnScreen(); +// Point startPoint = new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2); +// Point endPoint = new Point(frm.getLocationOnScreen().x + frm.getWidth() / 2, frm.getLocationOnScreen().y + frm.getHeight() / 2); +// int mouseButton = MouseEvent.BUTTON2_MASK; +// int modifiers = 0; +// Timeout before = new Timeout("before", 500); +// Timeout after = new Timeout("after", 500); +// mouseDragged = false; +// instance.dragNDrop(startPoint, endPoint, mouseButton, modifiers, before, after); +// +// rb.waitForIdle(); +// new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ +// +// public Boolean reached() { +// return mouseDragged ? true: null; +// } +// +// }); +// assertTrue(mouseDragged); +// } + + /** + * Test of pressKey method, of class RobotDriver. + */ + public void testPressKey() throws InterruptedException { + System.out.println("pressKey"); + Thread.sleep(3000); + + final int keyCode = KeyEvent.VK_A; + final int modifiers = getModifiers(); + AWTMap map = new AWTMap(); + KeyboardButton button = map.convertKeyboardButton(keyCode); + Modifier[] converted_modifiers = map.convertModifiers(modifiers); + + queue.clear(); + + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + instance.clickMouse(new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2), 1, + MouseButtons.BUTTON1, DELTA_TIMEOUT); + + rb.waitForIdle(); + + instance.pressKey(button, converted_modifiers); + instance.releaseKey(button, converted_modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + while(true) { + InputEvent e = queue.poll(); + if (e != null) { + if (e instanceof KeyEvent) { + KeyEvent ke = (KeyEvent) e; + if (ke.getID() == KeyEvent.KEY_PRESSED && ke.getKeyCode() == keyCode && compareModifiers(ke.getModifiers(), modifiers)) { + return true; + } + if (ke.getID() == KeyEvent.KEY_PRESSED) { + System.out.println("Wrong combination of button and modifiers triggered:"); + System.out.println("ke.getModifiers() = " + Integer.toString(ke.getModifiers(), 2) + ", modifiers = " + Integer.toString(modifiers, 2)); + System.out.println("expected: " + new KeyEvent(ke.getComponent(), KeyEvent.KEY_PRESSED, ke.getWhen(), modifiers, keyCode, KeyEvent.CHAR_UNDEFINED)); + System.out.println(" got: " + ke); + } + } + } else { + break; + } + } + return null; + } + + }); + + System.out.println("PASSED"); + } + +// /** +// * Test of releaseKey method, of class RobotDriver. +// */ +// @Test +// public void testReleaseKey() throws InterruptedException { +// System.out.println("releaseKey"); +// int keyCode = KeyEvent.VK_B; +// int modifiers = 0; +// keyReleased = false; +// instance.pressKey(keyCode, modifiers); +// instance.releaseKey(keyCode, modifiers); +// +// rb.waitForIdle(); +// new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ +// +// public Boolean reached() { +// return keyReleased ? true: null; +// } +// +// }); +// assertTrue(keyReleased); +// } + +} diff --git a/core/JemmyAWTInput/test/org/jemmy/input/RobotDriverTest.java b/core/JemmyAWTInput/test/org/jemmy/input/RobotDriverTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/input/RobotDriverTest.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.awt.AWTException; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.Robot; +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import org.jemmy.Point; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.image.AWTImage; +import org.jemmy.interfaces.Keyboard; +import org.jemmy.interfaces.Keyboard.KeyboardButton; +import org.jemmy.interfaces.Keyboard.KeyboardButtons; +import org.jemmy.interfaces.Keyboard.KeyboardModifiers; +import org.jemmy.interfaces.Modifier; +import org.jemmy.interfaces.Mouse; +import org.jemmy.interfaces.Mouse.MouseButton; +import org.jemmy.interfaces.Mouse.MouseButtons; +import org.jemmy.timing.State; +import org.jemmy.timing.Waiter; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + + +/** + * + * @author Alexander Kouznetsov + */ +public class RobotDriverTest { + + final static Timeout TIMEOUT = new Timeout("Wait for state to be reached", 10000); + final static Timeout DELTA_TIMEOUT = new Timeout("Delta timeout of wait for state to be reached", 1000); + + public RobotDriverTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + File workdir = new File(System.getProperty("user.dir") + File.separator + + "build" + File.separator + + "test" + File.separator + + "results"); + workdir.mkdirs(); + AWTImage.setImageRoot(workdir); + } + + @AfterClass + public static void tearDownClass() throws Exception { + RobotDriver.exit(); + } + + Frame frm; + Button btn; + Wrap area; + private volatile boolean mousePressed; + private volatile boolean mouseReleased; + private volatile boolean mouseMoved; + private volatile boolean mouseClicked; + private volatile boolean mouseDragged; + private volatile boolean keyPressed; + private volatile boolean keyReleased; + RobotDriver instance; + Robot rb; + + @BeforeMethod + public void setUp() throws InterruptedException, AWTException, InvocationTargetException { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm = new Frame("some frame"); + frm.setSize(100, 100); + frm.setLocation(100, 100); + btn = new Button("some button"); + MouseAdapter m = new MouseAdapter() { + + @Override + public void mousePressed(MouseEvent e) { + System.out.println("mousePressed event triggered: " + e); + System.out.flush(); + if ((e.getButton() & MouseEvent.BUTTON1) != 0 && e.isShiftDown()) { + mousePressed = true; + } + } + + @Override + public void mouseReleased(MouseEvent e) { + System.out.println("mouseReleased event triggered: " + e); + System.out.flush(); + if ((e.getButton() & MouseEvent.BUTTON2) != 0 && e.isControlDown()) { + mouseReleased = true; + } + } + + @Override + public void mouseMoved(MouseEvent e) { + System.out.println("mouseMoved event triggered: " + e); + System.out.flush(); + mouseMoved = true; + } + + @Override + public void mouseClicked(MouseEvent e) { + System.out.println("mouseClicked event triggered: " + e); + System.out.flush(); + if ((e.getButton() & MouseEvent.BUTTON3) != 0 && e.isAltDown()) { + mouseClicked = true; + } + } + + @Override + public void mouseDragged(MouseEvent e) { + System.out.println("mouseDragged event triggered: " + e); + System.out.flush(); + if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) != 0) { + mouseDragged = true; + } + } + + }; + btn.addMouseListener(m); + btn.addMouseMotionListener(m); + btn.addKeyListener(new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent e) { + System.out.println("keyPressed event triggered: " + e); + System.out.flush(); + if (e.getKeyCode() == KeyEvent.VK_A && e.isShiftDown()) { + keyPressed = true; + } + } + + @Override + public void keyReleased(KeyEvent e) { + System.out.println("keyReleased event triggered: " + e); + System.out.flush(); + if (e.getKeyCode() == KeyEvent.VK_B) { + keyReleased = true; + } + } + + }); + frm.add(btn, BorderLayout.SOUTH); + frm.doLayout(); + instance = new RobotDriver(Environment.getEnvironment()); + frm.setVisible(true); + btn.requestFocusInWindow(); + } + }); + + rb = new Robot(); + rb.waitForIdle(); + + RobotExecutor.get().setRunInOtherJVM(true); + } + + @AfterMethod + public void tearDown() throws InterruptedException, InvocationTargetException { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm.setVisible(false); + } + }); + } + +// /** +// * Test of createScreenCapture method, of class RobotDriver. +// */ +// @Test +// public void testCreateScreenCaptureLocally() throws AWTException, InterruptedException { +// System.out.println("testCreateScreenCaptureLocally"); +// Thread.sleep(3000); +// Rectangle screenRect = new Rectangle(100, 100, 100, 100); +// RobotExecutor.get().setRunInOtherJVM(false); +// Image expResult = new AWTImage(new Robot().createScreenCapture(new java.awt.Rectangle(100, 100, 100, 100))); +// Image result = RobotDriver.createScreenCapture(screenRect); +// Image diff = expResult.compareTo(result); +// if (diff != null) { +// diff.save("testCreateScreenCaptureLocally.png"); +// fail(); +// } +// } +// +// /** +// * Test of createScreenCapture method, of class RobotDriver. +// */ +// @Test +// public void testCreateScreenCaptureRemotely() throws AWTException, InterruptedException { +// System.out.println("testCreateScreenCaptureRemotely"); +// Thread.sleep(3000); +// Rectangle screenRect = new Rectangle(100, 100, 100, 100); +// RobotExecutor.get().setRunInOtherJVM(true); +// Image expResult = new AWTImage(new Robot().createScreenCapture(new java.awt.Rectangle(100, 100, 100, 100))); +// Image result = RobotDriver.createScreenCapture(screenRect); +// RobotDriver.createScreenCapture(screenRect); +// Image diff = expResult.compareTo(result); +// if (diff != null) { +// diff.save("testCreateScreenCaptureRemotely.png"); +// fail(); +// } +// } +// +// /** +// * Test of createScreenCapture method, of class RobotDriver. +// */ +// @Test +// public void testCreateScreenCaptureRemotely2() throws AWTException, InterruptedException { +// System.out.println("testCreateScreenCaptureRemotely2"); +// Thread.sleep(3000); +// Rectangle screenRect = new Rectangle(100, 100, 100, 100); +// RobotExecutor.get().setRunInOtherJVM(true); +// Image expResult = new AWTImage(new Robot().createScreenCapture(new java.awt.Rectangle(100, 100, 100, 100))); +// Image result = RobotDriver.createScreenCapture(screenRect); +// Image diff = expResult.compareTo(result); +// if (diff != null) { +// diff.save("testCreateScreenCaptureRemotely2.png"); +// fail(); +// } +// } + + /** + * Test of pressMouse method, of class RobotDriver. + */ + @Test + public void testPressMouse() throws InterruptedException { + System.out.println("pressMouse"); + Thread.sleep(3000); +// new Thread() { +// +// @Override +// public void run() { + MouseButton mouseButton = MouseButtons.BUTTON1; + Modifier modifiers[] = new Modifier[] {KeyboardModifiers.SHIFT_DOWN_MASK}; + mousePressed = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Pressing mouse"); + instance.moveMouse(new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2)); + instance.pressMouse(mouseButton, modifiers); + instance.releaseMouse(mouseButton, modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mousePressed ? true: null; + } + + }); + +// } +// +// }.start(); + } + + /** + * Test of releaseMouse method, of class RobotDriver. + */ + @Test + public void testReleaseMouse() throws InterruptedException { + System.out.println("releaseMouse"); + Thread.sleep(3000); + MouseButton mouseButton = MouseButtons.BUTTON2; + Modifier modifiers[] = new Modifier[] {KeyboardModifiers.CTRL_DOWN_MASK}; + mouseReleased = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + instance.moveMouse(new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2)); + System.out.println("Pressing mouse"); + instance.pressMouse(mouseButton, modifiers); + System.out.println("Releasing mouse"); + instance.releaseMouse(mouseButton, modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseReleased ? true: null; + } + + }); + assertTrue(mouseReleased); + } + + /** + * Test of moveMouse method, of class RobotDriver. + */ + @Test + public void testMoveMouse() throws InterruptedException { + System.out.println("moveMouse"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x, locationOnScreen.y); + Point endPoint = new Point(locationOnScreen.x + btn.getWidth(), locationOnScreen.y + btn.getHeight()); + double steps = 5; //Math.max(btn.getWidth(), btn.getHeight()); + double dx = (endPoint.x - startPoint.x) / steps; + double dy = (endPoint.y - startPoint.y) / steps; + for(int i = 0; i < steps; i++) { + Point point = new Point(startPoint.x + dx * i, startPoint.y + dy * i); + instance.moveMouse(point); + Thread.sleep(100); + } + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true: null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method with smoothness set Integer.MAX_VALUE + * of class RobotDriver. + */ + @Test + public void testMoveNonSmoothMouse() throws InterruptedException { + System.out.println("testMoveNonSmoothMouse"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x - 10, locationOnScreen.y - 10); + Point endPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, locationOnScreen.y + btn.getHeight() + 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + Thread.sleep(2000); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return !mouseMoved ? true : null; + } + + }); + assertFalse(mouseMoved); + } + + /** + * Test of clickMouse method, of class RobotDriver. + */ + @Test + public void testClickMouse() throws InterruptedException { + System.out.println("clickMouse"); + Thread.sleep(3000); + mouseClicked = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + Point point = new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2); + int clickCount = 1; + MouseButton mouseButton = MouseButtons.BUTTON3; + Modifier modifiers[] = new Modifier[] {KeyboardModifiers.ALT_DOWN_MASK}; + Timeout mouseClick = new Timeout("mouseClick", 100); + System.out.println("Clicking mouse"); + instance.clickMouse(point, clickCount, mouseButton, mouseClick, modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseClicked ? true: null; + } + + }); + assertTrue(mouseClicked); + } + + /** + * Test of dragNDrop method, of class RobotDriver. + */ + @Test + public void testDragNDrop() throws InterruptedException { + System.out.println("dragNDrop"); + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + Point startPoint = new Point(locationOnScreen.x + btn.getWidth() / 2, locationOnScreen.y + btn.getHeight() / 2); + Point endPoint = new Point(frm.getLocationOnScreen().x + frm.getWidth() / 2, frm.getLocationOnScreen().y + frm.getHeight() / 2); + MouseButton mouseButton = MouseButtons.BUTTON2; + Modifier modifiers[] = new Modifier[] {}; + Timeout before = new Timeout("before", 500); + Timeout after = new Timeout("after", 500); + mouseDragged = false; + instance.dragNDrop(startPoint, endPoint, mouseButton, modifiers, before, after); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseDragged ? true: null; + } + + }); + assertTrue(mouseDragged); + } + + /** + * Test of pressKey method, of class RobotDriver. + */ + @Test + public void testPressKey() throws InterruptedException { + System.out.println("pressKey"); + KeyboardButton kbdButton = KeyboardButtons.A; + Modifier modifiers[] = new Modifier[] {KeyboardModifiers.SHIFT_DOWN_MASK}; + keyPressed = false; + instance.pressKey(kbdButton, modifiers); + instance.releaseKey(kbdButton, modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return keyPressed ? true: null; + } + + }); + assertTrue(keyPressed); + } + + /** + * Test of releaseKey method, of class RobotDriver. + */ + @Test + public void testReleaseKey() throws InterruptedException { + System.out.println("releaseKey"); + KeyboardButton kbdButton = KeyboardButtons.B; + Modifier modifiers[] = new Modifier[] {}; + keyReleased = false; + instance.pressKey(kbdButton, modifiers); + instance.releaseKey(kbdButton, modifiers); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return keyReleased ? true: null; + } + + }); + assertTrue(keyReleased); + } + +} \ No newline at end of file diff --git a/core/JemmyAWTInput/test/org/jemmy/input/RobotExecutorTest.java b/core/JemmyAWTInput/test/org/jemmy/input/RobotExecutorTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/input/RobotExecutorTest.java @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.jemmy.JemmyException; +import org.jemmy.Rectangle; +import org.jemmy.env.Environment; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.fail; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + + +/** + * TODO: this test is unstable + * @author Alexander Kouznetsov + */ +public class RobotExecutorTest { + +// public RobotExecutorTest() { +// } +// +// static File props; +// +// @BeforeClass +// public static void setUpClass() throws Exception { +// props = File.createTempFile("jemmy", "properties"); +// final FileWriter fileWriter = new FileWriter(props); +// fileWriter.write(AWTRobotInputFactory.OTHER_VM_CONNECTION_TIMEOUT_PROPERTY + "=" + CONNECTION_TIMEOUT); +// fileWriter.flush(); +// } +// +// @AfterClass +// public static void tearDownClass() throws Exception { +// props.delete(); +// } +// +// @BeforeMethod +// public void setUp() { +// } +// +// @AfterMethod +// public void tearDown() { +// } + +// /** +// * Test of get method, of class RobotExecutor. +// */ +// @Test +// public void testGet() { +// System.out.println("get"); +// RobotExecutor expResult = null; +// RobotExecutor result = RobotExecutor.get(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of createScreenCapture method, of class RobotExecutor. +// */ +// @Test +// public void testCreateScreenCapture() { +// System.out.println("createScreenCapture"); +// Rectangle screenRect = null; +// RobotExecutor instance = new RobotExecutor(); +// Image expResult = null; +// Image result = instance.createScreenCapture(screenRect); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of makeAnOperation method, of class RobotExecutor. +// */ +// @Test +// public void testMakeAnOperation() { +// System.out.println("makeAnOperation"); +// String method = ""; +// Object[] params = null; +// Class[] paramClasses = null; +// RobotExecutor instance = new RobotExecutor(); +// Object expResult = null; +// Object result = instance.makeAnOperation(method, params, paramClasses); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// final static int CONNECTION_TIMEOUT = 30000; +// final static int DESTROY_TIMEOUT = 8000; +// +// /** +// * +// * @throws IOException +// */ +// @Test +// public void testOtherVMConnectionTimout() throws IOException, InterruptedException { +// System.out.println("testOtherVMConnectionTimout"); +// String ROBOT_TIMEOUT = "123000"; +// Object prevValue = Environment.getEnvironment().setProperty(AWTRobotInputFactory.OTHER_VM_CONNECTION_TIMEOUT_PROPERTY, ROBOT_TIMEOUT); +// RobotExecutor re = RobotExecutor.get(); +// re.setRunInOtherJVM(true); +// String timeout = (String)re.getProperty(AWTRobotInputFactory.OTHER_VM_CONNECTION_TIMEOUT_PROPERTY); +// re.exit(); +// Thread.sleep(DESTROY_TIMEOUT); +// Environment.getEnvironment().setProperty(AWTRobotInputFactory.OTHER_VM_CONNECTION_TIMEOUT_PROPERTY, prevValue); +// assertEquals(ROBOT_TIMEOUT, timeout); +// } +// +// /** +// * +// * @throws IOException +// */ +// @Test +// public void testOtherVMConnectionPort() throws IOException, InterruptedException { +// System.out.println("testOtherVMJemmyProperties"); +// String PORT = "12300"; +// Object prevValue = Environment.getEnvironment().setProperty(AWTRobotInputFactory.OTHER_VM_PORT_PROPERTY, PORT); +// RobotExecutor re = RobotExecutor.get(); +// re.setRunInOtherJVM(true); +// String port = (String)re.getProperty(AWTRobotInputFactory.OTHER_VM_PORT_PROPERTY); +// re.exit(); +// Thread.sleep(DESTROY_TIMEOUT); +// Environment.getEnvironment().setProperty(AWTRobotInputFactory.OTHER_VM_PORT_PROPERTY, prevValue); +// assertEquals(PORT, port); +// } +// +// /** +// * Test of exit method, of class RobotExecutor. +// */ +// @Test +// public void testExit() { +// System.out.println("exit"); +// Process pp = null; +// try { +// ProcessBuilder pb = new ProcessBuilder("java", "-cp", System.getProperty("java.class.path"), "-D" + Environment.JEMMY_PROPERTIES_FILE_PROPERTY + "=" + props.getCanonicalPath(), RobotExecutor.class.getName()); +// pb.redirectErrorStream(true); +// final Process p = pb.start(); +// pp = p; +// new Thread() { +// +// @Override +// public void run() { +// BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); +// while (true) { +// try { +// String line = br.readLine(); +// if (line == null) { +// break; +// } +// System.out.println("SERVER: " + line); +// } catch (IOException ex) { +// throw new JemmyException("Exception during other JVM output processing", ex); +// } +// } +// } +// }.start(); +// RobotExecutor re = RobotExecutor.get(); +// re.setRunInOtherJVM(true); +// re.exit(); +// final boolean [] result = new boolean[] { false }; +// synchronized (result) { +// new Thread() { +// +// @Override +// public void run() { +// try { +// p.waitFor(); +// synchronized (result) { +// result[0] = true; +// result.notify(); +// } +// } catch (InterruptedException ex) { +// Logger.getLogger(RobotExecutorTest.class.getName()).log(Level.SEVERE, null, ex); +// synchronized (result) { +// result[0] = false; +// result.notify(); +// } +// } +// } +// +// }.start(); +// try { +// result.wait(DESTROY_TIMEOUT * 2); +// } catch (InterruptedException ex) { +// Logger.getLogger(RobotExecutorTest.class.getName()).log(Level.SEVERE, null, ex); +// } +// assertTrue("Server process doesn't finish", result[0]); +// } +// } catch (IOException ex) { +// throw new JemmyException("Failed to start other JVM", ex); +// } finally { +// if (pp != null) { +// pp.destroy(); +// try { +// Thread.sleep(DESTROY_TIMEOUT); +// } catch (InterruptedException ex) { +// Logger.getLogger(RobotExecutorTest.class.getName()).log(Level.SEVERE, null, ex); +// } +// } +// } +// } +// +// /** +// * Test of main method, of class RobotExecutor. +// */ +// @Test +// public void testMain() throws InterruptedException { +// System.out.println("main"); +// try { +// final boolean [] result = new boolean[] { false }; +// long start = System.currentTimeMillis(); +// ProcessBuilder pb = new ProcessBuilder("java", "-cp", System.getProperty("java.class.path"), "-D" + Environment.JEMMY_PROPERTIES_FILE_PROPERTY + "=" + props.getCanonicalPath(), RobotExecutor.class.getName()); +// pb.redirectErrorStream(true); +// final Process p = pb.start(); +// synchronized(result) { +// new Thread() { +// +// @Override +// public void run() { +// BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); +// while (true) { +// try { +// String line = br.readLine(); +// if (line == null) { +// break; +// } +// System.out.println("SERVER: " + line); +// if (line.startsWith("Exiting server as there is no connection for")) { +// synchronized (result) { +// result[0] = true; +// result.notify(); +// } +// } +// } catch (IOException ex) { +// throw new JemmyException("Exception during other JVM output processing", ex); +// } +// } +// } +// }.start(); +// result.wait((int)(CONNECTION_TIMEOUT * 1.1)); +// long end = System.currentTimeMillis(); +// long time = end - start; +// p.destroy(); +// try { +// Thread.sleep(DESTROY_TIMEOUT); +// } catch (InterruptedException interruptedException) { +// Logger.getLogger(RobotExecutorTest.class.getName()).log(Level.SEVERE, null, interruptedException); +// } +// if (Math.abs(time - CONNECTION_TIMEOUT) > CONNECTION_TIMEOUT * 0.3) { +// fail("Application finished with time (" + time + ") more than 30% different from timeout (" + CONNECTION_TIMEOUT + ")"); +// } +// } +// } catch (IOException ex) { +// throw new JemmyException("Failed to start other JVM", ex); +// } +// } +// +// /** +// * Test of main method, of class RobotExecutor. +// */ +// @Test +// public void testConnectToAlreadyRunningServer() throws InterruptedException { +// System.out.println("ConnectToAlreadyRunningServer"); +// Process pp = null; +// try { +// ProcessBuilder pb = new ProcessBuilder("java", "-cp", System.getProperty("java.class.path"), "-D" + Environment.JEMMY_PROPERTIES_FILE_PROPERTY + "=" + props.getCanonicalPath(), RobotExecutor.class.getName()); +// pb.redirectErrorStream(true); +// final Process p = pb.start(); +// pp = p; +// new Thread() { +// +// @Override +// public void run() { +// BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); +// while (true) { +// try { +// String line = br.readLine(); +// if (line == null) { +// break; +// } +// System.out.println("SERVER: " + line); +// } catch (IOException ex) { +// throw new JemmyException("Exception during other JVM output processing", ex); +// } +// } +// } +// }.start(); +// RobotExecutor re = RobotExecutor.get(); +// re.setRunInOtherJVM(true); +// re.createScreenCapture(new Rectangle(0, 0, 10, 10)); +// re.exit(); +// } catch (IOException ex) { +// throw new JemmyException("Failed to start other JVM", ex); +// } finally { +// if (pp != null) { +// pp.destroy(); +// Thread.sleep(DESTROY_TIMEOUT); +// } +// } +// } +// +// /** +// * Test of synchronizeRobot method, of class RobotExecutor. +// */ +// @Test +// public void testSynchronizeRobot() { +// System.out.println("synchronizeRobot"); +// RobotExecutor instance = new RobotExecutor(); +// instance.synchronizeRobot(); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setAutoDelay method, of class RobotExecutor. +// */ +// @Test +// public void testSetAutoDelay() { +// System.out.println("setAutoDelay"); +// Timeout autoDelay = null; +// RobotExecutor instance = new RobotExecutor(); +// instance.setAutoDelay(autoDelay); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of isRunInOtherJVM method, of class RobotExecutor. +// */ +// @Test +// public void testIsRunInSeparateJVM() { +// System.out.println("isRunInOtherJVM"); +// RobotExecutor instance = new RobotExecutor(); +// boolean expResult = false; +// boolean result = instance.isRunInOtherJVM(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } +// +// /** +// * Test of setRunInOtherJVM method, of class RobotExecutor. +// */ +// @Test +// public void testSetRunInSeparateJVM() { +// System.out.println("setRunInOtherJVM"); +// boolean runInSeparateJVM = false; +// RobotExecutor instance = new RobotExecutor(); +// instance.setRunInOtherJVM(runInSeparateJVM); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } + +} \ No newline at end of file diff --git a/core/JemmyAWTInput/test/org/jemmy/input/SmoothMoveTest.java b/core/JemmyAWTInput/test/org/jemmy/input/SmoothMoveTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/input/SmoothMoveTest.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.jemmy.input; + + +import java.awt.AWTException; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.Robot; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import org.jemmy.Point; +import org.jemmy.control.Wrap; +import org.jemmy.env.Environment; +import org.jemmy.env.Timeout; +import org.jemmy.image.AWTImage; +import org.jemmy.timing.State; +import org.jemmy.timing.Waiter; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertTrue; + + +/** + * + * @author Alexander Kouznetsov + */ +public class SmoothMoveTest { + + final static Timeout TIMEOUT = new Timeout("Wait for state to be reached", 10000); + final static Timeout DELTA_TIMEOUT = new Timeout("Delta timeout of wait for state to be reached", 1000); + + public SmoothMoveTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + File workdir = new File(System.getProperty("user.dir") + File.separator + + "build" + File.separator + + "test" + File.separator + + "results"); + workdir.mkdirs(); + AWTImage.setImageRoot(workdir); + } + + @AfterClass + public static void tearDownClass() throws Exception { + RobotDriver.exit(); + } + + Frame frm; + Button btn; + Wrap area; + private volatile boolean mousePressed; + private volatile boolean mouseReleased; + private volatile boolean mouseMoved; + private volatile boolean mouseClicked; + private volatile boolean mouseDragged; + private volatile boolean keyPressed; + private volatile boolean keyReleased; + RobotDriver instance; + Robot rb; + + @BeforeMethod + public void setUp() throws InterruptedException, AWTException, InvocationTargetException { + + Environment.getEnvironment().setProperty(AWTRobotInputFactory.ROBOT_MOUSE_SMOOTHNESS_PROPERTY, "5"); + + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm = new Frame("some frame"); + frm.setSize(100, 100); + frm.setLocation(100, 100); + btn = new Button("some button"); + MouseAdapter m = new MouseAdapter() { + + @Override + public void mousePressed(MouseEvent e) { + System.out.println("mousePressed event triggered: " + e); + System.out.flush(); + if ((e.getButton() & MouseEvent.BUTTON1) != 0 && e.isShiftDown()) { + mousePressed = true; + } + } + + @Override + public void mouseReleased(MouseEvent e) { + System.out.println("mouseReleased event triggered: " + e); + System.out.flush(); + if ((e.getButton() & MouseEvent.BUTTON2) != 0 && e.isControlDown()) { + mouseReleased = true; + } + } + + @Override + public void mouseMoved(MouseEvent e) { + System.out.println("mouseMoved event triggered: " + e); + System.out.flush(); + mouseMoved = true; + } + + @Override + public void mouseClicked(MouseEvent e) { + System.out.println("mouseClicked event triggered: " + e); + System.out.flush(); + if ((e.getButton() & MouseEvent.BUTTON3) != 0 && e.isAltDown()) { + mouseClicked = true; + } + } + + @Override + public void mouseDragged(MouseEvent e) { + System.out.println("mouseDragged event triggered: " + e); + System.out.flush(); + if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) != 0) { + mouseDragged = true; + } + } + + }; + btn.addMouseListener(m); + btn.addMouseMotionListener(m); + btn.addKeyListener(new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent e) { + System.out.println("keyPressed event triggered: " + e); + System.out.flush(); + if (e.getKeyCode() == KeyEvent.VK_A && e.isShiftDown()) { + keyPressed = true; + } + } + + @Override + public void keyReleased(KeyEvent e) { + System.out.println("keyReleased event triggered: " + e); + System.out.flush(); + if (e.getKeyCode() == KeyEvent.VK_B) { + keyReleased = true; + } + } + + }); + frm.add(btn, BorderLayout.SOUTH); + frm.doLayout(); + instance = new RobotDriver(Environment.getEnvironment()); + frm.setVisible(true); + btn.requestFocusInWindow(); + } + }); + + rb = new Robot(); + rb.waitForIdle(); + + RobotExecutor.get().setRunInOtherJVM(false); + } + + @AfterMethod + public void tearDown() throws InterruptedException, InvocationTargetException { + EventQueue.invokeAndWait(new Runnable() { + + public void run() { + frm.setVisible(false); + } + }); + } + + /** + * Test of moveMouse method in right-down direction of class RobotDriver. + */ + @Test + public void testMoveSmoothMousePP() throws InterruptedException { + System.out.println("testMoveSmoothMousePP"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x - 10, locationOnScreen.y - 10); + Point endPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, locationOnScreen.y + btn.getHeight() + 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method in left-down direction of class RobotDriver. + */ + @Test + public void testMoveSmoothMouseNP() throws InterruptedException { + System.out.println("testMoveSmoothMouseNP"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, locationOnScreen.y - 10); + Point endPoint = new Point(locationOnScreen.x - 10, locationOnScreen.y + btn.getHeight() + 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method in left-up direction of class RobotDriver. + */ + @Test + public void testMoveSmoothMouseNN() throws InterruptedException { + System.out.println("testMoveSmoothMouseNN"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, locationOnScreen.y + btn.getHeight() + 10); + Point endPoint = new Point(locationOnScreen.x - 10, locationOnScreen.y - 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method in right-up direction of class RobotDriver. + */ + @Test + public void testMoveSmoothMousePN() throws InterruptedException { + System.out.println("testMoveSmoothMousePN"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x - 10, locationOnScreen.y + btn.getHeight() + 10); + Point endPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, locationOnScreen.y - 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method along X axis of class RobotDriver. + */ + @Test + public void testMoveSmoothMouseX() throws InterruptedException { + System.out.println("testMoveSmoothMouseX"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x - 10, + locationOnScreen.y + btn.getHeight() / 2); + Point endPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, + locationOnScreen.y + btn.getHeight() / 2); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method along Y axis of class RobotDriver. + */ + @Test + public void testMoveSmoothMouseY() throws InterruptedException { + System.out.println("testMoveSmoothMouseY"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x + btn.getWidth() / 2, + locationOnScreen.y - 10); + Point endPoint = new Point(locationOnScreen.x + btn.getWidth() / 2, + locationOnScreen.y + btn.getHeight() + 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + + /** + * Test of moveMouse method with smooth set to false, of class RobotDriver. + */ + @Test + public void testMoveSmoothMouseDifferentDrivers() throws InterruptedException { + System.out.println("testMoveSmoothMouseDifferentDrivers"); + Thread.sleep(3000); + mouseMoved = false; + java.awt.Point locationOnScreen = btn.getLocationOnScreen(); + System.out.println("Moving mouse"); + Point startPoint = new Point(locationOnScreen.x + btn.getWidth() + 10, locationOnScreen.y - 10); + Point endPoint = new Point(locationOnScreen.x - 10, locationOnScreen.y + btn.getHeight() + 10); + instance.moveMouse(startPoint); + Thread.sleep(100); + instance = new RobotDriver(Environment.getEnvironment()); + instance.moveMouse(endPoint); + + rb.waitForIdle(); + new Waiter(TIMEOUT, DELTA_TIMEOUT).ensureState(new State(){ + + public Boolean reached() { + return mouseMoved ? true : null; + } + + }); + assertTrue(mouseMoved); + } + +} \ No newline at end of file diff --git a/core/JemmyAWTInput/test/org/jemmy/operators/ScreenTest.java b/core/JemmyAWTInput/test/org/jemmy/operators/ScreenTest.java new file mode 100644 --- /dev/null +++ b/core/JemmyAWTInput/test/org/jemmy/operators/ScreenTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.jemmy.operators; + +import org.jemmy.Point; +import org.jemmy.env.*; +import org.jemmy.Rectangle; +import org.jemmy.control.Wrap; +import org.jemmy.input.AWTRobotInputFactory; +import org.jemmy.operators.Screen; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * @author shura + */ +public class ScreenTest { + + public ScreenTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + Environment.getEnvironment().setInputFactory(new AWTRobotInputFactory()); + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @BeforeMethod + public void setUp() { + } + + @AfterMethod + public void tearDown() { + } + + // TODO add test methods here. + // The methods must be annotated with annotation @Test. For example: + // + @Test + public void testMouseMove() { + Wrap screen = new AWTScreen(Environment.getEnvironment()); + screen.mouse().move(); + screen.mouse().move(new Point(0, 0)); + screen.mouse().move(new Point(screen.getScreenBounds().width, screen.getScreenBounds().height)); + } + +} \ No newline at end of file