aboutsummaryrefslogtreecommitdiffstats
path: root/uitest-common
diff options
context:
space:
mode:
authorHenri Sara <hesara@vaadin.com>2016-06-07 13:14:29 +0300
committerVaadin Code Review <review@vaadin.com>2016-08-17 12:13:50 +0000
commit3816d34daaf9eca4b3b591b79309a98796b3a4da (patch)
tree9a478b861310c00cdc750790120d9eea56a2a580 /uitest-common
parentc89f50eb801a903ec28c8618cf30ae31b9184c8f (diff)
downloadvaadin-framework-3816d34daaf9eca4b3b591b79309a98796b3a4da.tar.gz
vaadin-framework-3816d34daaf9eca4b3b591b79309a98796b3a4da.zip
Create module uitest-common
This module will contain most of the classes shared between uitest and integration test modules. Change-Id: Ibc327339cf1d360252483287c0eeb8dd03c176d5
Diffstat (limited to 'uitest-common')
-rw-r--r--uitest-common/pom.xml114
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/AbstractTB3Test.java1262
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/BrowserStackBrowserFactory.java135
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/CustomTestBenchCommandExecutor.java165
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/ExcludeFromSuite.java37
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/IncludeIfProperty.java44
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/MultiBrowserTest.java116
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/ParallelScheduler.java69
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/ParameterizedTB3Runner.java168
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java281
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/RetryOnFail.java64
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/SauceLabsBrowserFactory.java117
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/ScreenshotTB3Test.java498
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTest.java34
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTestPhantomJS2.java29
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/TB3Runner.java80
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestLocator.java229
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestSuite.java48
-rw-r--r--uitest-common/src/main/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java95
19 files changed, 3585 insertions, 0 deletions
diff --git a/uitest-common/pom.xml b/uitest-common/pom.xml
new file mode 100644
index 0000000000..d46927fd2a
--- /dev/null
+++ b/uitest-common/pom.xml
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.vaadin</groupId>
+ <artifactId>vaadin-root</artifactId>
+ <version>8.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>vaadin-uitest-common</artifactId>
+ <name>vaadin-uitest-common</name>
+ <packaging>jar</packaging>
+ <properties>
+ <skip.uitest.deployment>true</skip.uitest.deployment>
+ </properties>
+
+ <url>https://vaadin.com/</url>
+ <description>Vaadin UI test common classes and dependencies</description>
+
+ <repositories>
+ <repository>
+ <id>vaadin-addons</id>
+ <url>http://maven.vaadin.com/vaadin-addons</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <!-- LIBRARY DEPENDENCIES (compile time) -->
+ <dependency>
+ <groupId>com.vaadin</groupId>
+ <artifactId>vaadin-server</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.11</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons-io.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.vaadin</groupId>
+ <artifactId>vaadin-testbench</artifactId>
+ <version>4.0.3</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>${skip.uitest.deployment}</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>animal-sniffer-maven-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <configuration>
+ <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <profiles>
+ <profile>
+ <!-- Profile used to deploy the uitest-common jar for testing -->
+ <id>dev-build</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <properties>
+ <skip.uitest.deployment>false</skip.uitest.deployment>
+ </properties>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/AbstractTB3Test.java
new file mode 100644
index 0000000000..9c617ab128
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/AbstractTB3Test.java
@@ -0,0 +1,1262 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+import org.openqa.selenium.interactions.HasInputDevices;
+import org.openqa.selenium.interactions.Keyboard;
+import org.openqa.selenium.interactions.Mouse;
+import org.openqa.selenium.interactions.internal.Coordinates;
+import org.openqa.selenium.internal.Locatable;
+import org.openqa.selenium.internal.WrapsElement;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.HttpCommandExecutor;
+import org.openqa.selenium.remote.RemoteWebDriver;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import com.thoughtworks.selenium.webdriven.WebDriverBackedSelenium;
+import com.vaadin.server.LegacyApplication;
+import com.vaadin.server.UIProvider;
+import com.vaadin.testbench.TestBenchDriverProxy;
+import com.vaadin.testbench.TestBenchElement;
+import com.vaadin.testbench.annotations.BrowserConfiguration;
+import com.vaadin.testbench.elements.CheckBoxElement;
+import com.vaadin.testbench.elements.LabelElement;
+import com.vaadin.testbench.elements.TableElement;
+import com.vaadin.testbench.elements.VerticalLayoutElement;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.testbench.parallel.BrowserUtil;
+import com.vaadin.testbench.parallel.ParallelTest;
+import com.vaadin.ui.UI;
+
+import elemental.json.JsonObject;
+import elemental.json.impl.JsonUtil;
+
+/**
+ * Base class for TestBench 3+ tests. All TB3+ tests in the project should
+ * extend this class.
+ *
+ * Provides:
+ * <ul>
+ * <li>Helpers for browser selection</li>
+ * <li>Hub connection setup and teardown</li>
+ * <li>Automatic generation of URL for a given test on the development server
+ * using {@link #getUIClass()} or by automatically finding an enclosing UI class
+ * and based on requested features, e.g. {@link #isDebug()}, {@link #isPush()}</li>
+ * <li>Generic helpers for creating TB3+ tests</li>
+ * </ul>
+ *
+ * @author Vaadin Ltd
+ */
+@RunWith(TB3Runner.class)
+public abstract class AbstractTB3Test extends ParallelTest {
+
+ @Rule
+ public TestName testName = new TestName();
+
+ @Rule
+ public RetryOnFail retry = new RetryOnFail();
+
+ /**
+ * Height of the screenshots we want to capture
+ */
+ private static final int SCREENSHOT_HEIGHT = 850;
+
+ /**
+ * Width of the screenshots we want to capture
+ */
+ private static final int SCREENSHOT_WIDTH = 1500;
+
+ /**
+ * Timeout used by the TB grid
+ */
+ private static final int BROWSER_TIMEOUT_IN_MS = 30 * 1000;
+
+ protected static DesiredCapabilities PHANTOMJS2() {
+ DesiredCapabilities phantomjs2 = new VaadinBrowserFactory().create(
+ Browser.PHANTOMJS, "2");
+ // Hack for the test cluster
+ phantomjs2
+ .setCapability("phantomjs.binary.path", "/usr/bin/phantomjs2");
+ return phantomjs2;
+ }
+
+ private boolean debug = false;
+
+ private boolean push = false;
+
+ static {
+ com.vaadin.testbench.Parameters
+ .setScreenshotComparisonCursorDetection(true);
+ }
+
+ /**
+ * Connect to the hub using a remote web driver, set the canvas size and
+ * opens the initial URL as specified by {@link #getTestUrl()}
+ *
+ * @throws Exception
+ */
+ @Override
+ public void setup() throws Exception {
+ super.setup();
+
+ int w = SCREENSHOT_WIDTH;
+ int h = SCREENSHOT_HEIGHT;
+
+ if (BrowserUtil.isIE8(super.getDesiredCapabilities())) {
+ // IE8 gets size wrong, who would have guessed...
+ w += 4;
+ h += 4;
+ }
+ try {
+ testBench().resizeViewPortTo(w, h);
+ } catch (UnsupportedOperationException e) {
+ // Opera does not support this...
+ }
+ }
+
+ /**
+ * Method for closing the tested application.
+ */
+ protected void closeApplication() {
+ if (driver != null) {
+ try {
+ openTestURL("closeApplication");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected WebElement getTooltipErrorElement() {
+ WebElement tooltip = getDriver().findElement(
+ com.vaadin.testbench.By.className("v-tooltip"));
+ return tooltip.findElement(By.className("v-errormessage"));
+ }
+
+ protected WebElement getTooltipElement() {
+ return getDriver().findElement(
+ com.vaadin.testbench.By.className("v-tooltip-text"));
+ }
+
+ protected Coordinates getCoordinates(TestBenchElement element) {
+ return ((Locatable) element.getWrappedElement()).getCoordinates();
+ }
+
+ private boolean hasDebugMessage(String message) {
+ return getDebugMessage(message) != null;
+ }
+
+ private WebElement getDebugMessage(String message) {
+ return driver.findElement(By.xpath(String.format(
+ "//span[@class='v-debugwindow-message' and text()='%s']",
+ message)));
+ }
+
+ protected void waitForDebugMessage(final String expectedMessage) {
+ waitForDebugMessage(expectedMessage, 30);
+ }
+
+ protected void waitForDebugMessage(final String expectedMessage, int timeout) {
+ waitUntil(new ExpectedCondition<Boolean>() {
+
+ @Override
+ public Boolean apply(WebDriver input) {
+ return hasDebugMessage(expectedMessage);
+ }
+ }, timeout);
+ }
+
+ protected void clearDebugMessages() {
+ driver.findElement(
+ By.xpath("//button[@class='v-debugwindow-button' and @title='Clear log']"))
+ .click();
+ }
+
+ protected void waitUntilRowIsVisible(final TableElement table, final int row) {
+ waitUntil(new ExpectedCondition<Object>() {
+ @Override
+ public Object apply(WebDriver input) {
+ try {
+ return table.getCell(row, 0) != null;
+ } catch (NoSuchElementException e) {
+ return false;
+ }
+ }
+ });
+ }
+
+ protected void scrollTable(TableElement table, int rows, int rowToWait) {
+ testBenchElement(table.findElement(By.className("v-scrollable")))
+ .scroll(rows * 30);
+
+ waitUntilRowIsVisible(table, rowToWait);
+ }
+
+ /**
+ * Opens the given test (defined by {@link #getTestUrl()}, optionally with
+ * debug window and/or push (depending on {@link #isDebug()} and
+ * {@link #isPush()}.
+ */
+ protected void openTestURL() {
+ openTestURL(new String[0]);
+ }
+
+ /**
+ * Opens the given test (defined by {@link #getTestUrl()}, optionally with
+ * debug window and/or push (depending on {@link #isDebug()} and
+ * {@link #isPush()}.
+ */
+ protected void openTestURL(String... parameters) {
+ openTestURL(getUIClass(), parameters);
+ }
+
+ /**
+ * Opens the given test (defined by {@link #getTestUrl()}, optionally with
+ * debug window and/or push (depending on {@link #isDebug()} and
+ * {@link #isPush()}.
+ */
+ protected void openTestURL(Class<?> uiClass, String... parameters) {
+ openTestURL(uiClass, new HashSet<String>(Arrays.asList(parameters)));
+ }
+
+ private void openTestURL(Class<?> uiClass, Set<String> parameters) {
+ String url = getTestURL(uiClass);
+
+ if (isDebug()) {
+ parameters.add("debug");
+ }
+
+ if (LegacyApplication.class.isAssignableFrom(uiClass)) {
+ parameters.add("restartApplication");
+ }
+
+ if (parameters.size() > 0) {
+ url += "?" + StringUtils.join(parameters, "&");
+ }
+
+ driver.get(url);
+ }
+
+ /**
+ * Returns the full URL to be used for the test
+ *
+ * @return the full URL for the test
+ */
+ protected String getTestUrl() {
+ return StringUtils.strip(getBaseURL(), "/") + getDeploymentPath();
+ }
+
+ /**
+ * Returns the full URL to be used for the test for the provided UI class.
+ *
+ * @return the full URL for the test
+ */
+ protected String getTestURL(Class<?> uiClass) {
+ return StringUtils.strip(getBaseURL(), "/")
+ + getDeploymentPath(uiClass);
+ }
+
+ /**
+ * Used to determine what URL to initially open for the test
+ *
+ * @return the host name of development server
+ */
+ protected abstract String getDeploymentHostname();
+
+ /**
+ * Used to determine what port the test is running on
+ *
+ * @return The port teh test is running on, by default 8888
+ */
+ protected abstract int getDeploymentPort();
+
+ /**
+ * Produces a collection of browsers to run the test on. This method is
+ * executed by the test runner when determining how many test methods to
+ * invoke and with what parameters. For each returned value a test method is
+ * ran and before running that,
+ * {@link #setDesiredCapabilities(DesiredCapabilities)} is invoked with the
+ * value returned by this method.
+ *
+ * This method is not static to allow overriding it in sub classes. By
+ * default runs the test only on Firefox
+ *
+ * @return The browsers to run the test on
+ */
+ @BrowserConfiguration
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ return Collections.singletonList(Browser.FIREFOX
+ .getDesiredCapabilities());
+ }
+
+ /**
+ * Finds an element based on the part of a TB2 style locator following the
+ * :: (e.g. vaadin=runLabelModes::PID_Scheckboxaction-Enabled/domChild[0] ->
+ * PID_Scheckboxaction-Enabled/domChild[0]).
+ *
+ * @param vaadinLocator
+ * The part following :: of the vaadin locator string
+ * @return
+ */
+ protected WebElement vaadinElement(String vaadinLocator) {
+ return driver.findElement(vaadinLocator(vaadinLocator));
+ }
+
+ /**
+ * Uses JavaScript to determine the currently focused element.
+ *
+ * @return Focused element or null
+ */
+ protected WebElement getFocusedElement() {
+ Object focusedElement = executeScript("return document.activeElement");
+ if (null != focusedElement) {
+ return (WebElement) focusedElement;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Executes the given Javascript
+ *
+ * @param script
+ * the script to execute
+ * @return whatever
+ * {@link org.openqa.selenium.JavascriptExecutor#executeScript(String, Object...)}
+ * returns
+ */
+ protected Object executeScript(String script, Object... args) {
+ return ((JavascriptExecutor) getDriver()).executeScript(script, args);
+ }
+
+ /**
+ * Find a Vaadin element based on its id given using Component.setId
+ *
+ * @param id
+ * The id to locate
+ * @return
+ */
+ public WebElement vaadinElementById(String id) {
+ return driver.findElement(By.id(id));
+ }
+
+ /**
+ * Finds a {@link By} locator based on the part of a TB2 style locator
+ * following the :: (e.g.
+ * vaadin=runLabelModes::PID_Scheckboxaction-Enabled/domChild[0] ->
+ * PID_Scheckboxaction-Enabled/domChild[0]).
+ *
+ * @param vaadinLocator
+ * The part following :: of the vaadin locator string
+ * @return
+ */
+ public org.openqa.selenium.By vaadinLocator(String vaadinLocator) {
+ String base = getApplicationId(getDeploymentPath());
+
+ base += "::";
+ return com.vaadin.testbench.By.vaadin(base + vaadinLocator);
+ }
+
+ /**
+ * Constructs a {@link By} locator for the id given using Component.setId
+ *
+ * @param id
+ * The id to locate
+ * @return a locator for the given id
+ */
+ public By vaadinLocatorById(String id) {
+ return vaadinLocator("PID_S" + id);
+ }
+
+ /**
+ * Waits up to 10s for the given condition to become true. Use e.g. as
+ * {@link #waitUntil(ExpectedConditions.textToBePresentInElement(by, text))}
+ *
+ * @param condition
+ * the condition to wait for to become true
+ */
+ protected <T> void waitUntil(ExpectedCondition<T> condition) {
+ waitUntil(condition, 10);
+ }
+
+ /**
+ * Waits the given number of seconds for the given condition to become true.
+ * Use e.g. as {@link
+ * #waitUntil(ExpectedConditions.textToBePresentInElement(by, text))}
+ *
+ * @param condition
+ * the condition to wait for to become true
+ */
+ protected <T> void waitUntil(ExpectedCondition<T> condition,
+ long timeoutInSeconds) {
+ new WebDriverWait(driver, timeoutInSeconds).until(condition);
+ }
+
+ /**
+ * Waits up to 10s for the given condition to become false. Use e.g. as
+ * {@link #waitUntilNot(ExpectedConditions.textToBePresentInElement(by,
+ * text))}
+ *
+ * @param condition
+ * the condition to wait for to become false
+ */
+ protected <T> void waitUntilNot(ExpectedCondition<T> condition) {
+ waitUntilNot(condition, 10);
+ }
+
+ /**
+ * Waits the given number of seconds for the given condition to become
+ * false. Use e.g. as {@link
+ * #waitUntilNot(ExpectedConditions.textToBePresentInElement(by, text))}
+ *
+ * @param condition
+ * the condition to wait for to become false
+ */
+ protected <T> void waitUntilNot(ExpectedCondition<T> condition,
+ long timeoutInSeconds) {
+ waitUntil(ExpectedConditions.not(condition), timeoutInSeconds);
+ }
+
+ protected void waitForElementPresent(final By by) {
+ waitUntil(ExpectedConditions.presenceOfElementLocated(by));
+ }
+
+ protected void waitForElementNotPresent(final By by) {
+ waitUntil(new ExpectedCondition<Boolean>() {
+ @Override
+ public Boolean apply(WebDriver input) {
+ return input.findElements(by).isEmpty();
+ }
+ });
+ }
+
+ protected void waitForElementVisible(final By by) {
+ waitUntil(ExpectedConditions.visibilityOfElementLocated(by));
+ }
+
+ /**
+ * Checks if the given element has the given class name.
+ *
+ * Matches only full class names, i.e. has ("foo") does not match
+ * class="foobar"
+ *
+ * @param element
+ * @param className
+ * @return
+ */
+ protected boolean hasCssClass(WebElement element, String className) {
+ String classes = element.getAttribute("class");
+ if (classes == null || classes.isEmpty()) {
+ return (className == null || className.isEmpty());
+ }
+
+ for (String cls : classes.split(" ")) {
+ if (className.equals(cls)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * For tests extending AbstractTestUIWithLog, returns the element
+ * for the Nth log row
+ *
+ * @param rowNr
+ * The log row to retrieve
+ * @return the Nth log row
+ */
+ protected WebElement getLogRowElement(int rowNr) {
+ return vaadinElementById("Log_row_" + rowNr);
+ }
+
+ /**
+ * For tests extending AbstractTestUIWithLog, returns the text in
+ * the Nth log row
+ *
+ * @param rowNr
+ * The log row to retrieve text for
+ * @return the text in the log row
+ */
+ protected String getLogRow(int rowNr) {
+ return getLogRowElement(rowNr).getText();
+ }
+
+ /**
+ * Asserts that {@literal a} is &gt;= {@literal b}
+ *
+ * @param message
+ * The message to include in the {@link AssertionError}
+ * @param a
+ * @param b
+ * @throws AssertionError
+ * If comparison fails
+ */
+ public static final <T> void assertGreaterOrEqual(String message,
+ Comparable<T> a, T b) throws AssertionError {
+ if (a.compareTo(b) >= 0) {
+ return;
+ }
+
+ throw new AssertionError(decorate(message, a, b));
+ }
+
+ /**
+ * Asserts that {@literal a} is &gt; {@literal b}
+ *
+ * @param message
+ * The message to include in the {@link AssertionError}
+ * @param a
+ * @param b
+ * @throws AssertionError
+ * If comparison fails
+ */
+ public static final <T> void assertGreater(String message, Comparable<T> a,
+ T b) throws AssertionError {
+ if (a.compareTo(b) > 0) {
+ return;
+ }
+ throw new AssertionError(decorate(message, a, b));
+ }
+
+ /**
+ * Asserts that {@literal a} is &lt;= {@literal b}
+ *
+ * @param message
+ * The message to include in the {@link AssertionError}
+ * @param a
+ * @param b
+ * @throws AssertionError
+ * If comparison fails
+ */
+ public static final <T> void assertLessThanOrEqual(String message,
+ Comparable<T> a, T b) throws AssertionError {
+ if (a.compareTo(b) <= 0) {
+ return;
+ }
+
+ throw new AssertionError(decorate(message, a, b));
+ }
+
+ /**
+ * Asserts that {@literal a} is &lt; {@literal b}
+ *
+ * @param message
+ * The message to include in the {@link AssertionError}
+ * @param a
+ * @param b
+ * @throws AssertionError
+ * If comparison fails
+ */
+ public static final <T> void assertLessThan(String message,
+ Comparable<T> a, T b) throws AssertionError {
+ if (a.compareTo(b) < 0) {
+ return;
+ }
+ throw new AssertionError(decorate(message, a, b));
+ }
+
+ private static <T> String decorate(String message, Comparable<T> a, T b) {
+ message = message.replace("{0}", a.toString());
+ message = message.replace("{1}", b.toString());
+ return message;
+ }
+
+ /**
+ * Returns the path that should be used for the test. The path contains the
+ * full path (appended to hostname+port) and must start with a slash.
+ *
+ * @param push
+ * true if "?debug" should be added
+ * @param debug
+ * true if /run-push should be used instead of /run
+ *
+ * @return The URL path to the UI class to test
+ */
+ protected String getDeploymentPath() {
+ Class<?> uiClass = getUIClass();
+ if (uiClass != null) {
+ return getDeploymentPath(uiClass);
+ }
+ throw new IllegalArgumentException("Unable to determine path for "
+ + getClass().getCanonicalName());
+
+ }
+
+ /**
+ * Returns the UI class the current test is connected to (or in special
+ * cases UIProvider or LegacyApplication). Uses the enclosing class if the
+ * test class is a static inner class to a UI class.
+ *
+ * Test which are not enclosed by a UI class must implement this method and
+ * return the UI class they want to test.
+ *
+ * Note that this method will update the test name to the enclosing class to
+ * be compatible with TB2 screenshot naming
+ *
+ * @return the UI class the current test is connected to
+ */
+ protected Class<?> getUIClass() {
+ try {
+ // Convention: SomeUITest uses the SomeUI UI class
+ String uiClassName = getClass().getName().replaceFirst("Test$", "");
+ Class<?> cls = Class.forName(uiClassName);
+ if (isSupportedRunnerClass(cls)) {
+ return cls;
+ }
+ } catch (Exception e) {
+ }
+ throw new RuntimeException(
+ "Could not determine UI class. Ensure the test is named UIClassTest and is in the same package as the UIClass");
+ }
+
+ /**
+ * @return true if the given class is supported by ApplicationServletRunner
+ */
+ @SuppressWarnings("deprecation")
+ private boolean isSupportedRunnerClass(Class<?> cls) {
+ if (UI.class.isAssignableFrom(cls)) {
+ return true;
+ }
+ if (UIProvider.class.isAssignableFrom(cls)) {
+ return true;
+ }
+ if (LegacyApplication.class.isAssignableFrom(cls)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether to run the test in debug mode (with the debug console
+ * open) or not
+ *
+ * @return true to run with the debug window open, false by default
+ */
+ protected final boolean isDebug() {
+ return debug;
+ }
+
+ /**
+ * Sets whether to run the test in debug mode (with the debug console open)
+ * or not.
+ *
+ * @param debug
+ * true to open debug window, false otherwise
+ */
+ protected final void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ /**
+ * Returns whether to run the test with push enabled (using /run-push) or
+ * not. Note that push tests can and should typically be created using @Push
+ * on the UI instead of overriding this method
+ *
+ * @return true if /run-push is used, false otherwise
+ */
+ protected final boolean isPush() {
+ return push;
+ }
+
+ /**
+ * Sets whether to run the test with push enabled (using /run-push) or not.
+ * Note that push tests can and should typically be created using @Push on
+ * the UI instead of overriding this method
+ *
+ * @param push
+ * true to use /run-push in the test, false otherwise
+ */
+ protected final void setPush(boolean push) {
+ this.push = push;
+ }
+
+ /**
+ * Returns the path for the given UI class when deployed on the test server.
+ * The path contains the full path (appended to hostname+port) and must
+ * start with a slash.
+ *
+ * This method takes into account {@link #isPush()} and {@link #isDebug()}
+ * when the path is generated.
+ *
+ * @param uiClass
+ * @param push
+ * true if "?debug" should be added
+ * @param debug
+ * true if /run-push should be used instead of /run
+ * @return The path to the given UI class
+ */
+ protected String getDeploymentPath(Class<?> uiClass) {
+ String runPath = "/run";
+ if (isPush()) {
+ runPath = "/run-push";
+ }
+
+ if (UI.class.isAssignableFrom(uiClass)
+ || UIProvider.class.isAssignableFrom(uiClass)
+ || LegacyApplication.class.isAssignableFrom(uiClass)) {
+ return runPath + "/" + uiClass.getCanonicalName();
+ } else {
+ throw new IllegalArgumentException(
+ "Unable to determine path for enclosing class "
+ + uiClass.getCanonicalName());
+ }
+ }
+
+ /**
+ * Used to determine what URL to initially open for the test
+ *
+ * @return The base URL for the test. Does not include a trailing slash.
+ */
+ protected String getBaseURL() {
+ return "http://" + getDeploymentHostname() + ":" + getDeploymentPort();
+ }
+
+ /**
+ * Generates the application id based on the URL in a way compatible with
+ * VaadinServletService.
+ *
+ * @param pathWithQueryParameters
+ * The path part of the URL, possibly still containing query
+ * parameters
+ * @return The application ID string used in Vaadin locators
+ */
+ private String getApplicationId(String pathWithQueryParameters) {
+ // Remove any possible URL parameters
+ String pathWithoutQueryParameters = pathWithQueryParameters.replaceAll(
+ "\\?.*", "");
+ if ("".equals(pathWithoutQueryParameters)) {
+ return "ROOT";
+ }
+
+ // Retain only a-z and numbers
+ return pathWithoutQueryParameters.replaceAll("[^a-zA-Z0-9]", "");
+ }
+
+ /**
+ * Sleeps for the given number of ms but ensures that the browser connection
+ * does not time out.
+ *
+ * @param timeoutMillis
+ * Number of ms to wait
+ * @throws InterruptedException
+ */
+ protected void sleep(int timeoutMillis) throws InterruptedException {
+ while (timeoutMillis > 0) {
+ int d = Math.min(BROWSER_TIMEOUT_IN_MS, timeoutMillis);
+ Thread.sleep(d);
+ timeoutMillis -= d;
+
+ // Do something to keep the connection alive
+ getDriver().getTitle();
+ }
+ }
+
+ /**
+ * Called by the test runner whenever there is an exception in the test that
+ * will cause termination of the test
+ *
+ * @param t
+ * the throwable which caused the termination
+ */
+ public void onUncaughtException(Throwable t) {
+ // Do nothing by default
+
+ }
+
+ /**
+ * Returns the mouse object for doing mouse commands
+ *
+ * @return Returns the mouse
+ */
+ public Mouse getMouse() {
+ return ((HasInputDevices) getDriver()).getMouse();
+ }
+
+ /**
+ * Returns the keyboard object for controlling keyboard events
+ *
+ * @return Return the keyboard
+ */
+ public Keyboard getKeyboard() {
+ return ((HasInputDevices) getDriver()).getKeyboard();
+ }
+
+ public void hitButton(String id) {
+ if (BrowserUtil.isPhantomJS(getDesiredCapabilities())) {
+ driver.findElement(By.id(id)).click();
+ } else {
+ WebDriverBackedSelenium selenium = new WebDriverBackedSelenium(
+ driver, driver.getCurrentUrl());
+
+ selenium.keyPress("id=" + id, "\\13");
+ }
+ }
+
+ protected void openDebugLogTab() {
+
+ waitUntil(new ExpectedCondition<Boolean>() {
+ @Override
+ public Boolean apply(WebDriver input) {
+ WebElement element = getDebugLogButton();
+ return element != null;
+ }
+ }, 15);
+ getDebugLogButton().click();
+ }
+
+ private WebElement getDebugLogButton() {
+ return findElement(By.xpath("//button[@title='Debug message log']"));
+ }
+
+ protected void assertNoDebugMessage(Level level) {
+ // class="v-debugwindow-row Level.getName()"
+ List<WebElement> logElements = driver
+ .findElements(By.xpath(String
+ .format("//div[@class='v-debugwindow-row %s']/span[@class='v-debugwindow-message']",
+ level.getName())));
+ if (!logElements.isEmpty()) {
+ String logRows = "";
+ for (WebElement e : logElements) {
+ logRows += "\n" + e.getText();
+ }
+ Assert.fail("Found debug messages with level " + level.getName()
+ + ": " + logRows);
+ }
+ }
+
+ /**
+ * Should the "require window focus" be enabled for Internet Explorer.
+ * RequireWindowFocus makes tests more stable but seems to be broken with
+ * certain commands such as sendKeys. Therefore it is not enabled by default
+ * for all tests
+ *
+ * @return true, to use the "require window focus" feature, false otherwise
+ */
+ protected boolean requireWindowFocusForIE() {
+ return false;
+ }
+
+ /**
+ * Should the "enable persistent hover" be enabled for Internet Explorer.
+ *
+ * Persistent hovering causes continuous firing of mouse over events at the
+ * last location the mouse cursor has been moved to. This is to avoid
+ * problems where the real mouse cursor is inside the browser window and
+ * Internet Explorer uses that location for some undefined operation
+ * (http://
+ * jimevansmusic.blogspot.fi/2012/06/whats-wrong-with-internet-explorer
+ * .html)
+ *
+ * @return true, to use the "persistent hover" feature, false otherwise
+ */
+ protected boolean usePersistentHoverForIE() {
+ return true;
+ }
+
+ /**
+ * Should the "native events" be enabled for Internet Explorer.
+ * <p>
+ * Native events sometimes cause failure in clicking on buttons/checkboxes
+ * but are possibly needed for some operations.
+ *
+ * @return true, to use "native events", false to use generated Javascript
+ * events
+ */
+ protected boolean useNativeEventsForIE() {
+ return true;
+ }
+
+ // FIXME: Remove this once TB4 getRemoteControlName works properly
+ private RemoteWebDriver getRemoteDriver() {
+ WebDriver d = getDriver();
+ if (d instanceof TestBenchDriverProxy) {
+ try {
+ Field f = TestBenchDriverProxy.class
+ .getDeclaredField("actualDriver");
+ f.setAccessible(true);
+ return (RemoteWebDriver) f.get(d);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (d instanceof RemoteWebDriver) {
+ return (RemoteWebDriver) d;
+ }
+
+ return null;
+
+ }
+
+ // FIXME: Remove this once TB4 getRemoteControlName works properly
+ protected String getRemoteControlName() {
+ try {
+ RemoteWebDriver d = getRemoteDriver();
+ if (d == null) {
+ return null;
+ }
+ HttpCommandExecutor ce = (HttpCommandExecutor) d
+ .getCommandExecutor();
+ String hostName = ce.getAddressOfRemoteServer().getHost();
+ int port = ce.getAddressOfRemoteServer().getPort();
+ HttpHost host = new HttpHost(hostName, port);
+ DefaultHttpClient client = new DefaultHttpClient();
+ URL sessionURL = new URL("http://" + hostName + ":" + port
+ + "/grid/api/testsession?session=" + d.getSessionId());
+ BasicHttpEntityEnclosingRequest r = new BasicHttpEntityEnclosingRequest(
+ "POST", sessionURL.toExternalForm());
+ HttpResponse response = client.execute(host, r);
+ JsonObject object = extractObject(response);
+ URL myURL = new URL(object.getString("proxyId"));
+ if ((myURL.getHost() != null) && (myURL.getPort() != -1)) {
+ return myURL.getHost();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ protected boolean logContainsText(String string) {
+ List<String> logs = getLogs();
+
+ for (String text : logs) {
+ if (text.contains(string)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected List<String> getLogs() {
+ VerticalLayoutElement log = $(VerticalLayoutElement.class).id("Log");
+ List<LabelElement> logLabels = log.$(LabelElement.class).all();
+ List<String> logTexts = new ArrayList<String>();
+
+ for (LabelElement label : logLabels) {
+ logTexts.add(label.getText());
+ }
+
+ return logTexts;
+ }
+
+ private static JsonObject extractObject(HttpResponse resp)
+ throws IOException {
+ InputStream contents = resp.getEntity().getContent();
+ StringWriter writer = new StringWriter();
+ IOUtils.copy(contents, writer, "UTF8");
+ return JsonUtil.parse(writer.toString());
+ }
+
+ protected void click(CheckBoxElement checkbox) {
+ WebElement cb = checkbox.findElement(By.xpath("input"));
+ if (BrowserUtil.isChrome(getDesiredCapabilities())) {
+ testBenchElement(cb).click(0, 0);
+ } else {
+ cb.click();
+ }
+ }
+
+ protected boolean isLoadingIndicatorVisible() {
+ WebElement loadingIndicator = findElement(By
+ .className("v-loading-indicator"));
+
+ return loadingIndicator.isDisplayed();
+ }
+
+ protected void waitUntilLoadingIndicatorNotVisible() {
+ waitUntil(new ExpectedCondition<Boolean>() {
+
+ @Override
+ public Boolean apply(WebDriver input) {
+ WebElement loadingIndicator = input.findElement(By
+ .className("v-loading-indicator"));
+
+ return !loadingIndicator.isDisplayed();
+ }
+ });
+ }
+
+ /**
+ * Selects a menu item. By default, this will click on the menu item.
+ *
+ * @param menuCaption
+ * caption of the menu item
+ */
+ protected void selectMenu(String menuCaption) {
+ selectMenu(menuCaption, true);
+ }
+
+ /**
+ * Selects a menu item.
+ *
+ * @param menuCaption
+ * caption of the menu item
+ * @param click
+ * <code>true</code> if should click the menu item;
+ * <code>false</code> if not
+ */
+ protected void selectMenu(String menuCaption, boolean click) {
+ WebElement menuElement = getMenuElement(menuCaption);
+ Dimension size = menuElement.getSize();
+ new Actions(getDriver()).moveToElement(menuElement, size.width - 10,
+ size.height / 2).perform();
+ if (click) {
+ new Actions(getDriver()).click().perform();
+ }
+ }
+
+ /**
+ * Finds the menu item from the DOM based on menu item caption.
+ *
+ * @param menuCaption
+ * caption of the menu item
+ * @return the found menu item
+ * @throws NoSuchElementException
+ * if menu item is not found
+ */
+ protected WebElement getMenuElement(String menuCaption)
+ throws NoSuchElementException {
+ return getDriver().findElement(
+ By.xpath("//span[text() = '" + menuCaption + "']"));
+ }
+
+ /**
+ * Selects a submenu described by a path of menus from the first MenuBar in
+ * the UI.
+ *
+ * @param menuCaptions
+ * array of menu captions
+ */
+ protected void selectMenuPath(String... menuCaptions) {
+ selectMenu(menuCaptions[0], true);
+
+ // Move to the menu item opened below the menu bar.
+ new Actions(getDriver()).moveByOffset(0,
+ getMenuElement(menuCaptions[0]).getSize().getHeight())
+ .perform();
+
+ for (int i = 1; i < menuCaptions.length - 1; i++) {
+ selectMenu(menuCaptions[i]);
+ new Actions(getDriver()).moveByOffset(40, 0).build().perform();
+ }
+ selectMenu(menuCaptions[menuCaptions.length - 1], true);
+ }
+
+ /**
+ * Asserts that an element is present
+ *
+ * @param by
+ * the locatore for the element
+ */
+ protected void assertElementPresent(By by) {
+ Assert.assertTrue("Element is not present", isElementPresent(by));
+ }
+
+ /**
+ * Asserts that an element is not present
+ *
+ * @param by
+ * the locatore for the element
+ */
+ protected void assertElementNotPresent(By by) {
+ Assert.assertFalse("Element is present", isElementPresent(by));
+ }
+
+ /**
+ * Asserts that no error notifications are shown. Requires the use of
+ * "?debug" as exceptions are otherwise not shown as notifications.
+ */
+ protected void assertNoErrorNotifications() {
+ Assert.assertFalse(
+ "Error notification with client side exception is shown",
+ isNotificationPresent("error"));
+ }
+
+ /**
+ * Asserts that no system notifications are shown.
+ */
+ protected void assertNoSystemNotifications() {
+ Assert.assertFalse(
+ "Error notification with system error exception is shown",
+ isNotificationPresent("system"));
+ }
+
+ /**
+ * Asserts that a system notification is shown.
+ */
+ protected void assertSystemNotification() {
+ Assert.assertTrue(
+ "Error notification with system error exception is not shown",
+ isNotificationPresent("system"));
+ }
+
+ private boolean isNotificationPresent(String type) {
+ if ("error".equals(type)) {
+ Assert.assertTrue(
+ "Debug window must be open to be able to see error notifications",
+ isDebugWindowOpen());
+ }
+ return isElementPresent(By.className("v-Notification-" + type));
+ }
+
+ private boolean isDebugWindowOpen() {
+ return isElementPresent(By.className("v-debugwindow"));
+ }
+
+ protected void assertNoHorizontalScrollbar(WebElement element,
+ String errorMessage) {
+ // IE rounds clientWidth/clientHeight down and scrollHeight/scrollWidth
+ // up, so using clientWidth/clientHeight will fail if the element height
+ // is not an integer
+ int clientWidth = getClientWidth(element);
+ int scrollWidth = getScrollWidth(element);
+ boolean hasScrollbar = scrollWidth > clientWidth;
+
+ Assert.assertFalse(
+ "The element should not have a horizontal scrollbar (scrollWidth: "
+ + scrollWidth + ", clientWidth: " + clientWidth + "): "
+ + errorMessage, hasScrollbar);
+ }
+
+ protected void assertNoVerticalScrollbar(WebElement element,
+ String errorMessage) {
+ // IE rounds clientWidth/clientHeight down and scrollHeight/scrollWidth
+ // up, so using clientWidth/clientHeight will fail if the element height
+ // is not an integer
+ int clientHeight = getClientHeight(element);
+ int scrollHeight = getScrollHeight(element);
+ boolean hasScrollbar = scrollHeight > clientHeight;
+
+ Assert.assertFalse(
+ "The element should not have a vertical scrollbar (scrollHeight: "
+ + scrollHeight + ", clientHeight: " + clientHeight
+ + "): " + errorMessage, hasScrollbar);
+ }
+
+ protected int getScrollHeight(WebElement element) {
+ return ((Number) executeScript("return arguments[0].scrollHeight;",
+ element)).intValue();
+ }
+
+ protected int getScrollWidth(WebElement element) {
+ return ((Number) executeScript("return arguments[0].scrollWidth;",
+ element)).intValue();
+ }
+
+ /**
+ * Returns client height rounded up instead of as double because of IE9
+ * issues: https://dev.vaadin.com/ticket/18469
+ */
+ protected int getClientHeight(WebElement e) {
+ String script;
+ if (BrowserUtil.isIE8(getDesiredCapabilities())) {
+ script = "return arguments[0].clientHeight;"; //
+ } else {
+ script = "var cs = window.getComputedStyle(arguments[0]);"
+ + "return Math.ceil(parseFloat(cs.height)+parseFloat(cs.paddingTop)+parseFloat(cs.paddingBottom));";
+ }
+ return ((Number) executeScript(script, e)).intValue();
+ }
+
+ /**
+ * Returns client width rounded up instead of as double because of IE9
+ * issues: https://dev.vaadin.com/ticket/18469
+ */
+ protected int getClientWidth(WebElement e) {
+ String script;
+ if (BrowserUtil.isIE8(getDesiredCapabilities())) {
+ script = "return arguments[0].clientWidth;";
+ } else {
+ script = "var cs = window.getComputedStyle(arguments[0]);"
+ + "var h = parseFloat(cs.width)+parseFloat(cs.paddingLeft)+parseFloat(cs.paddingRight);"
+ + "return Math.ceil(h);";
+ }
+
+ return ((Number) executeScript(script, e)).intValue();
+ }
+
+ protected void assertElementsEquals(WebElement expectedElement,
+ WebElement actualElement) {
+ while (expectedElement instanceof WrapsElement) {
+ expectedElement = ((WrapsElement) expectedElement)
+ .getWrappedElement();
+ }
+ while (actualElement instanceof WrapsElement) {
+ actualElement = ((WrapsElement) actualElement).getWrappedElement();
+ }
+
+ Assert.assertEquals(expectedElement, actualElement);
+ }
+
+ protected WebElement getActiveElement() {
+ return (WebElement) executeScript("return document.activeElement;");
+
+ }
+
+ protected void waitForThemeToChange(final String theme) {
+
+ final WebElement rootDiv = findElement(By
+ .xpath("//div[contains(@class,'v-app')]"));
+ waitUntil(new ExpectedCondition<Boolean>() {
+
+ @Override
+ public Boolean apply(WebDriver input) {
+ String rootClass = rootDiv.getAttribute("class").trim();
+
+ return rootClass.contains(theme);
+ }
+ }, 30);
+ }
+
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/BrowserStackBrowserFactory.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/BrowserStackBrowserFactory.java
new file mode 100644
index 0000000000..ab9f7018eb
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/BrowserStackBrowserFactory.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.util.logging.Logger;
+
+import org.openqa.selenium.Platform;
+import org.openqa.selenium.ie.InternetExplorerDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.shared.Version;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.testbench.parallel.DefaultBrowserFactory;
+
+/**
+ * Browser factory for the cloud test provider BrowserStack.
+ */
+public class BrowserStackBrowserFactory extends DefaultBrowserFactory {
+
+ @Override
+ public DesiredCapabilities create(Browser browser, String version,
+ Platform platform) {
+ DesiredCapabilities caps;
+
+ switch (browser) {
+ case CHROME:
+ caps = DesiredCapabilities.chrome();
+ caps.setVersion(version);
+ break;
+ case PHANTOMJS:
+ // This will not work on BrowserStack - should be filtered with
+ // browsers.exclude. However, we cannot throw an exception here as
+ // filtering only takes place if there is no exception.
+ caps = DesiredCapabilities.phantomjs();
+ caps.setVersion("1");
+ caps.setPlatform(Platform.LINUX);
+ break;
+ case SAFARI:
+ caps = DesiredCapabilities.safari();
+ caps.setVersion(version);
+ break;
+ case IE8:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("8");
+ caps.setCapability("browser", "IE");
+ caps.setCapability("browser_version", "8.0");
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case IE9:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("9");
+ caps.setCapability("browser", "IE");
+ caps.setCapability("browser_version", "9.0");
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case IE10:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("10");
+ caps.setCapability("browser", "IE");
+ caps.setCapability("browser_version", "10.0");
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case IE11:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("11");
+ caps.setCapability("browser", "IE");
+ caps.setCapability("browser_version", "11.0");
+ // There are 2 capabilities ie.ensureCleanSession and
+ // ensureCleanSession in Selenium
+ // IE 11 uses ie.ensureCleanSession
+ caps.setCapability("ie.ensureCleanSession", true);
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case FIREFOX:
+ caps = DesiredCapabilities.firefox();
+ caps.setVersion(version);
+ break;
+ default:
+ caps = DesiredCapabilities.firefox();
+ caps.setVersion(version);
+ caps.setPlatform(platform);
+ }
+
+ // BrowserStack specific parts
+
+ // for now, run all tests on Windows 7
+ if (!Browser.PHANTOMJS.equals(browser)) {
+ caps.setCapability("os", "Windows");
+ caps.setCapability("os_version", "7");
+ caps.setPlatform(Platform.WINDOWS);
+ }
+
+ // enable logging on BrowserStack
+ caps.setCapability("browserstack.debug", "true");
+
+ // tunnel
+ caps.setCapability("browserstack.local", "true");
+ // optionally, could also set browserstack.localIdentifier if we have a
+ // tunnel name
+
+ // build and project for easy identification in BrowserStack UI
+ caps.setCapability("project", "vaadin");
+ caps.setCapability("build", Version.getFullVersion());
+
+ // accept self-signed certificates
+ caps.setCapability("acceptSslCerts", "true");
+
+ caps.setCapability("resolution", "1680x1050");
+
+ getLogger().info("Using BrowserStack capabilities " + caps);
+
+ return caps;
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(BrowserStackBrowserFactory.class.getName());
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/CustomTestBenchCommandExecutor.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/CustomTestBenchCommandExecutor.java
new file mode 100644
index 0000000000..00d7788f8b
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/CustomTestBenchCommandExecutor.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.OutputType;
+import org.openqa.selenium.Point;
+import org.openqa.selenium.TakesScreenshot;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import com.vaadin.testbench.Parameters;
+import com.vaadin.testbench.screenshot.ImageComparison;
+import com.vaadin.testbench.screenshot.ImageFileUtil;
+
+/**
+ * Internal hack to support capturing screenshots for elements.
+ *
+ * Most parts are from TestBenchCommandExecutor and the feature should be
+ * integrated into TB4.
+ *
+ * @author Vaadin Ltd
+ */
+public class CustomTestBenchCommandExecutor {
+
+ private ImageComparison imageComparison = new ImageComparison();
+ private final WebDriver actualDriver;
+
+ public CustomTestBenchCommandExecutor(WebDriver driver) {
+ actualDriver = driver;
+ }
+
+ /**
+ * Compares the screenshot of the given element with the reference.
+ *
+ * Copied from TestBenchCommandExecutor
+ */
+ public boolean compareScreen(WebElement element, File reference,
+ boolean isIE8) throws IOException {
+ BufferedImage image = null;
+ try {
+ image = ImageIO.read(reference);
+ } catch (IIOException e) {
+ // Don't worry, an error screen shot will be generated that later
+ // can be used as the reference
+ }
+ return compareScreen(element, image, reference.getName(), isIE8);
+ }
+
+ /**
+ * Compares the screenshot of the given element with the reference.
+ *
+ * Copied from TestBenchCommandExecutor and added cropToElement
+ */
+ public boolean compareScreen(WebElement element, BufferedImage reference,
+ String referenceName, boolean isIE8) throws IOException {
+ for (int times = 0; times < Parameters.getMaxScreenshotRetries(); times++) {
+ BufferedImage screenshotImage = cropToElement(element,
+ ImageIO.read(new ByteArrayInputStream(
+ ((TakesScreenshot) actualDriver)
+ .getScreenshotAs(OutputType.BYTES))), isIE8);
+ if (reference == null) {
+ // Store the screenshot in the errors directory and fail the
+ // test
+ ImageFileUtil.createScreenshotDirectoriesIfNeeded();
+ ImageIO.write(screenshotImage, "png",
+ ImageFileUtil.getErrorScreenshotFile(referenceName));
+ getLogger().severe(
+ "No reference found for "
+ + referenceName
+ + " in "
+ + ImageFileUtil
+ .getScreenshotReferenceDirectory());
+ return false;
+ }
+ if (imageComparison.imageEqualToReference(screenshotImage,
+ reference, referenceName,
+ Parameters.getScreenshotComparisonTolerance())) {
+ return true;
+ }
+ pause(Parameters.getScreenshotRetryDelay());
+ }
+ return false;
+ }
+
+ /**
+ * Crops the image to show only the element. If the element is partly off
+ * screen, crops to show the part of the element which is in the screenshot
+ *
+ * @param element
+ * the element to retain in the screenshot
+ * @param fullScreen
+ * the full screen image
+ * @param isIE8
+ * true if the browser is IE8
+ * @return
+ * @throws IOException
+ */
+ public static BufferedImage cropToElement(WebElement element,
+ BufferedImage fullScreen, boolean isIE8) throws IOException {
+ Point loc = element.getLocation();
+ Dimension size = element.getSize();
+ int x = loc.x, y = loc.y;
+ int w = size.width;
+ int h = size.height;
+
+ if (isIE8) {
+ // IE8 border...
+ x += 2;
+ y += 2;
+ }
+ if (x >= 0 && x < fullScreen.getWidth()) {
+ // X loc on screen
+ // Get the part of the element which is on screen
+ w = Math.min(fullScreen.getWidth() - x, w);
+ } else {
+ throw new IOException("Element x is outside the screenshot (x: "
+ + x + ", y: " + y + ")");
+ }
+
+ if (y >= 0 && y < fullScreen.getHeight()) {
+ // Y loc on screen
+ // Get the part of the element which is on screen
+ h = Math.min(fullScreen.getHeight() - y, h);
+ } else {
+ throw new IOException("Element y is outside the screenshot (x: "
+ + x + ", y: " + y + ")");
+ }
+
+ return fullScreen.getSubimage(x, y, w, h);
+ }
+
+ private void pause(int delay) {
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(CustomTestBenchCommandExecutor.class.getName());
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/ExcludeFromSuite.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/ExcludeFromSuite.java
new file mode 100644
index 0000000000..dd061646be
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/ExcludeFromSuite.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marker interface for a TB3+ test class which will exclude the test from any
+ * test suite which automatically scans for test classes. Mostly useful for long
+ * tests which should not be run in every build.
+ *
+ * @since 7.1.10
+ * @author Vaadin Ltd
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface ExcludeFromSuite {
+
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/IncludeIfProperty.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/IncludeIfProperty.java
new file mode 100644
index 0000000000..789422c0c6
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/IncludeIfProperty.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to control inclusion of a test into a test suite.
+ * <p>
+ * The test will be included in the suite only if the given System property
+ * {@code property} has the given {@code value}.
+ * <p>
+ * Used by {@link TB3TestLocator}
+ *
+ * @since
+ * @author Vaadin Ltd
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface IncludeIfProperty {
+
+ String property();
+
+ String value();
+
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/MultiBrowserTest.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/MultiBrowserTest.java
new file mode 100644
index 0000000000..ec40510d96
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/MultiBrowserTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.parallel.Browser;
+
+/**
+ * Base class for tests which should be run on all supported browsers. The test
+ * is automatically launched for multiple browsers in parallel by the test
+ * runner.
+ *
+ * Sub classes can, but typically should not, restrict the browsers used by
+ * implementing a
+ *
+ * <pre>
+ * &#064;Parameters
+ * public static Collection&lt;DesiredCapabilities&gt; getBrowsersForTest() {
+ * }
+ * </pre>
+ *
+ * @author Vaadin Ltd
+ */
+public abstract class MultiBrowserTest extends PrivateTB3Configuration {
+
+ protected List<DesiredCapabilities> getBrowsersSupportingWebSocket() {
+ // No WebSocket support in IE8-9 and PhantomJS
+ return getBrowserCapabilities(Browser.IE10, Browser.IE11,
+ Browser.FIREFOX, Browser.CHROME);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersExcludingPhantomJS() {
+ return getBrowserCapabilities(Browser.IE8, Browser.IE9, Browser.IE10,
+ Browser.IE11, Browser.CHROME, Browser.FIREFOX);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersExcludingIE() {
+ return getBrowserCapabilities(Browser.FIREFOX, Browser.CHROME,
+ Browser.PHANTOMJS);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersExcludingFirefox() {
+ // this is sometimes needed as the Firefox driver causes extra mouseOut
+ // events that make tooltips disappear etc.
+ return getBrowserCapabilities(Browser.IE8, Browser.IE9, Browser.IE10,
+ Browser.IE11, Browser.CHROME, Browser.PHANTOMJS);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersExcludingIE8() {
+ return getBrowserCapabilities(Browser.IE9, Browser.IE10, Browser.IE11,
+ Browser.FIREFOX, Browser.CHROME, Browser.PHANTOMJS);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersSupportingShiftClick() {
+ return getBrowserCapabilities(Browser.IE8, Browser.IE9, Browser.IE10,
+ Browser.IE11, Browser.CHROME);
+ }
+
+ protected List<DesiredCapabilities> getIEBrowsersOnly() {
+ return getBrowserCapabilities(Browser.IE8, Browser.IE9, Browser.IE10,
+ Browser.IE11);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersSupportingContextMenu() {
+ // context menu doesn't work in phantom JS and works weirdly with IE8
+ // and selenium.
+ return getBrowserCapabilities(Browser.IE9, Browser.IE10, Browser.IE11,
+ Browser.FIREFOX, Browser.CHROME);
+ }
+
+ protected List<DesiredCapabilities> getBrowsersSupportingTooltip() {
+ // With IEDriver, the cursor seems to jump to default position after the
+ // mouse move, so we are not able to test the tooltip behaviour properly
+ // unless using requireWindowFocusForIE() { return true; } .
+ // See #13854.
+ // On Firefox, the driver causes additional mouseOut events causing the
+ // tooltip to disappear immediately. Tooltips may work in some
+ // particular cases, but not in general.
+ return getBrowserCapabilities(Browser.CHROME, Browser.PHANTOMJS);
+ }
+
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ // Uncomment Safari and Opera if those become tested browsers again.
+ return getBrowserCapabilities(Browser.IE8, Browser.IE9, Browser.IE10,
+ Browser.IE11, Browser.FIREFOX, Browser.CHROME,
+ Browser.PHANTOMJS /* , Browser.SAFARI, Browser.OPERA */);
+ }
+
+ protected List<DesiredCapabilities> getBrowserCapabilities(
+ Browser... browsers) {
+ List<DesiredCapabilities> capabilities = new ArrayList<DesiredCapabilities>();
+ for (Browser browser : browsers) {
+ capabilities.add(browser.getDesiredCapabilities());
+ }
+ return capabilities;
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/ParallelScheduler.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/ParallelScheduler.java
new file mode 100644
index 0000000000..ef9ee382d0
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/ParallelScheduler.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.junit.runners.model.RunnerScheduler;
+
+/**
+ * JUnit scheduler capable of running multiple tets in parallel. Each test is
+ * run in its own thread. Uses an {@link ExecutorService} to manage the threads.
+ *
+ * @author Vaadin Ltd
+ */
+public class ParallelScheduler implements RunnerScheduler {
+ private final List<Future<Object>> fResults = new ArrayList<Future<Object>>();
+ private ExecutorService fService;
+
+ /**
+ * Creates a parallel scheduler which will use the given executor service
+ * when submitting test jobs.
+ *
+ * @param service
+ * The service to use for tests
+ */
+ public ParallelScheduler(ExecutorService service) {
+ fService = service;
+ }
+
+ @Override
+ public void schedule(final Runnable childStatement) {
+ fResults.add(fService.submit(new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ childStatement.run();
+ return null;
+ }
+ }));
+ }
+
+ @Override
+ public void finished() {
+ for (Future<Object> each : fResults) {
+ try {
+ each.get();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/ParameterizedTB3Runner.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/ParameterizedTB3Runner.java
new file mode 100644
index 0000000000..06e1ea9df9
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/ParameterizedTB3Runner.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+/**
+ * TestBench test runner which supports static @Parameters annotated methods
+ * providing parameters for the corresponding setter.
+ * <p>
+ * {@code @Parameters public static Collection<String> getThemes() } creates one
+ * permutation for each value returned by {@code getThemes()}. The value is
+ * automatically assigned to the test instance using {@code setTheme(String)}
+ * before invoking the test method
+ *
+ * @author Vaadin Ltd
+ */
+public class ParameterizedTB3Runner extends TB3Runner {
+
+ public ParameterizedTB3Runner(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ List<FrameworkMethod> methods = super.computeTestMethods();
+
+ Map<Method, Collection<String>> parameters = new LinkedHashMap<Method, Collection<String>>();
+
+ // Find all @Parameters methods and invoke them to find out permutations
+
+ for (Method m : getTestClass().getJavaClass().getMethods()) {
+ Parameters p = m.getAnnotation(Parameters.class);
+ if (p == null) {
+ continue;
+ }
+
+ if (!m.getName().startsWith("get") || !m.getName().endsWith("s")) {
+ throw new IllegalStateException(
+ "Method "
+ + m.getName()
+ + " is annotated with @Parameter but is not named getSomeThings() as it should");
+ }
+
+ if (m.getParameterTypes().length != 0) {
+ throw new IllegalStateException(
+ "Method "
+ + m.getName()
+ + " annotated with @Parameter should not have any arguments");
+ }
+
+ if (!Modifier.isStatic(m.getModifiers())) {
+ throw new IllegalStateException("Method " + m.getName()
+ + " annotated with @Parameter must be static");
+ }
+
+ // getThemes -> setTheme
+ String setter = "set" + m.getName().substring("get".length());
+ setter = setter.substring(0, setter.length() - 1);
+ // property = property.substring(0, 1).toLowerCase()
+ // + property.substring(1);
+
+ Method setterMethod;
+ try {
+ setterMethod = getTestClass().getJavaClass().getMethod(setter,
+ String.class);
+ } catch (Exception e) {
+ throw new IllegalStateException("No setter " + setter
+ + " found in "
+ + getTestClass().getJavaClass().getName(), e);
+ }
+
+ Collection<String> values;
+ try {
+ values = (Collection<String>) m.invoke(null);
+ if (!values.isEmpty()) {
+ // Ignore any empty collections to allow e.g. integration
+ // tests to use "/demo" path by default without adding that
+ // to the screenshot name
+ parameters.put(setterMethod, values);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("The setter " + m.getName()
+ + " could not be invoked", e);
+ }
+ }
+
+ // Add method permutations for all @Parameters
+ for (Method setter : parameters.keySet()) {
+ List<FrameworkMethod> newMethods = new ArrayList<FrameworkMethod>();
+ for (FrameworkMethod m : methods) {
+
+ if (!(m instanceof TBMethod)) {
+ System.err.println("Unknown method type: "
+ + m.getClass().getName());
+ newMethods.add(m);
+ continue;
+ }
+
+ // testFoo
+ // testBar
+ // ->
+ // testFoo[valo]
+ // testFoo[runo]
+ // testBar[valo]
+ // testBar[runo]
+
+ for (final String value : parameters.get(setter)) {
+ newMethods.add(new TBMethodWithBefore((TBMethod) m, setter,
+ value));
+ }
+ }
+ // Update methods so next parameters will use all expanded methods
+ methods = newMethods;
+ }
+ return methods;
+ }
+
+ public static class TBMethodWithBefore extends TBMethod {
+
+ private Method setter;
+ private String value;
+ private TBMethod parent;
+
+ public TBMethodWithBefore(TBMethod m, Method setter, String value) {
+ super(m.getMethod(), m.getCapabilities());
+ parent = m;
+ this.setter = setter;
+ this.value = value;
+ }
+
+ @Override
+ public Object invokeExplosively(Object target, Object... params)
+ throws Throwable {
+ setter.invoke(target, value);
+ return parent.invokeExplosively(target, params);
+ }
+
+ @Override
+ public String getName() {
+ return parent.getName() + "[" + value + "]";
+ };
+
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java
new file mode 100644
index 0000000000..85104c784b
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/PrivateTB3Configuration.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2000-2013 Vaadind Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.junit.Assert;
+import org.openqa.selenium.ie.InternetExplorerDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.annotations.BrowserFactory;
+import com.vaadin.testbench.annotations.RunLocally;
+import com.vaadin.testbench.annotations.RunOnHub;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.testbench.parallel.BrowserUtil;
+
+/**
+ * Provides values for parameters which depend on where the test is run.
+ * Parameters should be configured in work/eclipse-run-selected-test.properties.
+ * A template is available in uitest/.
+ *
+ * @author Vaadin Ltd
+ */
+@RunOnHub("tb3-hub.intra.itmill.com")
+@BrowserFactory(VaadinBrowserFactory.class)
+public abstract class PrivateTB3Configuration extends ScreenshotTB3Test {
+ /**
+ *
+ */
+ public static final String SCREENSHOT_DIRECTORY = "com.vaadin.testbench.screenshot.directory";
+ private static final String HOSTNAME_PROPERTY = "com.vaadin.testbench.deployment.hostname";
+ private static final String RUN_LOCALLY_PROPERTY = "com.vaadin.testbench.runLocally";
+ private static final String ALLOW_RUN_LOCALLY_PROPERTY = "com.vaadin.testbench.allowRunLocally";
+ private static final String PORT_PROPERTY = "com.vaadin.testbench.deployment.port";
+ private static final String DEPLOYMENT_PROPERTY = "com.vaadin.testbench.deployment.url";
+ private static final String HUB_URL = "com.vaadin.testbench.hub.url";
+ private static final Properties properties = new Properties();
+ private static final File propertiesFile = new File("../work",
+ "eclipse-run-selected-test.properties");
+ private static final String FIREFOX_PATH = "firefox.path";
+ private static final String PHANTOMJS_PATH = "phantomjs.binary.path";
+
+ static {
+ if (propertiesFile.exists()) {
+ try {
+ properties.load(new FileInputStream(propertiesFile));
+ if (properties.containsKey(RUN_LOCALLY_PROPERTY)) {
+ System.setProperty("useLocalWebDriver", "true");
+ DesiredCapabilities localBrowser = getRunLocallyCapabilities();
+ System.setProperty(
+ "browsers.include",
+ localBrowser.getBrowserName()
+ + localBrowser.getVersion());
+ }
+ if (properties.containsKey(FIREFOX_PATH)) {
+ System.setProperty(FIREFOX_PATH,
+ properties.getProperty(FIREFOX_PATH));
+ }
+ if (properties.containsKey(PHANTOMJS_PATH)) {
+ System.setProperty(PHANTOMJS_PATH,
+ properties.getProperty(PHANTOMJS_PATH));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setup() throws Exception {
+ String allowRunLocally = getProperty(ALLOW_RUN_LOCALLY_PROPERTY);
+ if ((allowRunLocally == null || !allowRunLocally.equals("" + true))
+ && getClass().getAnnotation(RunLocally.class) != null) {
+ Assert.fail("@RunLocally annotation is not allowed by default in framework tests. "
+ + "See file uitest/eclipse-run-selected-test.properties for more information.");
+ }
+
+ super.setup();
+ }
+
+ @Override
+ public void setDesiredCapabilities(DesiredCapabilities desiredCapabilities) {
+ super.setDesiredCapabilities(desiredCapabilities);
+
+ if (BrowserUtil.isIE(desiredCapabilities)) {
+ if (requireWindowFocusForIE()) {
+ desiredCapabilities.setCapability(
+ InternetExplorerDriver.REQUIRE_WINDOW_FOCUS, true);
+ }
+ if (!usePersistentHoverForIE()) {
+ desiredCapabilities.setCapability(
+ InternetExplorerDriver.ENABLE_PERSISTENT_HOVERING,
+ false);
+ }
+ if (!useNativeEventsForIE()) {
+ desiredCapabilities.setCapability(
+ InternetExplorerDriver.NATIVE_EVENTS, false);
+ }
+ }
+
+ desiredCapabilities.setCapability("project", "Vaadin Framework");
+ desiredCapabilities.setCapability("build", String.format("%s / %s",
+ getDeploymentHostname(), Calendar.getInstance().getTime()));
+ desiredCapabilities.setCapability(
+ "name",
+ String.format("%s.%s", getClass().getCanonicalName(),
+ testName.getMethodName()));
+ }
+
+ protected static DesiredCapabilities getRunLocallyCapabilities() {
+ VaadinBrowserFactory factory = new VaadinBrowserFactory();
+ try {
+ return factory.create(Browser.valueOf(properties.getProperty(
+ RUN_LOCALLY_PROPERTY).toUpperCase()));
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ System.err.println("Falling back to FireFox");
+ }
+ return factory.create(Browser.FIREFOX);
+ }
+
+ protected static String getProperty(String name) {
+ String property = properties.getProperty(name);
+ if (property == null) {
+ property = System.getProperty(name);
+ }
+
+ return property;
+ }
+
+ @Override
+ protected String getScreenshotDirectory() {
+ String screenshotDirectory = getProperty(SCREENSHOT_DIRECTORY);
+ if (screenshotDirectory == null) {
+ throw new RuntimeException(
+ "No screenshot directory defined. Use -D"
+ + SCREENSHOT_DIRECTORY + "=<path>");
+ }
+ return screenshotDirectory;
+ }
+
+ @Override
+ protected String getHubURL() {
+ String hubUrl = getProperty(HUB_URL);
+ if (hubUrl == null || hubUrl.trim().isEmpty()) {
+ return super.getHubURL();
+ }
+
+ return hubUrl;
+ }
+
+ @Override
+ protected String getBaseURL() {
+ if (isRunLocally()) {
+ return "http://localhost:8888";
+ }
+ String url = getProperty(DEPLOYMENT_PROPERTY);
+ if (url == null || url.trim().isEmpty()) {
+ return super.getBaseURL();
+ }
+ return url;
+ }
+
+ @Override
+ protected String getDeploymentHostname() {
+ if (isRunLocally()) {
+ return "localhost";
+ }
+ return getConfiguredDeploymentHostname();
+ }
+
+ protected boolean isRunLocally() {
+ if (properties.containsKey(RUN_LOCALLY_PROPERTY)) {
+ return true;
+ }
+
+ if (properties.containsKey(ALLOW_RUN_LOCALLY_PROPERTY)
+ && properties.get(ALLOW_RUN_LOCALLY_PROPERTY).equals("true")
+ && getClass().getAnnotation(RunLocally.class) != null) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the hostname that tests are configured to use.
+ *
+ * @return the host name configuration value
+ */
+ public static String getConfiguredDeploymentHostname() {
+ String hostName = getProperty(HOSTNAME_PROPERTY);
+
+ if (hostName == null || "".equals(hostName)) {
+ hostName = findAutoHostname();
+ }
+
+ return hostName;
+ }
+
+ @Override
+ protected int getDeploymentPort() {
+ return getConfiguredDeploymentPort();
+ }
+
+ /**
+ * Gets the port that tests are configured to use.
+ *
+ * @return the port configuration value
+ */
+ public static int getConfiguredDeploymentPort() {
+ String portString = getProperty(PORT_PROPERTY);
+
+ int port = 8888;
+ if (portString != null && !"".equals(portString)) {
+ port = Integer.parseInt(portString);
+ }
+
+ return port;
+ }
+
+ /**
+ * Tries to automatically determine the IP address of the machine the test
+ * is running on.
+ *
+ * @return An IP address of one of the network interfaces in the machine.
+ * @throws RuntimeException
+ * if there was an error or no IP was found
+ */
+ private static String findAutoHostname() {
+ try {
+ Enumeration<NetworkInterface> interfaces = NetworkInterface
+ .getNetworkInterfaces();
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface nwInterface = interfaces.nextElement();
+ if (!nwInterface.isUp() || nwInterface.isLoopback()
+ || nwInterface.isVirtual()) {
+ continue;
+ }
+ Enumeration<InetAddress> addresses = nwInterface
+ .getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress address = addresses.nextElement();
+ if (address.isLoopbackAddress()) {
+ continue;
+ }
+ if (address.isSiteLocalAddress()) {
+ return address.getHostAddress();
+ }
+ }
+ }
+ } catch (SocketException e) {
+ throw new RuntimeException("Could not enumerate ");
+ }
+
+ throw new RuntimeException(
+ "No compatible (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) ip address found.");
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/RetryOnFail.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/RetryOnFail.java
new file mode 100644
index 0000000000..7a1656a0d2
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/RetryOnFail.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class RetryOnFail implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return statement(base, description);
+ }
+
+ private Statement statement(final Statement base,
+ final Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ Throwable caughtThrowable = null;
+ int retryCount = getRetryCount();
+
+ for (int i = 0; i <= retryCount; i++) {
+ try {
+ base.evaluate();
+ return;
+ } catch (Throwable t) {
+ caughtThrowable = t;
+ System.err.println(String.format(
+ "%s: run %s/%s failed.",
+ description.getDisplayName(), i + 1,
+ retryCount + 1));
+ System.err.println(t.getMessage());
+ }
+ }
+ throw caughtThrowable;
+ }
+
+ private int getRetryCount() {
+ String retryCount = System
+ .getProperty("com.vaadin.testbench.max.retries");
+
+ if (retryCount != null && retryCount != "") {
+ return Integer.parseInt(retryCount);
+ }
+
+ return 0;
+ }
+ };
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/SauceLabsBrowserFactory.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/SauceLabsBrowserFactory.java
new file mode 100644
index 0000000000..8eae64d0b8
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/SauceLabsBrowserFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import org.openqa.selenium.Platform;
+import org.openqa.selenium.ie.InternetExplorerDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.shared.Version;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.testbench.parallel.DefaultBrowserFactory;
+
+public class SauceLabsBrowserFactory extends DefaultBrowserFactory {
+
+ @Override
+ public DesiredCapabilities create(Browser browser) {
+ String version = "";
+ // need to pick a version to request, but for these two auto-updating
+ // browsers there is a special value "latest" (and "latest-1",
+ // "latest-2")
+ if (Browser.FIREFOX.equals(browser) || Browser.CHROME.equals(browser)) {
+ version = "latest";
+ }
+ return create(browser, version, Platform.ANY);
+ }
+
+ @Override
+ public DesiredCapabilities create(Browser browser, String version,
+ Platform platform) {
+ DesiredCapabilities caps;
+
+ switch (browser) {
+ case CHROME:
+ caps = DesiredCapabilities.chrome();
+ caps.setVersion(version);
+ break;
+ case PHANTOMJS:
+ // This will not work on SauceLabs - should be filtered with
+ // browsers.exclude. However, we cannot throw an exception here as
+ // filtering only takes place if there is no exception.
+ caps = DesiredCapabilities.phantomjs();
+ caps.setVersion("1");
+ caps.setPlatform(Platform.LINUX);
+ break;
+ case SAFARI:
+ caps = DesiredCapabilities.safari();
+ caps.setVersion(version);
+ break;
+ case IE8:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("8.0");
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case IE9:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("9.0");
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case IE10:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("10.0");
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case IE11:
+ caps = DesiredCapabilities.internetExplorer();
+ caps.setVersion("11.0");
+ // There are 2 capabilities ie.ensureCleanSession and
+ // ensureCleanSession in Selenium
+ // IE 11 uses ie.ensureCleanSession
+ caps.setCapability("ie.ensureCleanSession", true);
+ caps.setCapability(InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION,
+ true);
+ break;
+ case FIREFOX:
+ caps = DesiredCapabilities.firefox();
+ caps.setVersion(version);
+ break;
+ default:
+ caps = DesiredCapabilities.firefox();
+ caps.setVersion(version);
+ caps.setPlatform(platform);
+ }
+
+ if (!Browser.PHANTOMJS.equals(browser)) {
+ caps.setCapability("platform", "Windows 7");
+ }
+
+ // accept self-signed certificates
+ caps.setCapability("acceptSslCerts", "true");
+
+ // SauceLabs specific parts
+
+ caps.setCapability("screenResolution", "1680x1050");
+
+ // build and project for easy identification in SauceLabs UI
+ caps.setCapability("build", Version.getFullVersion());
+
+ return caps;
+ }
+
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/ScreenshotTB3Test.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/ScreenshotTB3Test.java
new file mode 100644
index 0000000000..d94e05efdb
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/ScreenshotTB3Test.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.Parameters;
+import com.vaadin.testbench.ScreenshotOnFailureRule;
+import com.vaadin.testbench.parallel.BrowserUtil;
+import com.vaadin.testbench.screenshot.ImageFileUtil;
+
+/**
+ * Base class which provides functionality for tests which use the automatic
+ * screenshot comparison function.
+ *
+ * @author Vaadin Ltd
+ */
+public abstract class ScreenshotTB3Test extends AbstractTB3Test {
+
+ @Rule
+ public ScreenshotOnFailureRule screenshotOnFailure = new ScreenshotOnFailureRule(
+ this, true) {
+
+ @Override
+ protected void failed(Throwable throwable, Description description) {
+ super.failed(throwable, description);
+ closeApplication();
+ }
+
+ @Override
+ protected void succeeded(Description description) {
+ super.succeeded(description);
+ closeApplication();
+ }
+
+ @Override
+ protected File getErrorScreenshotFile(Description description) {
+ return ImageFileUtil
+ .getErrorScreenshotFile(getScreenshotFailureName());
+ };
+ };
+
+ private String screenshotBaseName;
+
+ @Rule
+ public TestRule watcher = new TestWatcher() {
+
+ @Override
+ protected void starting(org.junit.runner.Description description) {
+ Class<?> testClass = description.getTestClass();
+ // Runner adds [BrowserName] which we do not want to use in the
+ // screenshot name
+ String testMethod = description.getMethodName();
+ testMethod = testMethod.replaceAll("\\[.*\\]", "");
+
+ String className = testClass.getSimpleName();
+ screenshotBaseName = className + "-" + testMethod;
+ }
+ };
+
+ /**
+ * Contains a list of screenshot identifiers for which
+ * {@link #compareScreen(String)} has failed during the test
+ */
+ private List<String> screenshotFailures;
+
+ /**
+ * Defines TestBench screen comparison parameters before each test run
+ */
+ @Before
+ public void setupScreenComparisonParameters() {
+ screenshotFailures = new ArrayList<String>();
+
+ Parameters.setScreenshotErrorDirectory(getScreenshotErrorDirectory());
+ Parameters
+ .setScreenshotReferenceDirectory(getScreenshotReferenceDirectory());
+ }
+
+ /**
+ * Grabs a screenshot and compares with the reference image with the given
+ * identifier. Supports alternative references and will succeed if the
+ * screenshot matches at least one of the references.
+ *
+ * In case of a failed comparison this method stores the grabbed screenshots
+ * in the error directory as defined by
+ * {@link #getScreenshotErrorDirectory()}. It will also generate a html file
+ * in the same directory, comparing the screenshot with the first found
+ * reference.
+ *
+ * @param identifier
+ * @throws IOException
+ */
+ protected void compareScreen(String identifier) throws IOException {
+ compareScreen(null, identifier);
+ }
+
+ protected void compareScreen(WebElement element, String identifier)
+ throws IOException {
+ if (identifier == null || identifier.isEmpty()) {
+ throw new IllegalArgumentException("Empty identifier not supported");
+ }
+
+ File mainReference = getScreenshotReferenceFile(identifier);
+
+ List<File> referenceFiles = findReferenceAndAlternatives(mainReference);
+ List<File> failedReferenceFiles = new ArrayList<File>();
+
+ for (File referenceFile : referenceFiles) {
+ boolean match = false;
+ if (element == null) {
+ // Full screen
+ match = testBench(driver).compareScreen(referenceFile);
+ } else {
+ // Only the element
+ match = customTestBench(driver).compareScreen(element,
+ referenceFile,
+ BrowserUtil.isIE8(getDesiredCapabilities()));
+ }
+ if (match) {
+ // There might be failure files because of retries in TestBench.
+ deleteFailureFiles(getErrorFileFromReference(referenceFile));
+ break;
+ } else {
+ failedReferenceFiles.add(referenceFile);
+ }
+ }
+
+ File referenceToKeep = null;
+ if (failedReferenceFiles.size() == referenceFiles.size()) {
+ // Ensure we use the correct browser version (e.g. if running IE11
+ // and only an IE 10 reference was available, then mainReference
+ // will be for IE 10, not 11)
+ String originalName = getScreenshotReferenceName(identifier);
+ File exactVersionFile = new File(originalName);
+
+ if (!exactVersionFile.equals(mainReference)) {
+ // Rename png+html to have the correct version
+ File correctPng = getErrorFileFromReference(exactVersionFile);
+ File producedPng = getErrorFileFromReference(mainReference);
+ File correctHtml = htmlFromPng(correctPng);
+ File producedHtml = htmlFromPng(producedPng);
+
+ producedPng.renameTo(correctPng);
+ producedHtml.renameTo(correctHtml);
+ referenceToKeep = exactVersionFile;
+ screenshotFailures.add(exactVersionFile.getName());
+ } else {
+ // All comparisons failed, keep the main error image + HTML
+ screenshotFailures.add(mainReference.getName());
+ referenceToKeep = mainReference;
+ }
+ }
+
+ // Remove all PNG/HTML files we no longer need (failed alternative
+ // references or all error files (PNG/HTML) if comparison succeeded)
+ for (File failedAlternative : failedReferenceFiles) {
+ File failurePng = getErrorFileFromReference(failedAlternative);
+ if (failedAlternative != referenceToKeep) {
+ // Delete png + HTML
+ deleteFailureFiles(failurePng);
+ }
+ }
+ if (referenceToKeep != null) {
+ File errorPng = getErrorFileFromReference(referenceToKeep);
+ enableAutoswitch(new File(errorPng.getParentFile(),
+ errorPng.getName() + ".html"));
+ }
+ }
+
+ private CustomTestBenchCommandExecutor customTestBench = null;
+
+ private CustomTestBenchCommandExecutor customTestBench(WebDriver driver) {
+ if (customTestBench == null) {
+ customTestBench = new CustomTestBenchCommandExecutor(driver);
+ }
+
+ return customTestBench;
+ }
+
+ private void enableAutoswitch(File htmlFile) throws FileNotFoundException,
+ IOException {
+ if (htmlFile == null || !htmlFile.exists()) {
+ return;
+ }
+
+ String html = FileUtils.readFileToString(htmlFile);
+
+ html = html.replace("body onclick=\"",
+ "body onclick=\"clearInterval(autoSwitch);");
+ html = html.replace("</script>",
+ ";autoSwitch=setInterval(switchImage,500);</script>");
+
+ FileUtils.writeStringToFile(htmlFile, html);
+ }
+
+ private void deleteFailureFiles(File failurePng) {
+ File failureHtml = htmlFromPng(failurePng);
+
+ failurePng.delete();
+ failureHtml.delete();
+ }
+
+ /**
+ * Returns a new File which points to a .html file instead of the given .png
+ * file
+ *
+ * @param png
+ * @return
+ */
+ private static File htmlFromPng(File png) {
+ return new File(png.getParentFile(), png.getName().replaceAll(
+ "\\.png$", ".png.html"));
+ }
+
+ /**
+ *
+ * @param referenceFile
+ * The reference image file (in the directory defined by
+ * {@link #getScreenshotReferenceDirectory()})
+ * @return the file name of the file generated in the directory defined by
+ * {@link #getScreenshotErrorDirectory()} if comparison with the
+ * given reference image fails.
+ */
+ private File getErrorFileFromReference(File referenceFile) {
+
+ String absolutePath = referenceFile.getAbsolutePath();
+ String screenshotReferenceDirectory = getScreenshotReferenceDirectory();
+ String screenshotErrorDirectory = getScreenshotErrorDirectory();
+ // We throw an exception to safeguard against accidental reference
+ // deletion. See (#14446)
+ if (!absolutePath.contains(screenshotReferenceDirectory)) {
+ throw new IllegalStateException(
+ "Reference screenshot not in reference directory. Screenshot path: '"
+ + absolutePath + "', directory path: '"
+ + screenshotReferenceDirectory + "'");
+ }
+ return new File(absolutePath.replace(screenshotReferenceDirectory,
+ screenshotErrorDirectory));
+ }
+
+ /**
+ * Finds alternative references for the given files
+ *
+ * @param reference
+ * @return all references which should be considered when comparing with the
+ * given files, including the given reference
+ */
+ private List<File> findReferenceAndAlternatives(File reference) {
+ List<File> files = new ArrayList<File>();
+ files.add(reference);
+
+ File screenshotDir = reference.getParentFile();
+ String name = reference.getName();
+ // Remove ".png"
+ String nameBase = name.substring(0, name.length() - 4);
+ for (int i = 1;; i++) {
+ File file = new File(screenshotDir, nameBase + "_" + i + ".png");
+ if (file.exists()) {
+ files.add(file);
+ } else {
+ break;
+ }
+ }
+
+ return files;
+ }
+
+ /**
+ * @param testName
+ * @return the reference file name to use for the given browser, as
+ * described by {@literal capabilities}, and identifier
+ */
+ private File getScreenshotReferenceFile(String identifier) {
+ DesiredCapabilities capabilities = getDesiredCapabilities();
+
+ String originalName = getScreenshotReferenceName(identifier);
+ File exactVersionFile = new File(originalName);
+ if (exactVersionFile.exists()) {
+ return exactVersionFile;
+ }
+
+ String browserVersion = capabilities.getVersion();
+
+ // compare against screenshots for this version and older
+ // default such that if no particular version is requested, compare with
+ // any version
+ int maxVersion = 100;
+ if (browserVersion.matches("\\d+")) {
+ maxVersion = Integer.parseInt(browserVersion);
+ }
+ for (int version = maxVersion; version > 0; version--) {
+ String fileName = getScreenshotReferenceName(identifier, version);
+ File oldVersionFile = new File(fileName);
+ if (oldVersionFile.exists()) {
+ return oldVersionFile;
+ }
+ }
+
+ return exactVersionFile;
+ }
+
+ /**
+ * @return the base directory of 'reference' and 'errors' screenshots
+ */
+ protected abstract String getScreenshotDirectory();
+
+ /**
+ * @return the base directory of 'reference' and 'errors' screenshots with a
+ * trailing file separator
+ */
+ private String getScreenshotDirectoryWithTrailingSeparator() {
+ String screenshotDirectory = getScreenshotDirectory();
+ if (!screenshotDirectory.endsWith(File.separator)) {
+ screenshotDirectory += File.separator;
+ }
+ return screenshotDirectory;
+ }
+
+ /**
+ * @return the directory where reference images are stored (the 'reference'
+ * folder inside the screenshot directory)
+ */
+ private String getScreenshotReferenceDirectory() {
+ return getScreenshotDirectoryWithTrailingSeparator() + "reference";
+ }
+
+ /**
+ * @return the directory where comparison error images should be created
+ * (the 'errors' folder inside the screenshot directory)
+ */
+ private String getScreenshotErrorDirectory() {
+ return getScreenshotDirectoryWithTrailingSeparator() + "errors";
+ }
+
+ /**
+ * Checks if any screenshot comparisons failures occurred during the test
+ * and combines all comparison errors into one exception
+ *
+ * @throws IOException
+ * If there were failures during the test
+ */
+ @After
+ public void checkCompareFailures() throws IOException {
+ if (screenshotFailures != null && !screenshotFailures.isEmpty()) {
+ throw new IOException(
+ "The following screenshots did not match the reference: "
+ + screenshotFailures.toString());
+ }
+
+ }
+
+ /**
+ * @return the name of a "failure" image which is stored in the folder
+ * defined by {@link #getScreenshotErrorDirectory()} when the test
+ * fails
+ */
+ private String getScreenshotFailureName() {
+ return getScreenshotBaseName() + "_" + getUniqueIdentifier(null)
+ + "-failure.png";
+ }
+
+ /**
+ * @return the base name used for screenshots. This is the first part of the
+ * screenshot file name, typically created as "testclass-testmethod"
+ */
+ public String getScreenshotBaseName() {
+ return screenshotBaseName;
+ }
+
+ /**
+ * Returns the name of the reference file based on the given parameters.
+ *
+ * @param testName
+ * @param capabilities
+ * @param identifier
+ * @return the full path of the reference
+ */
+ private String getScreenshotReferenceName(String identifier) {
+ return getScreenshotReferenceName(identifier, null);
+ }
+
+ /**
+ * Returns the name of the reference file based on the given parameters. The
+ * version given in {@literal capabilities} is used unless it is overridden
+ * by the {@literal versionOverride} parameter.
+ *
+ * @param testName
+ * @param capabilities
+ * @param identifier
+ * @return the full path of the reference
+ */
+ private String getScreenshotReferenceName(String identifier,
+ Integer versionOverride) {
+ return getScreenshotReferenceDirectory() + File.separator
+ + getScreenshotBaseName() + "_"
+ + getUniqueIdentifier(versionOverride) + "_" + identifier
+ + ".png";
+ }
+
+ private String getUniqueIdentifier(Integer versionOverride) {
+ String testNameAndParameters = testName.getMethodName();
+ // runTest-wildfly9-nginx[Windows_Firefox_24][/buffering/demo][valo]
+
+ String parameters = testNameAndParameters.substring(
+ testNameAndParameters.indexOf("[") + 1,
+ testNameAndParameters.length() - 1);
+ // Windows_Firefox_24][/buffering/demo][valo
+
+ parameters = parameters.replace("][", "_");
+ // Windows_Firefox_24_/buffering/demo_valo
+
+ parameters = parameters.replace("/", "");
+ // Windows_Firefox_24_bufferingdemo_valo
+
+ if (versionOverride != null) {
+ // Windows_Firefox_17_bufferingdemo_valo
+ parameters = parameters.replaceFirst("_"
+ + getDesiredCapabilities().getVersion(), "_"
+ + versionOverride);
+ }
+ return parameters;
+ }
+
+ /**
+ * Returns the base name of the screenshot in the error directory. This is a
+ * name so that all files matching {@link #getScreenshotErrorBaseName()}*
+ * are owned by this test instance (taking into account
+ * {@link #getDesiredCapabilities()}) and can safely be removed before
+ * running this test.
+ */
+ private String getScreenshotErrorBaseName() {
+ return getScreenshotReferenceName("dummy", null).replace(
+ getScreenshotReferenceDirectory(),
+ getScreenshotErrorDirectory()).replace("_dummy.png", "");
+ }
+
+ /**
+ * Removes any old screenshots related to this test from the errors
+ * directory before running the test
+ */
+ @Before
+ public void cleanErrorDirectory() {
+ // Remove any screenshots for this test from the error directory
+ // before running it. Leave unrelated files as-is
+ File errorDirectory = new File(getScreenshotErrorDirectory());
+
+ // Create errors directory if it does not exist
+ if (!errorDirectory.exists()) {
+ errorDirectory.mkdirs();
+ }
+
+ final String errorBase = getScreenshotErrorBaseName();
+ File[] files = errorDirectory.listFiles(new FileFilter() {
+
+ @Override
+ public boolean accept(File pathname) {
+ String thisFile = pathname.getAbsolutePath();
+ if (thisFile.startsWith(errorBase)) {
+ return true;
+ }
+ return false;
+ }
+ });
+ for (File f : files) {
+ f.delete();
+ }
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTest.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTest.java
new file mode 100644
index 0000000000..b5389d3eec
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.parallel.Browser;
+
+public abstract class SingleBrowserTest extends PrivateTB3Configuration {
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ if (isRunLocally()) {
+ return Collections.singletonList(getRunLocallyCapabilities());
+ }
+ return Collections.singletonList(Browser.PHANTOMJS
+ .getDesiredCapabilities());
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTestPhantomJS2.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTestPhantomJS2.java
new file mode 100644
index 0000000000..7447a3c056
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/SingleBrowserTestPhantomJS2.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+public abstract class SingleBrowserTestPhantomJS2 extends
+ PrivateTB3Configuration {
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ return Collections.singletonList(PHANTOMJS2());
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3Runner.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3Runner.java
new file mode 100644
index 0000000000..4917e398cf
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3Runner.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+import org.apache.http.client.HttpClient;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.InitializationError;
+import org.openqa.selenium.remote.internal.ApacheHttpClient;
+import org.openqa.selenium.remote.internal.HttpClientFactory;
+
+import com.vaadin.testbench.parallel.ParallelRunner;
+
+/**
+ * This runner is loosely based on FactoryTestRunner by Ted Young
+ * (http://tedyoung.me/2011/01/23/junit-runtime-tests-custom-runners/). The
+ * generated test names give information about the parameters used (unlike
+ * {@link Parameterized}).
+ *
+ * @since 7.1
+ */
+public class TB3Runner extends ParallelRunner {
+
+ /**
+ * Socket timeout for HTTP connections to the grid hub. The connection is
+ * closed after 30 minutes of inactivity to avoid builds hanging for up to
+ * three hours per connection if the test client crashes/hangs.
+ */
+ private static final int SOCKET_TIMEOUT = 30 * 60 * 1000;
+
+ static {
+
+ // reduce socket timeout to avoid tests hanging for three hours
+ try {
+ Field field = ApacheHttpClient.Factory.class
+ .getDeclaredField("defaultClientFactory");
+ assert (Modifier.isStatic(field.getModifiers()));
+ field.setAccessible(true);
+ field.set(null, new HttpClientFactory() {
+ @Override
+ public HttpClient getGridHttpClient(int connection_timeout,
+ int socket_timeout) {
+
+ if (socket_timeout == 0 || socket_timeout > SOCKET_TIMEOUT) {
+ return super.getGridHttpClient(connection_timeout,
+ SOCKET_TIMEOUT);
+ }
+
+ return super.getGridHttpClient(connection_timeout,
+ socket_timeout);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Changing socket timeout for TestBench failed", e);
+ }
+ }
+
+ public TB3Runner(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestLocator.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestLocator.java
new file mode 100644
index 0000000000..eb0861757e
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestLocator.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.net.JarURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+
+public class TB3TestLocator {
+ /**
+ * Traverses the directory on the classpath (inside or outside a Jar file)
+ * specified by 'basePackage'. Collects all classes inside the location
+ * which can be assigned to 'baseClass' except for classes inside packages
+ * listed in 'ignoredPackages'.
+ *
+ * @param baseClass
+ * @param basePackage
+ * @param ignorePackages
+ * @return
+ */
+ public Class<?>[] findTests(Class<? extends AbstractTB3Test> baseClass,
+ String basePackage, String[] ignorePackages) {
+ try {
+ List<?> l = findClasses(baseClass, basePackage, ignorePackages);
+ return l.toArray(new Class[] {});
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Traverses the directory on the classpath (inside or outside a Jar file)
+ * specified by 'basePackage'. Collects all classes inside the location
+ * which can be assigned to 'baseClass' except for classes inside packages
+ * listed in 'ignoredPackages'.
+ *
+ * @param baseClass
+ * @param basePackage
+ * @param ignoredPackages
+ * @return
+ * @throws IOException
+ */
+ protected <T> List<Class<? extends T>> findClasses(Class<T> baseClass,
+ String basePackage, String[] ignoredPackages) throws IOException {
+ List<Class<? extends T>> classes = new ArrayList<Class<? extends T>>();
+ String basePackageDirName = "/" + basePackage.replace('.', '/');
+ URL location = baseClass.getResource(basePackageDirName);
+ if (location.getProtocol().equals("file")) {
+ try {
+ File f = new File(location.toURI());
+ if (!f.exists()) {
+ throw new IOException("Directory " + f.toString()
+ + " does not exist");
+ }
+ findPackages(f, basePackage, baseClass, classes,
+ ignoredPackages);
+ } catch (URISyntaxException e) {
+ throw new IOException(e.getMessage());
+ }
+ } else if (location.getProtocol().equals("jar")) {
+ JarURLConnection juc = (JarURLConnection) location.openConnection();
+ findClassesInJar(juc, basePackage, baseClass, classes);
+ }
+
+ Collections.sort(classes, new Comparator<Class<? extends T>>() {
+
+ @Override
+ public int compare(Class<? extends T> o1, Class<? extends T> o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+
+ });
+
+ return classes;
+ }
+
+ /**
+ * Traverses the given directory and collects all classes which are inside
+ * the given 'javaPackage' and can be assigned to the given 'baseClass'. The
+ * found classes are added to 'result'.
+ *
+ * @param parent
+ * The directory to traverse
+ * @param javaPackage
+ * The java package which 'parent' contains
+ * @param baseClass
+ * The class which the target classes extend
+ * @param result
+ * The collection to which found classes are added
+ * @param ignoredPackages
+ * A collection of packages (including sub packages) to ignore
+ */
+ private <T> void findPackages(File parent, String javaPackage,
+ Class<T> baseClass, Collection<Class<? extends T>> result,
+ String[] ignoredPackages) {
+ for (String ignoredPackage : ignoredPackages) {
+ if (javaPackage.equals(ignoredPackage)) {
+ return;
+ }
+ }
+
+ for (File file : parent.listFiles()) {
+ if (file.isDirectory()) {
+ findPackages(file, javaPackage + "." + file.getName(),
+ baseClass, result, ignoredPackages);
+ } else if (file.getName().endsWith(".class")) {
+ String fullyQualifiedClassName = javaPackage + "."
+ + file.getName().replace(".class", "");
+ addClassIfMatches(result, fullyQualifiedClassName, baseClass);
+ }
+ }
+
+ }
+
+ /**
+ * Traverses a Jar file using the given connection and collects all classes
+ * which are inside the given 'javaPackage' and can be assigned to the given
+ * 'baseClass'. The found classes are added to 'result'.
+ *
+ * @param javaPackage
+ * The java package containing the classes (classes may be in a
+ * sub package)
+ * @param baseClass
+ * The class which the target classes extend
+ * @param result
+ * The collection to which found classes are added
+ * @throws IOException
+ */
+ private <T> void findClassesInJar(JarURLConnection juc, String javaPackage,
+ Class<T> baseClass, Collection<Class<? extends T>> result)
+ throws IOException {
+ String javaPackageDir = javaPackage.replace('.', '/');
+ Enumeration<JarEntry> ent = juc.getJarFile().entries();
+ while (ent.hasMoreElements()) {
+ JarEntry e = ent.nextElement();
+ if (e.getName().endsWith(".class")
+ && e.getName().startsWith(javaPackageDir)) {
+ String fullyQualifiedClassName = e.getName().replace('/', '.')
+ .replace(".class", "");
+ addClassIfMatches(result, fullyQualifiedClassName, baseClass);
+ }
+ }
+ }
+
+ /**
+ * Verifies that the class represented by 'fullyQualifiedClassName' can be
+ * loaded, assigned to 'baseClass' and is not an abstract or anonymous
+ * class.
+ *
+ * @param result
+ * The collection to add to
+ * @param fullyQualifiedClassName
+ * The candidate class
+ * @param baseClass
+ * The class 'fullyQualifiedClassName' should be assignable to
+ */
+ @SuppressWarnings("unchecked")
+ protected <T> void addClassIfMatches(Collection<Class<? extends T>> result,
+ String fullyQualifiedClassName, Class<T> baseClass) {
+ try {
+ // Try to load the class
+
+ Class<?> c = Class.forName(fullyQualifiedClassName);
+ if (!baseClass.isAssignableFrom(c)) {
+ return;
+ }
+ if (!includeInSuite(c)) {
+ return;
+ }
+
+ if (!Modifier.isAbstract(c.getModifiers()) && !c.isAnonymousClass()) {
+ result.add((Class<? extends T>) c);
+ }
+ } catch (Exception e) {
+ // Could ignore that class cannot be loaded
+ e.printStackTrace();
+ } catch (LinkageError e) {
+ // Ignore. Client side classes will at least throw LinkageErrors
+ }
+
+ }
+
+ /**
+ * @return true if the class should be included in the suite, false if not
+ */
+ private boolean includeInSuite(Class<?> c) {
+ if (c.getAnnotation(ExcludeFromSuite.class) != null) {
+ return false;
+ }
+
+ IncludeIfProperty includeIfProperty = c
+ .getAnnotation(IncludeIfProperty.class);
+ if (includeIfProperty != null) {
+ String includeValue = includeIfProperty.value();
+ String systemPropertyValue = System.getProperty(includeIfProperty
+ .property());
+ if (!includeValue.equals(systemPropertyValue)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestSuite.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestSuite.java
new file mode 100644
index 0000000000..c68d59a4eb
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/TB3TestSuite.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.tests.tb3;
+
+import java.io.IOException;
+
+import org.junit.runners.model.InitializationError;
+
+import com.vaadin.testbench.parallel.ParallelTestSuite;
+
+/**
+ * Test suite which consists of all the TB3 tests passed in the constructor.
+ * Runs the tests in parallel using a {@link ParallelScheduler}
+ *
+ * @author Vaadin Ltd
+ */
+public class TB3TestSuite extends ParallelTestSuite {
+
+ public TB3TestSuite(Class<?> klass,
+ Class<? extends AbstractTB3Test> baseClass, String basePackage,
+ String[] ignorePackages) throws InitializationError, IOException {
+ this(klass, baseClass, basePackage, ignorePackages,
+ new TB3TestLocator());
+ }
+
+ public TB3TestSuite(Class<?> klass,
+ Class<? extends AbstractTB3Test> baseClass, String basePackage,
+ String[] ignorePackages, TB3TestLocator locator)
+ throws InitializationError, IOException {
+ super(klass, locator
+ .findClasses(baseClass, basePackage, ignorePackages).toArray(
+ new Class<?>[] {}));
+ }
+}
diff --git a/uitest-common/src/main/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java b/uitest-common/src/main/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java
new file mode 100644
index 0000000000..2389811ff0
--- /dev/null
+++ b/uitest-common/src/main/java/com/vaadin/tests/tb3/VaadinBrowserFactory.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.tb3;
+
+import java.util.logging.Logger;
+
+import org.openqa.selenium.Platform;
+import org.openqa.selenium.ie.InternetExplorerDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.testbench.parallel.DefaultBrowserFactory;
+import com.vaadin.testbench.parallel.TestBenchBrowserFactory;
+
+public class VaadinBrowserFactory extends DefaultBrowserFactory {
+
+ TestBenchBrowserFactory delegate = null;
+
+ @Override
+ public DesiredCapabilities create(Browser browser) {
+ String browserFactoryClass = System.getProperty("browser.factory");
+ if (browserFactoryClass != null
+ && !browserFactoryClass.trim().isEmpty()) {
+ if (delegate == null) {
+ getLogger()
+ .info("Using browser factory " + browserFactoryClass);
+ try {
+ delegate = (TestBenchBrowserFactory) getClass()
+ .getClassLoader().loadClass(browserFactoryClass)
+ .newInstance();
+ } catch (Exception e) {
+ getLogger().warning(
+ "Failed to instantiate browser factory "
+ + browserFactoryClass);
+ throw new RuntimeException(e);
+ }
+ }
+ return delegate.create(browser);
+ }
+
+ return doCreate(browser);
+ }
+
+ public DesiredCapabilities doCreate(Browser browser) {
+ switch (browser) {
+ case IE8:
+ return createIE(browser, "8");
+ case IE9:
+ return createIE(browser, "9");
+ case IE10:
+ return createIE(browser, "10");
+ case IE11:
+ return createIE(browser, "11");
+ case PHANTOMJS:
+ return create(browser, "1", Platform.LINUX);
+ case CHROME:
+ return create(browser, "40", Platform.VISTA);
+ case FIREFOX:
+ default:
+ return create(browser, "24", Platform.XP);
+ }
+ }
+
+ private DesiredCapabilities createIE(Browser browser, String version) {
+ DesiredCapabilities capabilities = create(browser, version,
+ Platform.WINDOWS);
+ capabilities.setCapability(
+ InternetExplorerDriver.IE_ENSURE_CLEAN_SESSION, true);
+ return capabilities;
+ }
+
+ @Override
+ public DesiredCapabilities create(Browser browser, String version) {
+ DesiredCapabilities capabilities = create(browser);
+ capabilities.setVersion(version);
+ return capabilities;
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(VaadinBrowserFactory.class.getName());
+ }
+}