view src/org/thermostat/qa/framework/GuiRobot.java @ 150:3d06e2d323cb

refactoring of the framework+testsuites for gui tests so Xvfb+fluxbox may be used
author Jana Fabrikova <jfabriko@redhat.com>
date Tue, 01 Apr 2014 15:09:08 +0200
parents 49c76d19d687
children 2285461bec0c
line wrap: on
line source

/*

    ThermostatQA - test framework for Thermostat Monitoring Tool

    Copyright 2013 Red Hat, Inc.

This file is part of ThermostatQA

ThermostatQA is distributed under the GNU General Public License,
version 2 or any later version (with a special exception described
below, commonly known as the "Classpath Exception").

A copy of GNU General Public License (GPL) is included in this
distribution, in the file COPYING.

Linking ThermostatQA code with other modules is making a combined work
based on ThermostatQA.  Thus, the terms and conditions of the GPL
cover the whole combination.

As a special exception, the copyright holders of ThermostatQA give you
permission to link this code with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on ThermostatQA code.  If you modify ThermostatQA, you may
extend this exception to your version of the software, but you are
not obligated to do so.  If you do not wish to do so, delete this
exception statement from your version.
*/

package org.thermostat.qa.framework;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;









import javax.imageio.ImageIO;









import org.thermostat.qa.common.Configuration;
import org.thermostat.qa.common.ScreenshotSourceType;



/**
 * Class used for automatic mouse and keyboard system events generation. This
 * class also contains methods for capturing the screenshot and for finding some
 * pattern in that screenshot.
 * 
 * @author Pavel Tisnovsky
 */
public class GuiRobot
{

    /**
     * Number of millisecond between two consecutive robot actions.
     */
    private static final int ROBOT_DELAY = 100;

    /**
     * Number of millisecond for key press event. 
     */
    private static final int KEY_PRESS_DELAY = 10;

    Robot robot = null;

    BufferedImage screenCapture = null;
    
    Configuration config = null;
    ScreenshotSourceType scrSource = null;

    public GuiRobot(Configuration c) throws AWTException
    {
        this.robot = new Robot();
        this.config = c;
        this.scrSource = c.getScreenshotsSource();
    }

    /**
     * @return the robot
     */
    public Robot getRobot()
    {
        return this.robot;
    }

    /**
     * @param robot the robot to set
     */
    public void setRobot(Robot robot)
    {
        this.robot = robot;
    }

    /**
     * @return the screenCapture
     */
    public BufferedImage getScreenCapture()
    {
        return this.screenCapture;
    }

    
    
    public Configuration getConfig() {
        return config;
    }

    public void setConfig(Configuration config) {
        this.config = config;
    }

    public ScreenshotSourceType getScrSource() {
        return scrSource;
    }

    public void setScrSource(ScreenshotSourceType scrSource) {
        this.scrSource = scrSource;
    }

    /**
     * @param screenCapture the screenCapture to set
     */
    public void setScreenCapture(BufferedImage screenCapture)
    {
        this.screenCapture = screenCapture;
    }

    public void prepareScreenshot(String fileName) throws IOException
    {
        switch(scrSource){
            case DUMMY:
                loadScreenshot(fileName);
                break;
            case NORMAL:
                createScreenCapture();
                saveScreenshot(fileName);
                break;
            case XVFB:
                createXvbfScreenCaptureFile(fileName);
                loadScreenshot(fileName);
                break;
        };
    }

    /*using import command*/
    private void createXvbfScreenCaptureFile(String fileName) throws IOException
    {
        //import -display :1 -window root screenshots/fileName.png
        List<String> content = new LinkedList<String>();
        content.add("import -display :1 -window root screenshots/"+fileName+".png");
        ThermostatUtilities.runBashScriptWithContent("xvfbScreenCapture.sh", content);
    }
    
    private void createScreenCapture()
    {
        Rectangle screenRectangle = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
        setScreenCapture(getRobot().createScreenCapture(screenRectangle));
    }

    public void saveScreenshot(String fileName) throws IOException
    {
        ImageIO.write(getScreenCapture(), "png", new File("screenshots/" + fileName + ".png"));
    }

    public void saveImage(String fileName) throws IOException
    {
        ImageIO.write(getScreenCapture(), "png", new File(fileName));
    }

    public void loadScreenshot(String fileName) throws IOException
    {
        loadImage("screenshots/" + fileName + ".png");
    }

    public void loadImage(String fileName) throws IOException
    {
        setScreenCapture(ImageIO.read(new File(fileName)));
    }

    public Rectangle findPattern(String markerFileName) throws IOException
    {
        BufferedImage marker = ImageIO.read(new File(markerFileName));
        return ImageProcessing.findPattern(marker, getScreenCapture());
    }

    public void highlightRectangle(Rectangle rect)
    {
        highlightRectangle(getScreenCapture(), rect);
    }

    public void clickToRectangle(Rectangle rectangle)
    {
        if (rectangle == null)
        {
            return;
        }

        int x = rectangle.x + rectangle.width / 2;
        int y = rectangle.y + rectangle.height / 2;
        click(x, y);
    }

    /**
     * Method click has
     * 3 cases - for NORMAL screenshot source moves the regular mouse,
     * for XVFB uses the import command, for DUMMY does nothing
     * 
     * @param x
     * @param y
     */
    private void click(int x, int y) {
        
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //operate with the mouse on regular display
                this.robot.mouseMove(x, y);
                this.robot.delay(ROBOT_DELAY);
                this.robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
                this.robot.delay(ROBOT_DELAY);
                this.robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
                break;
                
            case XVFB:
                //use xdotool to click on xvfb screen
                //export DISPLAY=:1
                //xdotool mousemove x y
                //xdotool click --clearmodifiers 1 
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool mousemove "+x+" "+y);
                content.add("xdotool click --clearmodifiers 1");
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbScreenCapture.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };
    }

    private void pressAWTKey(int keycode)
    {           //send keys via awt robot
                robot.keyPress(keycode);
                robot.delay(KEY_PRESS_DELAY);
                robot.keyRelease(keycode);
    }

    /**
     * Method press2Keys uses AWT robot to 
     * press key2 while key1 is pressed, then release both.
     * Can be used e.g. to press key1=CTRL and key2=S to get CTRL+S.
     * 
     * @param key1
     * @param key2
     */
    public void press2AWTKeys(int key1, int key2)
    {
        //send keys via awt robot
        robot.keyPress(key1);
        robot.delay(KEY_PRESS_DELAY);
        robot.keyPress(key2);
        robot.delay(KEY_PRESS_DELAY);
        robot.keyRelease(key2);
        robot.delay(KEY_PRESS_DELAY);
        robot.keyRelease(key1);
    }
    
    /**
     * Method pressCtrlPlusSmallLetter
     * gets one parameter - char letter, works for both normal screenshots
     * (then it uses AWT robot) and for Xvfb (then xdotool is used).
     * 
     * @param c
     */
    public void pressCtrlPlusSmallLetter(char c)
    {
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                int keycode = KeyEvent.VK_A + c - 'a';
                press2AWTKeys(KeyEvent.VK_CONTROL, keycode);
                break;
                
            case XVFB:
                //TODO - does this work with the given keycodes?
                //use xdotool to type for xvfb
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool key Ctrl+"+(char)c);
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbPressCtrlAndSmallLetter.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };        

    }

    /**
     * Method resize
     * 3 cases - does nothing if screenshot config is DUMMY, 
     * - gets a small rectangle where the right lower corner of the gui is,
     * and number of x and y pixels by which the window should be resized.
     * It uses mouse and resizes the gui window, while adding sufficient
     * time delays between the mouse actions,
     * - uses xdotool to resize the active window
     * 
     * @param from
     * @param xd
     * @param yd
     */
    public void resize(Rectangle from, int xd, int yd)
    {
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                int x = from.x + from.width - 4;
                int y = from.y + from.height - 4;
                robot.mouseMove(x, y);
                robot.delay(ROBOT_DELAY);
                x += 4;
                y += 4;
                robot.mouseMove(x, y);
                robot.delay(ROBOT_DELAY);
                robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
                robot.delay(ROBOT_DELAY);
                robot.mouseMove(x+xd, y+yd);
                robot.delay(ROBOT_DELAY);
                robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
                break;
                
            case XVFB:
                //TODO - insert code with xdotool for (activating and)
                //resizing the thermostat window
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                //content.add("xdotool click "+(from.x + from.width<<1)+" "+(from.y + from.height<<1));
                content.add("xdotool windowsize --usehints getactivewindow "+(from.width + xd)+" "+(from.height + yd));
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbResize.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };        
    }
    
    /**
     * Add some offset to a rectangle.
     * 
     * @param rect
     *            input rectangle (it's not changed)
     * @param offset
     *            offset, if greater than zero, the new rectangle is bigger than
     *            original one
     * @return new rectangle
     */
    private static Rectangle getRectangleWithOffset(Rectangle rect, int offset)
    {
        int x = rect.x - offset;
        int y = rect.y - offset;
        int width = rect.width + (offset << 1);
        int height = rect.height + (offset << 1);
        return new Rectangle(x, y, width, height);
    }

    /**
     * Highlight rectangle around the marker in the tested image.
     * 
     * @param testImage
     *            tested image
     * @param rect
     *            rectangle around the marker found in the tested image
     */
    private static void highlightRectangle(BufferedImage testImage, Rectangle rect)
    {
        Graphics2D graphics = (Graphics2D) testImage.getGraphics();
        Rectangle rect1 = getRectangleWithOffset(rect, 2);
        Rectangle rect2 = getRectangleWithOffset(rect, 4);
        graphics.setColor(Color.RED);
        graphics.draw(rect1);
        graphics.setColor(Color.BLACK);
        graphics.draw(rect2);
    }

    public void pressFNKey(int N) {
        
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                int keycode = KeyEvent.VK_F1 + N -1;
                pressAWTKey(keycode);
                break;
                
            case XVFB:
                //xdotool command for entering FN
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool key -clearmodifiers F"+N);
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbResize.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };        
    }

    public void pressRightKey() {
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                pressAWTKey(KeyEvent.VK_RIGHT);
                break;
                
            case XVFB:
                //TODO - does this work with the given keycodes?
                //use xdotool to type for xvfb
                //export DISPLAY=:1
                //xdotool key right
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool key -clearmodifiers Right");
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbPressRightKey.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };  
        
    }

    public void pressLeftKey() {
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                pressAWTKey(KeyEvent.VK_LEFT);
                break;
                
            case XVFB:
                //TODO - does this work with the given keycodes?
                //use xdotool to type for xvfb
                //export DISPLAY=:1
                //xdotool key right
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool key -clearmodifiers Left");
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbPressLeftKey.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };          
    }

    public void pressDownKey() {
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                pressAWTKey(KeyEvent.VK_DOWN);
                break;
                
            case XVFB:
                //TODO - does this work with the given keycodes?
                //use xdotool to type for xvfb
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool key -clearmodifiers Down");
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbPressDownKey.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        };  
    }

    public void pressEnterKey() {
        switch(scrSource){
            case DUMMY:
                //do nothing
                break;
                
            case NORMAL:
                //send keys via awt robot
                pressAWTKey(KeyEvent.VK_ENTER);
                break;
                
            case XVFB:
                //TODO - does this work with the given keycodes?
                //use xdotool to type for xvfb
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool key -clearmodifiers Return");
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbPressEnterKey.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        }; 
    }

    public void writeSmallLettersText(String string) {
        // TODO vyzkouset
        switch(scrSource)
        {
            case DUMMY:
                //do nothing;
                break;
            case NORMAL:
                //type letters successively using awt robot
                for(int i=0; i<string.length(); i++)
                {
                    int keycode = string.charAt(i) - 'a' + KeyEvent.VK_A;
                    pressAWTKey(keycode);
                }
                break;
            case XVFB:
                //type whole word using xdotool
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool type -clearmodifiers "+string);
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbWriteSmallLettersText.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        }
    }

    public void writeSpecialChar(char c) {
        // TODO vyzkouset, jak to funguje
        switch(scrSource)
        {
            case DUMMY:
                //do nothing;
                break;
            case NORMAL:
                switch(c){
                    case '-':
                        pressAWTKey(KeyEvent.VK_MINUS);
                        break;
                }
                break;
            case XVFB:
                //type the character using xdotool
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool type -clearmodifiers "+(char)c);
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbWriteSpecialChar.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        }
    }

    public void writeNumbersText(String string) {
        // TODO vyzkouset
        switch(scrSource)
        {
            case DUMMY:
                //do nothing;
                break;
            case NORMAL:
                //type letters successively using awt robot
                for(int i=0; i<string.length(); i++)
                {
                    int keycode = string.charAt(i) - '0' + KeyEvent.VK_0;
                    pressAWTKey(keycode);
                }
                break;
            case XVFB:
                //type whole word using xdotool
                List<String> content = new LinkedList<String>();
                content.add("export DISPLAY=:1");
                content.add("xdotool type -clearmodifiers "+string);
                try
                {
                    ThermostatUtilities.runBashScriptWithContent("xvfbWriteNumbersText.sh", content);
                }
                catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                break;
        }
    }

}