Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does java.awt.Robot.waitForIdle() wait for events to be dispatched?

I'm using java.awt.Robot for integration tests of my Swing application, but I'm having trouble running my actions in the correct order. How can I tell the thread that calls robot.mousePressed(...) to block until Swing is finished dispatching that event? Apparently, robot.setAutoWaitForIdle(true) does no good.

Here's my demo. I expect the "robot finished!" message to always come after "Action finished blocking.", but instead it often happens too soon instead.

import java.awt.AWTException;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.sql.Date;
import java.text.DateFormat;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;


public class RobotWaitForIdleDemo {
    /**
     * Create the device that contains the given point in screen coordinates.
     * Robot has to be constructed differently for each monitor.
     */
    public static GraphicsDevice getDevice(Point p) {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] gs = ge.getScreenDevices();

        // Search the devices for the one that draws the specified point.
        for (GraphicsDevice device : gs) {
            GraphicsConfiguration configuration = device.getDefaultConfiguration();
            Rectangle bounds = configuration.getBounds();
            if (bounds.contains(p)) {
                return device;
            }
        }
        return null;
    }
    public static final Logger logger = Logger.getLogger(RobotWaitForIdleDemo.class.getName());
    public static void main(String[] args) {
        LogManager.getLogManager().reset();
        Formatter formatter = new Formatter() {
            @Override
            public String format(LogRecord arg0) {
                Date date = new Date(arg0.getMillis());
                DateFormat.getTimeInstance().format(date);
                return String.format("%s %s %s %s%n",
                        DateFormat.getTimeInstance().format(date),
                        arg0.getLoggerName(),
                        arg0.getLevel(),
                        arg0.getMessage());
            }
        };
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(formatter);
        logger.addHandler(consoleHandler);

        final JFrame jframe = new JFrame("Robot experiment");
        GroupLayout groupLayout = new GroupLayout(jframe.getContentPane());

        final JButton jbutton = new JButton("Click me!");
        jbutton.addActionListener(new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                // Simulate a heavy Swing event handler.
                logger.info("(swing thread) Action starting to block...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e1) {}
                logger.info("(swing thread) Action finished blocking.");
            }
        });

        JButton tryAgainBUtton = new JButton("Automatically click above button.");
        tryAgainBUtton.addActionListener(new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                new Thread(new Runnable() {
                    @Override public void run() {
                        try {
                            Point point = new Point(jbutton.getWidth()/2,jbutton.getHeight()/2);
                            SwingUtilities.convertPointToScreen(point, jbutton);
                            GraphicsDevice device = getDevice(point);
                            Point offset = device.getDefaultConfiguration().getBounds().getLocation();

                            Robot robot = new Robot(device);
                            robot.setAutoWaitForIdle(true);
                            robot.setAutoDelay(30);

                            robot.mouseMove(point.x - offset.x, point.y - offset.y);
                            String threadName = Thread.currentThread().getName();
                            logger.info(String.format("(%s) robot.mousePress(%d)", threadName, InputEvent.BUTTON1_MASK));
                            robot.mousePress(InputEvent.BUTTON1_MASK);
                            logger.info(String.format("(%s) robot.mouseRelease(%d)", threadName, InputEvent.BUTTON1_MASK));
                            robot.mouseRelease(InputEvent.BUTTON1_MASK);
                            logger.info(String.format("(%s) robot finished!", threadName, InputEvent.BUTTON1_MASK));
                        } catch (AWTException ex) {
                            ex.printStackTrace();
                        }
                    }
                }, "robot thread").start();
            }
        });

        jframe.getContentPane().setLayout(groupLayout);
        groupLayout.setAutoCreateGaps(true);
        groupLayout.setAutoCreateContainerGaps(true);
        groupLayout.setVerticalGroup(
                groupLayout.createSequentialGroup()
                    .addComponent(jbutton)
                    .addComponent(tryAgainBUtton));
        groupLayout.setHorizontalGroup(
                groupLayout.createParallelGroup()
                    .addComponent(jbutton)
                    .addComponent(tryAgainBUtton)                           );

        jframe.setSize(300, 300);
        jframe.setVisible(true);
        jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    }
}

I'm running Java 1.6 on Ubuntu.

like image 531
yonran Avatar asked Jun 14 '12 23:06

yonran


People also ask

How does Java Robot work?

The primary purpose of Robot is to facilitate automated testing of Java platform implementations. Using the class to generate input events differs from posting events to the AWT event queue or AWT components in that the events are generated in the platform's native input queue.

What is Java AWT Robot?

The Robot class in the Java AWT package is used to generate native system input events for the purposes of test automation, self-running demos, and other applications where control of the mouse and keyboard is needed. The primary purpose of Robot is to facilitate automated testing of Java platform implementations.

What is the difference between action class and Robot class?

There are plenty of differences between the two. Selenium uses WebDriver API and sends commands to browser driver to perform actions (through the "JSON wire protocol"). However, Java AWT Robot uses native system events to control keyboard and mouse operations.

What is VK in Robot class?

VK_META and META_MASK are defined in KeyEvent and InputEvent classes. They both define the META key as a standalone key pressed and as a modifier used pressing another key respectively. The META key is a key used in old keyboards and now can be emulated using the Windows Key.


1 Answers

maybe this one can help you, notice not tested in Java7

you can test that in each of steps for isEventDispatchThread()

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import sun.awt.SunToolkit;

public class TestMenu {

    /**
     * Without a delay, SunToolkit may encounter a problem in SunToolkit (at
     * least in JDK 6, where the drop down size problem is not present).
     * 
     * Note: SunToolkit also has some mechanism to delay, but I forgot how it
     * worked.
     * 
     * <pre>
     * Exception in thread "main" sun.awt.SunToolkit$InfiniteLoop
     *         at sun.awt.SunToolkit.realSync(Unknown Source)
     *         at TestMenu.syncAndDelay(TestMenu.java:172)
     *         at TestMenu.click(TestMenu.java:88)
     *         at TestMenu.moveAndClickCenter(TestMenu.java:150)
     *         at TestMenu.main(TestMenu.java:45)
     * </pre>
     * 
     * As a bonus, the delay makes the scenario better visible for the human
     * eye.
     */
    private static int delay = 500;
    private static JMenu[] menus = new JMenu[5];
    private static Dimension[] parentSizes;
    private static Robot robot;
    private static SunToolkit toolkit;

    public static void main(String[] args) throws Exception {
        robot = new Robot();
        toolkit = (SunToolkit) Toolkit.getDefaultToolkit();
        parentSizes = new Dimension[menus.length];
        createGUI(); // Open the first menu. Then get the drop down size of all menu's
        moveAndClickCenter(menus[0]);
        for (int index = 0; index < menus.length; index++) {
            parentSizes[index] = getDropDownSize(index);
        }// Click the last item on the last menu.        
        Component item = menus[menus.length - 1].getMenuComponent(menus[menus.length - 1].getMenuComponentCount() - 1);
        moveAndClickCenter(item);
        // Open the last drop down again. Then get the drop down sizes once more. If size not equal to previous size, then it's a bug.
        boolean bug = false;
        moveAndClickCenter(menus[menus.length - 1]);
        for (int index = menus.length - 1; index >= 0; index--) {
            Dimension currentSize = getDropDownSize(index);
            System.out.print("old: " + parentSizes[index] + ", new: " + currentSize);
            if (!parentSizes[index].equals(currentSize)) {
                bug = true;
                System.out.println(" ERROR");
            } else {
                System.out.println();
            }
        }
        if (bug) {
            throw new RuntimeException("JMenu drop down size is changed for no reason.");
        }

    }

    private static Dimension getDropDownSize(int index) throws Exception {
        moveToCenter(menus[index]);
        return menus[index].getMenuComponent(0).getParent().getSize();
    }

    private static void click() throws Exception {
        robot.mousePress(InputEvent.BUTTON1_MASK);
        robot.mouseRelease(InputEvent.BUTTON1_MASK);
        syncAndDelay();
    }

    private static void createGUI() throws Exception {

        SwingUtilities.invokeAndWait(new Runnable() {

            @Override
            public void run() {
                UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();// The L&F defines the drop down policy.
                for (final UIManager.LookAndFeelInfo info : infos) {
                    if (info.getName().toLowerCase().indexOf("metal") >= 0) {
                        if (!UIManager.getLookAndFeel().getName().equals(info.getName())) {
                            try {
                                UIManager.setLookAndFeel(info.getClassName());
                                System.out.println("Attempt to set look and feel to " + info.getName());
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        } else {
                            System.out.println("Metal look and feel is the default");
                        }
                        break;
                    }
                }
                System.out.println("Testing with " + UIManager.getLookAndFeel().getName());  // Setup the GUI.
                JFrame frame = new JFrame("A frame");
                frame.setJMenuBar(new JMenuBar());
                for (int menuIndex = 0; menuIndex < menus.length; menuIndex++) {
                    menus[menuIndex] = new JMenu("Menu " + menuIndex);
                    frame.getJMenuBar().add(menus[menuIndex]);
                    for (int itemIndex = 0; itemIndex <= menus.length - menuIndex; itemIndex++) {
                        // It seems that the problem only occurs if the drop down is displayed outside the frame at the right
                        // (not sure though). A rather long item name.
                        JMenuItem item = new JMenuItem("Menu " + menuIndex + " item " + itemIndex);
                        menus[menuIndex].add(item);
                    }
                }
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
        syncAndDelay();
    }

    private static void moveAndClickCenter(Component c) throws Exception {
        moveToCenter(c);
        click();
    }

    private static void moveToCenter(final Component c) throws Exception {
        final Point cp = new Point();
        SwingUtilities.invokeAndWait(new Runnable() {

            @Override
            public void run() {
                Point p = new Point(c.getWidth() / 2, c.getHeight() / 2);
                SwingUtilities.convertPointToScreen(p, c);
                cp.setLocation(p);
            }
        });
        robot.mouseMove(cp.x, cp.y);
        syncAndDelay();
    }

    private static void syncAndDelay() throws Exception {
        if (delay > 0) {
            Thread.sleep(delay);
        }
        toolkit.realSync();
    }

    private TestMenu() {
    }
}
like image 171
mKorbel Avatar answered Oct 19 '22 09:10

mKorbel