summaryrefslogtreecommitdiffstats
path: root/uitest/src/com/vaadin/tests/tb3
diff options
context:
space:
mode:
Diffstat (limited to 'uitest/src/com/vaadin/tests/tb3')
-rw-r--r--uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java793
-rw-r--r--uitest/src/com/vaadin/tests/tb3/AllTB3Tests.java42
-rw-r--r--uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java69
-rw-r--r--uitest/src/com/vaadin/tests/tb3/ParallelScheduler.java69
-rw-r--r--uitest/src/com/vaadin/tests/tb3/PrivateTB3Configuration.java135
-rw-r--r--uitest/src/com/vaadin/tests/tb3/ScreenshotTB3Test.java400
-rw-r--r--uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java35
-rw-r--r--uitest/src/com/vaadin/tests/tb3/SimpleMultiBrowserTest.java50
-rw-r--r--uitest/src/com/vaadin/tests/tb3/TB3Runner.java154
-rw-r--r--uitest/src/com/vaadin/tests/tb3/TB3TestSuite.java238
-rw-r--r--uitest/src/com/vaadin/tests/tb3/WebsocketTest.java60
11 files changed, 2045 insertions, 0 deletions
diff --git a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java
new file mode 100644
index 0000000000..1967891a7a
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright 2000-2013 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.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.logging.Logger;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Platform;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.BrowserType;
+import org.openqa.selenium.remote.DesiredCapabilities;
+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.vaadin.server.LegacyApplication;
+import com.vaadin.server.UIProvider;
+import com.vaadin.testbench.TestBench;
+import com.vaadin.testbench.TestBenchTestCase;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.UI;
+
+/**
+ * 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(value = TB3Runner.class)
+public abstract class AbstractTB3Test extends TestBenchTestCase {
+ /**
+ * 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;
+
+ private DesiredCapabilities desiredCapabilities;
+
+ private boolean debug = false;
+
+ private boolean push = false;
+ {
+ // Default browser to run on unless setDesiredCapabilities is called
+ desiredCapabilities = BrowserUtil.firefox(24);
+ }
+
+ /**
+ * 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
+ */
+ @Before
+ public void setup() throws Exception {
+ setupDriver();
+ }
+
+ /**
+ * Creates and configure the web driver to be used for the test. By default
+ * creates a remote web driver which connects to {@link #getHubURL()} and
+ * selects a browser based on {@link #getDesiredCapabilities()}.
+ *
+ * This method MUST call {@link #setDriver(WebDriver)} with the newly
+ * generated driver.
+ *
+ * @throws Exception
+ * If something goes wrong
+ */
+ protected void setupDriver() throws Exception {
+ DesiredCapabilities capabilities = getDesiredCapabilities();
+
+ WebDriver dr = TestBench.createDriver(new RemoteWebDriver(new URL(
+ getHubURL()), capabilities));
+ setDriver(dr);
+
+ int w = SCREENSHOT_WIDTH;
+ int h = SCREENSHOT_HEIGHT;
+
+ if (BrowserUtil.isIE8(capabilities)) {
+ // 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...
+ }
+
+ }
+
+ /**
+ * Opens the given test (defined by {@link #getTestUrl(boolean, boolean)},
+ * optionally with debug window and/or push
+ *
+ * @param debug
+ * @param push
+ */
+ protected void openTestURL() {
+ driver.get(getTestUrl());
+ }
+
+ /**
+ * Returns the full URL to be used for the test
+ *
+ * @return the full URL for the test
+ */
+ protected String getTestUrl() {
+ String baseUrl = getBaseURL();
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+ }
+
+ return baseUrl + getDeploymentPath();
+ }
+
+ /**
+ *
+ * @return the location (URL) of the TB hub
+ */
+ protected String getHubURL() {
+ return "http://" + getHubHostname() + ":4444/wd/hub";
+ }
+
+ /**
+ * Used for building the hub URL to use for the test
+ *
+ * @return the host name of the TestBench hub
+ */
+ protected abstract String getHubHostname();
+
+ /**
+ * 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 String 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
+ */
+ public Collection<DesiredCapabilities> getBrowsersToTest() {
+ return Collections.singleton(BrowserUtil.firefox(17));
+
+ }
+
+ /**
+ * Used to determine which capabilities should be used when setting up a
+ * {@link WebDriver} for this test. Typically set by a test runner or left
+ * at its default (Firefox 24). If you want to run a test on a single
+ * browser other than Firefox 24 you can override this method.
+ *
+ * @return the requested browser capabilities
+ */
+ protected DesiredCapabilities getDesiredCapabilities() {
+ return desiredCapabilities;
+ }
+
+ /**
+ * Sets the requested browser capabilities (typically browser name and
+ * version)
+ *
+ * @param desiredCapabilities
+ */
+ public void setDesiredCapabilities(DesiredCapabilities desiredCapabilities) {
+ this.desiredCapabilities = desiredCapabilities;
+ }
+
+ /**
+ * Shuts down the driver after the test has been completed
+ *
+ * @throws Exception
+ */
+ @After
+ public void tearDown() throws Exception {
+ if (driver != null) {
+ driver.quit();
+ }
+ driver = null;
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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(vaadinLocatorById(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 a short while 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 void waitUntil(ExpectedCondition<Boolean> condition) {
+ new WebDriverWait(driver, 10).until(condition);
+ }
+
+ /**
+ * Waits a short while 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 void waitUntilNot(ExpectedCondition<Boolean> condition) {
+ new WebDriverWait(driver, 10).until(ExpectedConditions.not(condition));
+ }
+
+ /**
+ * For tests extending {@link 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 {@link 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) {
+ }
+ Class<?> enclosingClass = getClass().getEnclosingClass();
+ if (enclosingClass != null) {
+ if (UI.class.isAssignableFrom(enclosingClass)) {
+ Logger.getLogger(getClass().getName())
+ .severe("Test is an static inner class to the UI. This will no longer be supported in the future. The test should be named UIClassTest and reside in the same package as the UI");
+ return enclosingClass;
+ }
+ }
+ 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
+ */
+ private String getDeploymentPath(Class<?> uiClass) {
+ String runPath = "/run";
+ if (isPush()) {
+ runPath = "/run-push";
+ }
+
+ if (UI.class.isAssignableFrom(uiClass)) {
+ return runPath + "/" + uiClass.getCanonicalName()
+ + (isDebug() ? "?debug" : "");
+ } else if (LegacyApplication.class.isAssignableFrom(uiClass)) {
+ return runPath + "/" + uiClass.getCanonicalName()
+ + "?restartApplication" + (isDebug() ? "&debug" : "");
+ } 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]", "");
+ }
+
+ /**
+ * Helper method for sleeping X ms in a test. Catches and ignores
+ * InterruptedExceptions
+ *
+ * @param timeoutMillis
+ * Number of ms to wait
+ */
+ protected void sleep(int timeoutMillis) {
+ try {
+ Thread.sleep(timeoutMillis);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Provides helper method for selecting the browser to run on
+ *
+ * @author Vaadin Ltd
+ */
+ public static class BrowserUtil {
+ /**
+ * Gets the capabilities for Safari of the given version
+ *
+ * @param version
+ * the major version
+ * @return an object describing the capabilities required for running a
+ * test on the given Safari version
+ */
+ public static DesiredCapabilities safari(int version) {
+ DesiredCapabilities c = DesiredCapabilities.safari();
+ c.setVersion("" + version);
+ return c;
+ }
+
+ /**
+ * Gets the capabilities for Chrome of the given version
+ *
+ * @param version
+ * the major version
+ * @return an object describing the capabilities required for running a
+ * test on the given Chrome version
+ */
+ public static DesiredCapabilities chrome(int version) {
+ DesiredCapabilities c = DesiredCapabilities.chrome();
+ c.setVersion("" + version);
+ c.setPlatform(Platform.XP);
+ return c;
+ }
+
+ /**
+ * Gets the capabilities for Opera of the given version
+ *
+ * @param version
+ * the major version
+ * @return an object describing the capabilities required for running a
+ * test on the given Opera version
+ */
+ public static DesiredCapabilities opera(int version) {
+ DesiredCapabilities c = DesiredCapabilities.opera();
+ c.setVersion("" + version);
+ c.setPlatform(Platform.XP);
+ return c;
+ }
+
+ /**
+ * Gets the capabilities for Firefox of the given version
+ *
+ * @param version
+ * the major version
+ * @return an object describing the capabilities required for running a
+ * test on the given Firefox version
+ */
+ public static DesiredCapabilities firefox(int version) {
+ DesiredCapabilities c = DesiredCapabilities.firefox();
+ c.setVersion("" + version);
+ c.setPlatform(Platform.XP);
+ return c;
+ }
+
+ /**
+ * Gets the capabilities for Internet Explorer of the given version
+ *
+ * @param version
+ * the major version
+ * @return an object describing the capabilities required for running a
+ * test on the given Internet Explorer version
+ */
+ public static DesiredCapabilities ie(int version) {
+ DesiredCapabilities c = DesiredCapabilities.internetExplorer();
+ c.setVersion("" + version);
+ return c;
+ }
+
+ /**
+ * Checks if the given capabilities refer to Internet Explorer 8
+ *
+ * @param capabilities
+ * @return true if the capabilities refer to IE8, false otherwise
+ */
+ public static boolean isIE8(DesiredCapabilities capabilities) {
+ return BrowserType.IE.equals(capabilities.getBrowserName())
+ && "8".equals(capabilities.getVersion());
+ }
+
+ /**
+ * Returns a human readable identifier of the given browser. Used for
+ * test naming and screenshots
+ *
+ * @param capabilities
+ * @return a human readable string describing the capabilities
+ */
+ public static String getBrowserIdentifier(
+ DesiredCapabilities capabilities) {
+ String browserName = capabilities.getBrowserName();
+
+ if (BrowserType.IE.equals(browserName)) {
+ return "InternetExplorer";
+ } else if (BrowserType.FIREFOX.equals(browserName)) {
+ return "Firefox";
+ } else if (BrowserType.CHROME.equals(browserName)) {
+ return "Chrome";
+ } else if (BrowserType.SAFARI.equals(browserName)) {
+ return "Safari";
+ } else if (BrowserType.OPERA.equals(browserName)) {
+ return "Opera";
+ }
+
+ return browserName;
+ }
+
+ /**
+ * Returns a human readable identifier of the platform described by the
+ * given capabilities. Used mainly for screenshots
+ *
+ * @param capabilities
+ * @return a human readable string describing the platform
+ */
+ public static String getPlatform(DesiredCapabilities capabilities) {
+ if (capabilities.getPlatform() == Platform.WIN8
+ || capabilities.getPlatform() == Platform.WINDOWS
+ || capabilities.getPlatform() == Platform.VISTA
+ || capabilities.getPlatform() == Platform.XP) {
+ return "Windows";
+ } else if (capabilities.getPlatform() == Platform.MAC) {
+ return "Mac";
+ }
+ return capabilities.getPlatform().toString();
+ }
+
+ /**
+ * Returns a string which uniquely (enough) identifies this browser.
+ * Used mainly in screenshot names.
+ *
+ * @param capabilities
+ *
+ * @return a unique string for each browser
+ */
+ public static String getUniqueIdentifier(
+ DesiredCapabilities capabilities) {
+ return getUniqueIdentifier(getPlatform(capabilities),
+ getBrowserIdentifier(capabilities),
+ capabilities.getVersion());
+ }
+
+ /**
+ * Returns a string which uniquely (enough) identifies this browser.
+ * Used mainly in screenshot names.
+ *
+ * @param capabilities
+ *
+ * @return a unique string for each browser
+ */
+ public static String getUniqueIdentifier(
+ DesiredCapabilities capabilities, String versionOverride) {
+ return getUniqueIdentifier(getPlatform(capabilities),
+ getBrowserIdentifier(capabilities), versionOverride);
+ }
+
+ private static String getUniqueIdentifier(String platform,
+ String browser, String version) {
+ return platform + "_" + browser + "_" + version;
+ }
+
+ }
+
+ /**
+ * 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
+
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/AllTB3Tests.java b/uitest/src/com/vaadin/tests/tb3/AllTB3Tests.java
new file mode 100644
index 0000000000..bd9027bec2
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/AllTB3Tests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2013 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.runner.RunWith;
+import org.junit.runners.model.InitializationError;
+
+import com.vaadin.tests.tb3.AllTB3Tests.AllTB3TestsSuite;
+
+/**
+ * Test consisting of all TB3 tests except integration tests (classes extending
+ * AbstractTB3Test, excludes package com.vaadin.test.integration).
+ *
+ * @author Vaadin Ltd
+ */
+@RunWith(AllTB3TestsSuite.class)
+public class AllTB3Tests {
+
+ public static class AllTB3TestsSuite extends TB3TestSuite {
+
+ public AllTB3TestsSuite(Class<?> klass) throws InitializationError {
+ super(klass, AbstractTB3Test.class, "com.vaadin.tests",
+ new String[] { "com.vaadin.tests.integration" });
+ }
+
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java b/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java
new file mode 100644
index 0000000000..3553954ec0
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2013 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.Collection;
+import java.util.List;
+
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+/**
+ * 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 {
+
+ static List<DesiredCapabilities> allBrowsers = new ArrayList<DesiredCapabilities>();
+ static {
+ allBrowsers.add(BrowserUtil.ie(8));
+ allBrowsers.add(BrowserUtil.ie(9));
+ allBrowsers.add(BrowserUtil.ie(10));
+ allBrowsers.add(BrowserUtil.ie(11));
+ allBrowsers.add(BrowserUtil.firefox(24));
+ // Uncomment once we have the capability to run on Safari 6
+ // allBrowsers.add(safari(6));
+ allBrowsers.add(BrowserUtil.chrome(29));
+ allBrowsers.add(BrowserUtil.opera(12));
+
+ }
+
+ /**
+ * @return all supported browsers which are actively tested
+ */
+ public static List<DesiredCapabilities> getAllBrowsers() {
+ return allBrowsers;
+ }
+
+ @Override
+ public Collection<DesiredCapabilities> getBrowsersToTest() {
+ return allBrowsers;
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/ParallelScheduler.java b/uitest/src/com/vaadin/tests/tb3/ParallelScheduler.java
new file mode 100644
index 0000000000..912d7d010e
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/ParallelScheduler.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2013 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/src/com/vaadin/tests/tb3/PrivateTB3Configuration.java b/uitest/src/com/vaadin/tests/tb3/PrivateTB3Configuration.java
new file mode 100644
index 0000000000..09615f0b2e
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/PrivateTB3Configuration.java
@@ -0,0 +1,135 @@
+/*
+ * 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.Enumeration;
+import java.util.Properties;
+
+/**
+ * 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
+ */
+public abstract class PrivateTB3Configuration extends ScreenshotTB3Test {
+ private static final String HOSTNAME_PROPERTY = "com.vaadin.testbench.deployment.hostname";
+ private static final String PORT_PROPERTY = "com.vaadin.testbench.deployment.port";
+ private final Properties properties = new Properties();
+
+ public PrivateTB3Configuration() {
+ File file = new File("work", "eclipse-run-selected-test.properties");
+ if (file.exists()) {
+ try {
+ properties.load(new FileInputStream(file));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private 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("com.vaadin.testbench.screenshot.directory");
+ if (screenshotDirectory == null) {
+ throw new RuntimeException(
+ "No screenshot directory defined. Use -Dcom.vaadin.testbench.screenshot.directory=<path>");
+ }
+ return screenshotDirectory;
+ }
+
+ @Override
+ protected String getHubHostname() {
+ return "tb3-hub.intra.itmill.com";
+ }
+
+ @Override
+ protected String getDeploymentHostname() {
+ String hostName = getProperty(HOSTNAME_PROPERTY);
+
+ if (hostName == null || "".equals(hostName)) {
+ hostName = findAutoHostname();
+ }
+
+ return hostName;
+ }
+
+ @Override
+ protected String getDeploymentPort() {
+ String port = getProperty(PORT_PROPERTY);
+
+ if (port == null || "".equals(port)) {
+ port = "8888";
+ }
+
+ 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 String findAutoHostname() {
+ try {
+ Enumeration<NetworkInterface> interfaces = NetworkInterface
+ .getNetworkInterfaces();
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface current = interfaces.nextElement();
+ if (!current.isUp() || current.isLoopback()
+ || current.isVirtual()) {
+ continue;
+ }
+ Enumeration<InetAddress> addresses = current.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ InetAddress current_addr = addresses.nextElement();
+ if (current_addr.isLoopbackAddress()) {
+ continue;
+ }
+ String hostAddress = current_addr.getHostAddress();
+ if (hostAddress.startsWith("192.168.")) {
+ return hostAddress;
+ }
+ }
+ }
+ } catch (SocketException e) {
+ throw new RuntimeException("Could not enumerate ");
+ }
+
+ throw new RuntimeException(
+ "No compatible (192.168.*) ip address found.");
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/ScreenshotTB3Test.java b/uitest/src/com/vaadin/tests/tb3/ScreenshotTB3Test.java
new file mode 100644
index 0000000000..cbdae1a6c1
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/ScreenshotTB3Test.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2000-2013 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.FileFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+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.OutputType;
+import org.openqa.selenium.TakesScreenshot;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.Parameters;
+import com.vaadin.testbench.commands.TestBenchCommands;
+
+/**
+ * Base class which provides functionality for tests which use the automatic
+ * screenshot comparison function.
+ *
+ * @author Vaadin Ltd
+ */
+public abstract class ScreenshotTB3Test extends AbstractTB3Test {
+
+ 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;
+ }
+
+ @Override
+ protected void failed(Throwable e, Description description) {
+
+ // Notify Teamcity of failed test
+ if (!System.getProperty("teamcity.version", "").equals("")) {
+ System.out.print("##teamcity[publishArtifacts '");
+ System.out.println(getScreenshotErrorBaseName()
+ + "* => screenshot-errors']");
+ }
+ }
+ };
+
+ /**
+ * Contains a list of screenshot identifiers for which
+ * {@link #compareScreen(String)} has failed during the test
+ */
+ private List<String> screenshotFailures = new ArrayList<String>();
+
+ /**
+ * Defines TestBench screen comparison parameters before each test run
+ */
+ @Before
+ public void setupScreenComparisonParameters() {
+ 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 {
+ if (identifier == null || identifier.isEmpty()) {
+ throw new IllegalArgumentException("Empty identifier not supported");
+ }
+
+ File mainReference = getScreenshotReferenceFile(identifier);
+
+ List<File> alternativeFiles = findReferenceAlternatives(mainReference);
+ List<File> failedReferenceAlternatives = new ArrayList<File>();
+
+ for (File file : alternativeFiles) {
+ if (testBench(driver).compareScreen(file)) {
+ break;
+ } else {
+ failedReferenceAlternatives.add(file);
+ }
+ }
+
+ File referenceToKeep = null;
+ if (failedReferenceAlternatives.size() != alternativeFiles.size()) {
+ // Matched one comparison but not all, remove all error images +
+ // HTML files
+ } 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 : failedReferenceAlternatives) {
+ File failurePng = getErrorFileFromReference(failedAlternative);
+ if (failedAlternative != referenceToKeep) {
+ // Delete png + HTML
+ String htmlFileName = failurePng.getName().replace(".png",
+ ".html");
+ File failureHtml = new File(failurePng.getParentFile(),
+ htmlFileName);
+
+ failurePng.delete();
+ failureHtml.delete();
+ }
+ }
+ }
+
+ /**
+ *
+ * @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) {
+ return new File(referenceFile.getAbsolutePath().replace(
+ getScreenshotReferenceDirectory(),
+ getScreenshotErrorDirectory()));
+ }
+
+ /**
+ * 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> findReferenceAlternatives(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();
+
+ if (browserVersion.matches("\\d+")) {
+ for (int version = Integer.parseInt(browserVersion); 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 directory where reference images are stored (the 'reference'
+ * folder inside the screenshot directory)
+ */
+ private String getScreenshotReferenceDirectory() {
+ return getScreenshotDirectory() + "/reference";
+ }
+
+ /**
+ * @return the directory where comparison error images should be created
+ * (the 'errors' folder inside the screenshot directory)
+ */
+ private String getScreenshotErrorDirectory() {
+ return getScreenshotDirectory() + "/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.isEmpty()) {
+ throw new IOException(
+ "The following screenshots did not match the reference: "
+ + screenshotFailures.toString());
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.tests.tb3.AbstractTB3Test#onUncaughtException(java.lang.Throwable
+ * )
+ */
+ @Override
+ public void onUncaughtException(Throwable cause) {
+ super.onUncaughtException(cause);
+ // Grab a "failure" screenshot and store in the errors folder for later
+ // analysis
+ try {
+ TestBenchCommands testBench = testBench();
+ if (testBench != null) {
+ testBench.disableWaitForVaadin();
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ try {
+ if (driver != null) {
+ BufferedImage screenshotImage = ImageIO
+ .read(new ByteArrayInputStream(
+ ((TakesScreenshot) driver)
+ .getScreenshotAs(OutputType.BYTES)));
+ ImageIO.write(screenshotImage, "png", new File(
+ getScreenshotFailureName()));
+ }
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+
+ }
+
+ /**
+ * @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 getScreenshotErrorBaseName() + "-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) {
+ String uniqueBrowserIdentifier;
+ if (versionOverride == null) {
+ uniqueBrowserIdentifier = BrowserUtil
+ .getUniqueIdentifier(getDesiredCapabilities());
+ } else {
+ uniqueBrowserIdentifier = BrowserUtil.getUniqueIdentifier(
+ getDesiredCapabilities(), "" + versionOverride);
+ }
+
+ // WindowMaximizeRestoreTest_Windows_InternetExplorer_8_window-1-moved-maximized-restored.png
+ return getScreenshotReferenceDirectory() + "/"
+ + getScreenshotBaseName() + "_" + uniqueBrowserIdentifier + "_"
+ + identifier + ".png";
+ }
+
+ /**
+ * 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()
+ .replace("\\", "/");
+ File[] files = errorDirectory.listFiles(new FileFilter() {
+
+ @Override
+ public boolean accept(File pathname) {
+ String thisFile = pathname.getAbsolutePath().replace("\\", "/");
+ if (thisFile.startsWith(errorBase)) {
+ return true;
+ }
+ return false;
+ }
+ });
+ for (File f : files) {
+ f.delete();
+ }
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java b/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java
new file mode 100644
index 0000000000..c511b99e6e
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2013 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.runner.RunWith;
+import org.junit.runners.model.InitializationError;
+
+import com.vaadin.tests.integration.AbstractServletIntegrationTest;
+import com.vaadin.tests.tb3.ServletIntegrationTests.ServletIntegrationTestSuite;
+
+@RunWith(ServletIntegrationTestSuite.class)
+public class ServletIntegrationTests {
+
+ public static class ServletIntegrationTestSuite extends TB3TestSuite {
+ public ServletIntegrationTestSuite(Class<?> klass)
+ throws InitializationError {
+ super(klass, AbstractServletIntegrationTest.class,
+ "com.vaadin.tests.integration", new String[] {});
+ }
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/SimpleMultiBrowserTest.java b/uitest/src/com/vaadin/tests/tb3/SimpleMultiBrowserTest.java
new file mode 100644
index 0000000000..aeeed4198c
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/SimpleMultiBrowserTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2013 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.Test;
+
+/**
+ * A simple version of {@link MultiBrowserTest} which allows only one test
+ * method ({@link #test()}). Uses only the enclosing class name as test
+ * identifier (i.e. excludes "-test").
+ *
+ * This class is only provided as a helper for converting existing TB2 tests
+ * without renaming all screenshots. All new TB3+ tests should extend
+ * {@link MultiBrowserTest} directly instead of this.
+ *
+ * @author Vaadin Ltd
+ */
+@Deprecated
+public abstract class SimpleMultiBrowserTest extends MultiBrowserTest {
+
+ @Test
+ public abstract void test() throws Exception;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.tests.tb3.ScreenshotTB3Test#getScreenshotBaseName()
+ */
+ @Override
+ public String getScreenshotBaseName() {
+ return super.getScreenshotBaseName().replaceFirst("-test$", "");
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/TB3Runner.java b/uitest/src/com/vaadin/tests/tb3/TB3Runner.java
new file mode 100644
index 0000000000..b612b17caa
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/TB3Runner.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2000-2013 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.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.junit.Test;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.Parameterized;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.tests.tb3.AbstractTB3Test.BrowserUtil;
+
+/**
+ * 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 BlockJUnit4ClassRunner {
+
+ /**
+ * This is the total limit of actual JUnit test instances run in parallel
+ */
+ private static final int MAX_CONCURRENT_TESTS = 50;
+
+ /**
+ * This is static so it is shared by all tests running concurrently on the
+ * same machine and thus can limit the number of threads in use.
+ */
+ private static final ExecutorService service = Executors
+ .newFixedThreadPool(MAX_CONCURRENT_TESTS);
+
+ public TB3Runner(Class<?> klass) throws InitializationError {
+ super(klass);
+ setScheduler(new ParallelScheduler(service));
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ List<FrameworkMethod> tests = new LinkedList<FrameworkMethod>();
+
+ if (!AbstractTB3Test.class.isAssignableFrom(getTestClass()
+ .getJavaClass())) {
+ throw new RuntimeException(getClass().getName() + " only supports "
+ + AbstractTB3Test.class.getName());
+ }
+
+ try {
+ AbstractTB3Test testClassInstance = (AbstractTB3Test) getTestClass()
+ .getOnlyConstructor().newInstance();
+ for (DesiredCapabilities capabilities : testClassInstance
+ .getBrowsersToTest()) {
+
+ // Find any methods marked with @Test.
+ for (FrameworkMethod m : getTestClass().getAnnotatedMethods(
+ Test.class)) {
+ tests.add(new TB3Method(m.getMethod(), capabilities));
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Error retrieving browsers to run on", e);
+ }
+
+ return tests;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.junit.runners.BlockJUnit4ClassRunner#withBefores(org.junit.runners
+ * .model.FrameworkMethod, java.lang.Object,
+ * org.junit.runners.model.Statement)
+ */
+ @Override
+ protected Statement withBefores(final FrameworkMethod method,
+ final Object target, Statement statement) {
+ if (!(method instanceof TB3Method)) {
+ throw new RuntimeException("Unexpected method type "
+ + method.getClass().getName() + ", expected TB3Method");
+ }
+ final TB3Method tb3method = (TB3Method) method;
+
+ // setDesiredCapabilities before running the real @Befores (which use
+ // capabilities)
+
+ final Statement realBefores = super.withBefores(method, target,
+ statement);
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ ((AbstractTB3Test) target)
+ .setDesiredCapabilities(tb3method.capabilities);
+ try {
+ realBefores.evaluate();
+ } catch (Throwable t) {
+ // Give the test a chance to e.g. produce an error
+ // screenshot before failing the test by re-throwing the
+ // exception
+ ((AbstractTB3Test) target).onUncaughtException(t);
+ throw t;
+ }
+ }
+ };
+ }
+
+ private static class TB3Method extends FrameworkMethod {
+ private DesiredCapabilities capabilities;
+
+ public TB3Method(Method method, DesiredCapabilities capabilities) {
+ super(method);
+ this.capabilities = capabilities;
+ }
+
+ @Override
+ public Object invokeExplosively(final Object target, Object... params)
+ throws Throwable {
+ // Executes the test method with the supplied parameters
+ return super.invokeExplosively(target);
+ }
+
+ @Override
+ public String getName() {
+ return String.format("%s[%s]", getMethod().getName(),
+ BrowserUtil.getUniqueIdentifier(capabilities));
+ }
+
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/tb3/TB3TestSuite.java b/uitest/src/com/vaadin/tests/tb3/TB3TestSuite.java
new file mode 100644
index 0000000000..e1c8edfd60
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/TB3TestSuite.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2000-2013 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.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.jar.JarEntry;
+
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+
+/**
+ * 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 Suite {
+
+ /**
+ * This only restricts the number of test suites running concurrently. The
+ * number of tests to run concurrently are configured in {@link TB3Runner}.
+ */
+ private static final int MAX_CONCURRENT_TEST_SUITES = 20;
+
+ /**
+ * This is static so it is shared by all test suites running concurrently on
+ * the same machine and thus can limit the number of threads in use.
+ */
+ private final ExecutorService service = Executors
+ .newFixedThreadPool(MAX_CONCURRENT_TEST_SUITES);
+
+ public TB3TestSuite(Class<?> klass,
+ Class<? extends AbstractTB3Test> baseClass, String basePackage,
+ String[] ignorePackages) throws InitializationError {
+ super(klass, findTests(baseClass, basePackage, ignorePackages));
+ setScheduler(new ParallelScheduler(service));
+ }
+
+ /**
+ * 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
+ */
+ private static 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
+ */
+ private static <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 static <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 static <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")
+ private static <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 (!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
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/uitest/src/com/vaadin/tests/tb3/WebsocketTest.java b/uitest/src/com/vaadin/tests/tb3/WebsocketTest.java
new file mode 100644
index 0000000000..5c6ea329f5
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/tb3/WebsocketTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2013 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.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+/**
+ * A {@link MultiBrowserTest} which restricts the tests to the browsers which
+ * support websocket
+ *
+ * @author Vaadin Ltd
+ */
+public abstract class WebsocketTest extends PrivateTB3Configuration {
+ private static List<DesiredCapabilities> websocketBrowsers = new ArrayList<DesiredCapabilities>();
+ static {
+ websocketBrowsers.addAll(MultiBrowserTest.getAllBrowsers());
+ websocketBrowsers.remove(BrowserUtil.ie(8));
+ websocketBrowsers.remove(BrowserUtil.ie(9));
+ }
+
+ /**
+ * @return All supported browsers which are actively tested and support
+ * websockets
+ */
+ public static Collection<DesiredCapabilities> getWebsocketBrowsers() {
+ return Collections.unmodifiableCollection(websocketBrowsers);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.tests.tb3.AbstractTB3Test#getBrowserToRunOn()
+ */
+ @Override
+ public Collection<DesiredCapabilities> getBrowsersToTest() {
+ return getWebsocketBrowsers();
+ }
+}