/* * Copyright 2000-2016 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.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; 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<>(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 >= {@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 > {@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 <= {@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 < {@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 */ protected void sleep(int timeoutMillis) { while (timeoutMillis > 0) { int d = Math.min(BROWSER_TIMEOUT_IN_MS, timeoutMillis); try { Thread.sleep(d); } catch (InterruptedException e) { throw new RuntimeException(e); } 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) { driver.findElement(By.id(id)).click(); } 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); try (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<>(); 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 if (BrowserUtil.isFirefox(getDesiredCapabilities())) { // Firefox workaround getCommandExecutor().executeScript("arguments[0].click()", cb); } else { cb.click(); } } protected void clickElement(WebElement element) { if (BrowserUtil.isFirefox(getDesiredCapabilities())) { // Workaround for Selenium/TB and Firefox 45 issue ((TestBenchElement) (element)).clickHiddenElement(); } else { element.click(); } } protected void contextClickElement(WebElement element) { if (BrowserUtil.isFirefox(getDesiredCapabilities())) { // Workaround for Selenium/TB and Firefox 45 issue getCommandExecutor().executeScript( "var ev = document.createEvent('HTMLEvents'); ev.initEvent('contextmenu', true, false); arguments[0].dispatchEvent(ev);", element); } else { new Actions(getDriver()).contextClick(element).perform(); } } protected boolean isLoadingIndicatorVisible() { WebElement loadingIndicator = findElement( By.className("v-loading-indicator")); return loadingIndicator.isDisplayed(); } protected void waitUntilLoadingIndicatorVisible() { waitUntil(input -> isLoadingIndicatorVisible()); } protected void waitUntilLoadingIndicatorNotVisible() { waitUntil(input -> !isLoadingIndicatorVisible()); } /** * 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 = "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 = "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); } }