/*******************************************************************************
* Copyright (c) 2013 BREDEX GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.rc.javafx.tester;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jubula.rc.common.AUTServer;
import org.eclipse.jubula.rc.common.driver.IRobot;
import org.eclipse.jubula.rc.common.exception.RobotException;
import org.eclipse.jubula.rc.common.exception.StepExecutionException;
import org.eclipse.jubula.rc.common.logger.AutServerLogger;
import org.eclipse.jubula.rc.common.tester.AbstractApplicationTester;
import org.eclipse.jubula.rc.common.util.KeyStrokeUtil;
import org.eclipse.jubula.rc.common.util.MatchUtil;
import org.eclipse.jubula.rc.common.util.Verifier;
import org.eclipse.jubula.rc.javafx.components.CurrentStages;
import org.eclipse.jubula.rc.javafx.driver.EventThreadQueuerJavaFXImpl;
import org.eclipse.jubula.rc.javafx.driver.RobotJavaFXImpl;
import org.eclipse.jubula.rc.javafx.listener.ComponentHandler;
import org.eclipse.jubula.rc.javafx.tester.util.Rounding;
import org.eclipse.jubula.tools.internal.objects.event.EventFactory;
import org.eclipse.jubula.tools.internal.objects.event.TestErrorEvent;
import org.eclipse.jubula.tools.internal.utils.TimeUtil;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import static org.eclipse.jubula.rc.common.driver.CheckWithTimeoutQueuer.invokeAndWait;
/**
* Tester-Class for the Application as a whole.
*
* @author BREDEX GmbH
* @created 30.10.2013
*/
public class JavaFXApplicationTester extends AbstractApplicationTester {
/**
* The logging.
*/
private static AutServerLogger log = new AutServerLogger(
JavaFXApplicationTester.class);
/**
* constructor to add the stage which has focus to our event confirming mechanism
*/
public JavaFXApplicationTester() {
//Add scene graphs to the event confirmer
for (Window w : CurrentStages.getWindowList()) {
((RobotJavaFXImpl) getRobot()).getInterceptor().addSceneGraph(
w.getScene().windowProperty());
}
CurrentStages.addStagesListener(new ListChangeListener<Window>() {
@Override
public void onChanged(
ListChangeListener.Change<? extends Window> c) {
if (c.next()) {
if (c.wasAdded()) {
for (Window win : c.getAddedSubList()) {
((RobotJavaFXImpl) getRobot())
.getInterceptor().addSceneGraph(
win.getScene().windowProperty());
}
}
}
}
});
}
@Override
public String[] getTextArrayFromComponent() {
return null;
}
@Override
public Rectangle getActiveWindowBounds() {
Window window = CurrentStages.getfocusStage();
Rectangle rec = new Rectangle(Rounding.round(window.getX()),
Rounding.round(window.getY()),
Rounding.round(window.getWidth()), Rounding.round(window
.getHeight()));
return rec;
}
@Override
protected IRobot getRobot() {
return AUTServer.getInstance().getRobot();
}
/**
* perform a keystroke specified according <a
* href=http://java.sun.com/j2se/1.4
* .2/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)>
* string representation of a keystroke </a>,
*
* @param modifierSpec
* the string representation of the modifiers
* @param keySpec
* the string representation of the key
*/
@Override
public void rcKeyStroke(String modifierSpec, String keySpec) {
if (keySpec == null || keySpec.trim().length() == 0) {
throw new StepExecutionException(
"The base key of the key stroke must not be null or empty", //$NON-NLS-1$
EventFactory.createActionError());
}
String key = keySpec.trim().toUpperCase();
String mod = KeyStrokeUtil.getModifierString(modifierSpec);
if (mod.length() > 0) {
getRobot().keyStroke(mod.toString() + " " + key); //$NON-NLS-1$
} else {
int code = getKeyCode(key);
if (code != -1) {
rcKeyType(code);
} else {
getRobot().keyStroke(key);
}
}
}
@Override
protected Object getFocusOwner() {
Object result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"getFocusOwner", new Callable<Object>() { //$NON-NLS-1$
@Override
public Object call() throws Exception {
Stage s = (Stage) getActiveWindow();
return s.getScene().getFocusOwner();
}
});
return result;
}
@Override
protected int getEventCode(int key) {
int event = 0;
switch (key) {
case 1:
event = KeyEvent.VK_NUM_LOCK;
break;
case 2:
event = KeyEvent.VK_CAPS_LOCK;
break;
case 3:
event = KeyEvent.VK_SCROLL_LOCK;
break;
default:
break;
}
return event;
}
@Override
protected Object getActiveWindow() {
return CurrentStages.getfocusStage();
}
/**
* @param keyCodeName
* The name of a key code, e.g. <code>TAB</code> for a tabulator
* key code
* @return The key code or <code>-1</code>, if the key code name doesn't
* exist in the <code>KeyEvent</code> class
* @throws StepExecutionException
* If the key code name cannot be converted to a key code due to
* the reflection call
*/
public int getKeyCode(String keyCodeName) throws StepExecutionException {
int code = -1;
String codeName = "VK_" + keyCodeName; //$NON-NLS-1$
try {
code = KeyEvent.class.getField(codeName).getInt(KeyEvent.class);
} catch (IllegalArgumentException e) {
throw new StepExecutionException(e.getMessage(),
EventFactory.createActionError());
} catch (SecurityException e) {
throw new StepExecutionException(e.getMessage(),
EventFactory.createActionError());
} catch (IllegalAccessException e) {
throw new StepExecutionException(e.getMessage(),
EventFactory.createActionError());
} catch (NoSuchFieldException e) {
if (log.isInfoEnabled()) {
log.info("The key expression '" + keyCodeName //$NON-NLS-1$
+ "' is not a key code, typed as key stroke instead"); //$NON-NLS-1$
}
}
return code;
}
/**
* Checks for the existence of a window with the given title
*
* @param title
* the title
* @param operator
* the comparing operator
* @param exists
* <code>True</code> if the window is expected to exist and be
* visible, otherwise <code>false</code>.
* @param timeout the amount of time to wait for the existence of the
* window to be checked
*/
public void rcCheckExistenceOfWindow(final String title, String operator,
boolean exists, int timeout) {
invokeAndWait("rcCheckExistenceOfWindow", timeout, new Runnable() { //$NON-NLS-1$
@Override
public void run() {
Verifier.equals(exists, isStageInHierarchy(title, operator));
}
});
}
/**
* Checks if the Window(Stage) is in in the Hierarchy and therefore open and
* accessible.
*
* @param title
* the title of the Stage to look for
* @param operator
* the operator
* @return true if the Stage is open, otherwise false
*/
private boolean isStageInHierarchy(final String title,
final String operator) {
// We are doing this on the JavaFX thread to avoid concurrent
// modification in the hierarchy map.
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"isWindowInHierarchy", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
Stage stage = getStageByTitle(title, operator);
return stage != null;
}
});
return result;
}
/**
* Looks through the Hierarchy for a Stage with a given Title.
*
* @param title
* the Title of the Stage to look for
* @param operator
* the operator
* @return the Stage or null
*/
private Stage getStageByTitle(final String title, final String operator) {
Stage result = null;
result = EventThreadQueuerJavaFXImpl.invokeAndWait("getStageByTitle", //$NON-NLS-1$
new Callable<Stage>() {
@Override
public Stage call() throws Exception {
List<? extends Stage> stages = ComponentHandler
.getAssignableFrom(Stage.class);
for (final Stage stage : stages) {
if (MatchUtil.getInstance().match(stage.getTitle(),
title, operator)) {
return stage;
}
}
return null;
}
});
return result;
}
/**
* Waits <code>timeMillSec</code> if the application opens a window with the
* given title.
*
* @param title
* the title
* @param operator
* the comparing operator
* @param pTimeout
* the time in ms
* @param delay
* delay after the window is shown
*/
public void rcWaitForWindow(final String title, String operator,
int pTimeout, int delay) {
Stage s = null;
try {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
do {
s = getStageByTitle(title, operator);
now = System.currentTimeMillis();
timeout = done - now;
Thread.sleep(50);
} while (timeout > 0 && s == null);
} catch (InterruptedException e) {
throw new RobotException(e);
}
if (s == null) {
log.error("no Window found! In rcWaitForWindowActivation. Title: " //$NON-NLS-1$
+ title + "operator: " + operator); //$NON-NLS-1$
throw new StepExecutionException("no Window found!", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.COMP_NOT_FOUND));
}
final Stage stage = s;
final CountDownLatch signal = new CountDownLatch(1);
final EventHandler<WindowEvent> showHandler =
new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
signal.countDown();
}
};
boolean isShowing = EventThreadQueuerJavaFXImpl.invokeAndWait(
"rcWaitForWindow", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
if (!stage.isShowing()) {
stage.addEventFilter(WindowEvent.WINDOW_SHOWN,
showHandler);
return false;
}
return true;
}
});
if (!isShowing) {
try {
signal.await(pTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new StepExecutionException(
"Interrupted while waiting for window!", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.
EXECUTION_ERROR));
} finally {
stage.removeEventFilter(WindowEvent.WINDOW_SHOWN, showHandler);
}
}
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"rcWaitForWindowConfirm", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
return stage.isShowing();
}
});
if (!result) {
throw new StepExecutionException("window did not open", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.TIMEOUT_EXPIRED));
}
TimeUtil.delay(delay);
}
/**
* Waits <code>timeMillSec</code> if the application activates a window with
* the given title.
*
* @param title
* the title
* @param operator
* the comparing operator
* @param pTimeout
* the time in ms
* @param delay
* delay after the window is activated
*/
public void rcWaitForWindowActivation(final String title, String operator,
int pTimeout, int delay) {
Stage s = null;
try {
long timeout = pTimeout;
long done = System.currentTimeMillis() + timeout;
long now;
do {
s = getStageByTitle(title, operator);
now = System.currentTimeMillis();
timeout = done - now;
Thread.sleep(50);
} while (timeout > 0 && s == null);
} catch (InterruptedException e) {
throw new RobotException(e);
}
if (s == null) {
log.error("no Window found! In rcWaitForWindowActivation. Title: " //$NON-NLS-1$
+ title + "operator: " + operator); //$NON-NLS-1$
throw new StepExecutionException("no Window found!", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.EXECUTION_ERROR));
}
final Stage stage = s;
final CountDownLatch signal = new CountDownLatch(1);
final ChangeListener<Boolean> focusListener =
new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean>
observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
signal.countDown();
}
}
};
boolean isFocused = EventThreadQueuerJavaFXImpl.invokeAndWait(
"rcWaitForWindowActivation", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
if (!stage.isFocused()) {
stage.focusedProperty().addListener(focusListener);
return false;
}
return true;
}
});
if (!isFocused) {
try {
signal.await(pTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new StepExecutionException(
"Interrupted while waiting for window activation!", //$NON-NLS-1$
EventFactory.createActionError(TestErrorEvent.
EXECUTION_ERROR));
} finally {
stage.focusedProperty().removeListener(focusListener);
}
}
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"rcWaitForWindowActivationConfirm", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
return stage.isFocused();
}
});
if (!result) {
throw new StepExecutionException("window was not activated", //$NON-NLS-1$
EventFactory
.createActionError(TestErrorEvent.TIMEOUT_EXPIRED));
}
TimeUtil.delay(delay);
}
/**
* Waits <code>timeMillSec</code> if the application closes (or hides) a
* window with the given title. If no window with the given title can be
* found, then it is assumed that the window has already closed.
*
* @param title
* the title
* @param operator
* the comparing operator
* @param pTimeout
* the time in ms
* @param delay
* delay after the window is closed
*/
public void rcWaitForWindowToClose(final String title,
final String operator, int pTimeout, int delay) {
final Stage s = getStageByTitle(title, operator);
if (s == null) {
return;
}
final CountDownLatch signal = new CountDownLatch(1);
final EventHandler<WindowEvent> closeHandler =
new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
signal.countDown();
}
};
boolean isClosing = EventThreadQueuerJavaFXImpl.invokeAndWait(
"rcWaitForWindowToClose", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
if (s.isShowing()) {
s.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST,
closeHandler);
return false;
} else {
return true;
}
}
});
if (!isClosing) {
try {
signal.await(pTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new StepExecutionException(
"Interrupted while waiting for window closing!", //$NON-NLS-1$
EventFactory
.createActionError(
TestErrorEvent.EXECUTION_ERROR));
} finally {
s.removeEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST,
closeHandler);
}
}
boolean result = EventThreadQueuerJavaFXImpl.invokeAndWait(
"rcWaitForWindowToCloseConfirm", new Callable<Boolean>() { //$NON-NLS-1$
@Override
public Boolean call() throws Exception {
final Stage tmpS = getStageByTitle(title, operator);
// the stage might be still in the list be cause the fx
// thread was not fast enough to remove it, thus
// checking if showing == false which also means that
// the stage is not rendered
return tmpS == null || !tmpS.isShowing();
}
});
if (!result) {
throw new StepExecutionException("window was not closed", //$NON-NLS-1$
EventFactory
.createActionError(
TestErrorEvent.TIMEOUT_EXPIRED));
}
TimeUtil.delay(delay);
}
@Override
public void rcSyncShutdownAndRestart(int timeout) {
StepExecutionException.throwUnsupportedAction();
}
@Override
public void rcPrepareForShutdown() {
StepExecutionException.throwUnsupportedAction();
}
}