You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractTB3Test.java 43KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  1. package com.vaadin.tests.tb3;
  2. import static org.junit.Assert.assertEquals;
  3. import static org.junit.Assert.assertFalse;
  4. import static org.junit.Assert.assertTrue;
  5. import static org.junit.Assert.fail;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.StringWriter;
  9. import java.lang.reflect.Field;
  10. import java.net.URL;
  11. import java.util.ArrayList;
  12. import java.util.Arrays;
  13. import java.util.Collections;
  14. import java.util.HashSet;
  15. import java.util.List;
  16. import java.util.Set;
  17. import java.util.TimeZone;
  18. import java.util.logging.Level;
  19. import org.apache.commons.io.IOUtils;
  20. import org.apache.commons.lang3.StringUtils;
  21. import org.apache.http.HttpHost;
  22. import org.apache.http.HttpResponse;
  23. import org.apache.http.impl.client.DefaultHttpClient;
  24. import org.apache.http.message.BasicHttpEntityEnclosingRequest;
  25. import org.junit.Assume;
  26. import org.junit.Rule;
  27. import org.junit.rules.TestName;
  28. import org.junit.runner.Description;
  29. import org.junit.runner.RunWith;
  30. import org.openqa.selenium.By;
  31. import org.openqa.selenium.Dimension;
  32. import org.openqa.selenium.JavascriptExecutor;
  33. import org.openqa.selenium.NoSuchElementException;
  34. import org.openqa.selenium.Point;
  35. import org.openqa.selenium.WebDriver;
  36. import org.openqa.selenium.WebElement;
  37. import org.openqa.selenium.interactions.Actions;
  38. import org.openqa.selenium.interactions.HasInputDevices;
  39. import org.openqa.selenium.interactions.Keyboard;
  40. import org.openqa.selenium.interactions.Mouse;
  41. import org.openqa.selenium.internal.WrapsElement;
  42. import org.openqa.selenium.remote.DesiredCapabilities;
  43. import org.openqa.selenium.remote.HttpCommandExecutor;
  44. import org.openqa.selenium.remote.RemoteWebDriver;
  45. import org.openqa.selenium.support.ui.ExpectedCondition;
  46. import org.openqa.selenium.support.ui.ExpectedConditions;
  47. import com.vaadin.server.LegacyApplication;
  48. import com.vaadin.server.UIProvider;
  49. import com.vaadin.testbench.ScreenshotOnFailureRule;
  50. import com.vaadin.testbench.TestBenchDriverProxy;
  51. import com.vaadin.testbench.TestBenchElement;
  52. import com.vaadin.testbench.annotations.BrowserConfiguration;
  53. import com.vaadin.testbench.elements.CheckBoxElement;
  54. import com.vaadin.testbench.elements.LabelElement;
  55. import com.vaadin.testbench.elements.TableElement;
  56. import com.vaadin.testbench.elements.VerticalLayoutElement;
  57. import com.vaadin.testbench.parallel.Browser;
  58. import com.vaadin.testbench.parallel.BrowserUtil;
  59. import com.vaadin.testbench.parallel.ParallelTest;
  60. import com.vaadin.ui.UI;
  61. import elemental.json.JsonObject;
  62. import elemental.json.impl.JsonUtil;
  63. /**
  64. * Base class for TestBench 3+ tests. All TB3+ tests in the project should
  65. * extend this class.
  66. *
  67. * Provides:
  68. * <ul>
  69. * <li>Helpers for browser selection</li>
  70. * <li>Hub connection setup and teardown</li>
  71. * <li>Automatic generation of URL for a given test on the development server
  72. * using {@link #getUIClass()} or by automatically finding an enclosing UI class
  73. * and based on requested features, e.g. {@link #isDebug()},
  74. * {@link #isPush()}</li>
  75. * <li>Generic helpers for creating TB3+ tests</li>
  76. * </ul>
  77. *
  78. * @author Vaadin Ltd
  79. */
  80. @RunWith(TB3Runner.class)
  81. public abstract class AbstractTB3Test extends ParallelTest {
  82. @Rule
  83. public TestName testName = new TestName();
  84. {
  85. // Override default screenshotOnFailureRule to close application
  86. screenshotOnFailure = new ScreenshotOnFailureRule(this, true) {
  87. @Override
  88. protected void finished(Description description) {
  89. closeApplication();
  90. super.finished(description);
  91. }
  92. };
  93. }
  94. /**
  95. * Height of the screenshots we want to capture
  96. */
  97. private static final int SCREENSHOT_HEIGHT = 850;
  98. /**
  99. * Width of the screenshots we want to capture
  100. */
  101. private static final int SCREENSHOT_WIDTH = 1500;
  102. /**
  103. * Timeout used by the TB grid
  104. */
  105. private static final int BROWSER_TIMEOUT_IN_MS = 30 * 1000;
  106. private boolean debug = false;
  107. private boolean push = false;
  108. static {
  109. com.vaadin.testbench.Parameters
  110. .setScreenshotComparisonCursorDetection(true);
  111. }
  112. /**
  113. * Connect to the hub using a remote web driver, set the canvas size and
  114. * opens the initial URL as specified by {@link #getTestUrl()}
  115. *
  116. * @throws Exception
  117. */
  118. @Override
  119. public void setup() throws Exception {
  120. super.setup();
  121. int w = SCREENSHOT_WIDTH;
  122. int h = SCREENSHOT_HEIGHT;
  123. try {
  124. testBench().resizeViewPortTo(w, h);
  125. } catch (UnsupportedOperationException e) {
  126. // Opera does not support this...
  127. }
  128. }
  129. /**
  130. * Method for closing the tested application.
  131. */
  132. protected void closeApplication() {
  133. if (getDriver() != null) {
  134. try {
  135. openTestURL("closeApplication");
  136. } catch (Exception e) {
  137. e.printStackTrace();
  138. }
  139. }
  140. }
  141. protected WebElement getTooltipErrorElement() {
  142. WebElement tooltip = getDriver()
  143. .findElement(com.vaadin.testbench.By.className("v-tooltip"));
  144. return tooltip.findElement(By.className("v-errormessage"));
  145. }
  146. protected WebElement getTooltipElement() {
  147. return getDriver().findElement(
  148. com.vaadin.testbench.By.className("v-tooltip-text"));
  149. }
  150. private boolean hasDebugMessage(String message) {
  151. return getDebugMessage(message) != null;
  152. }
  153. private WebElement getDebugMessage(String message) {
  154. return driver.findElement(By.xpath(String.format(
  155. "//span[@class='v-debugwindow-message' and text()='%s']",
  156. message)));
  157. }
  158. protected void minimizeDebugWindow() {
  159. if (findElement(By.className("v-debugwindow-tabs")).isDisplayed()) {
  160. findElements(By.className("v-debugwindow-button")).stream()
  161. .filter(e -> e.getAttribute("title").equals("Minimize"))
  162. .findFirst().ifPresent(WebElement::click);
  163. }
  164. }
  165. protected void showDebugWindow() {
  166. if (!findElement(By.className("v-debugwindow-tabs")).isDisplayed()) {
  167. findElements(By.className("v-debugwindow-button")).stream()
  168. .filter(e -> e.getAttribute("title").equals("Minimize"))
  169. .findFirst().ifPresent(WebElement::click);
  170. }
  171. }
  172. protected void waitForDebugMessage(final String expectedMessage) {
  173. waitForDebugMessage(expectedMessage, 30);
  174. }
  175. protected void waitForDebugMessage(final String expectedMessage,
  176. int timeout) {
  177. waitUntil(input -> hasDebugMessage(expectedMessage), timeout);
  178. }
  179. protected void clearDebugMessages() {
  180. driver.findElement(By.xpath(
  181. "//button[@class='v-debugwindow-button' and @title='Clear log']"))
  182. .click();
  183. }
  184. protected void waitUntilRowIsVisible(final TableElement table,
  185. final int row) {
  186. waitUntil(input -> {
  187. try {
  188. return table.getCell(row, 0) != null;
  189. } catch (NoSuchElementException e) {
  190. return false;
  191. }
  192. });
  193. }
  194. protected void scrollTable(TableElement table, int rows, int rowToWait) {
  195. testBenchElement(table.findElement(By.className("v-scrollable")))
  196. .scroll(rows * 30);
  197. waitUntilRowIsVisible(table, rowToWait);
  198. }
  199. /**
  200. * Opens the given test (defined by {@link #getTestUrl()}, optionally with
  201. * debug window and/or push (depending on {@link #isDebug()} and
  202. * {@link #isPush()}.
  203. */
  204. protected void openTestURL() {
  205. openTestURL(new String[0]);
  206. }
  207. /**
  208. * Opens the given test (defined by {@link #getTestUrl()}, optionally with
  209. * debug window and/or push (depending on {@link #isDebug()} and
  210. * {@link #isPush()}.
  211. */
  212. protected void openTestURL(String... parameters) {
  213. openTestURL(getUIClass(), parameters);
  214. }
  215. /**
  216. * Opens the given test (defined by {@link #getTestUrl()}, optionally with
  217. * debug window and/or push (depending on {@link #isDebug()} and
  218. * {@link #isPush()}.
  219. */
  220. protected void openTestURL(Class<?> uiClass, String... parameters) {
  221. openTestURL(uiClass, new HashSet<>(Arrays.asList(parameters)));
  222. }
  223. private void openTestURL(Class<?> uiClass, Set<String> parameters) {
  224. String url = getTestURL(uiClass);
  225. if (isDebug()) {
  226. parameters.add("debug");
  227. }
  228. if (LegacyApplication.class.isAssignableFrom(uiClass)) {
  229. parameters.add("restartApplication");
  230. }
  231. if (!parameters.isEmpty()) {
  232. url += "?" + StringUtils.join(parameters, "&");
  233. }
  234. driver.get(url);
  235. }
  236. /**
  237. * Returns the full URL to be used for the test
  238. *
  239. * @return the full URL for the test
  240. */
  241. protected String getTestUrl() {
  242. return StringUtils.strip(getBaseURL(), "/") + getDeploymentPath();
  243. }
  244. /**
  245. * Returns the full URL to be used for the test for the provided UI class.
  246. *
  247. * @return the full URL for the test
  248. */
  249. protected String getTestURL(Class<?> uiClass) {
  250. return StringUtils.strip(getBaseURL(), "/")
  251. + getDeploymentPath(uiClass);
  252. }
  253. /**
  254. * Used to determine what URL to initially open for the test
  255. *
  256. * @return the host name of development server
  257. */
  258. protected abstract String getDeploymentHostname();
  259. /**
  260. * Used to determine what port the test is running on
  261. *
  262. * @return The port the test is running on, by default 8888
  263. */
  264. protected abstract int getDeploymentPort();
  265. /**
  266. * Produces a collection of browsers to run the test on. This method is
  267. * executed by the test runner when determining how many test methods to
  268. * invoke and with what parameters. For each returned value a test method is
  269. * ran and before running that,
  270. * {@link #setDesiredCapabilities(DesiredCapabilities)} is invoked with the
  271. * value returned by this method.
  272. *
  273. * This method is not static to allow overriding it in sub classes. By
  274. * default runs the test only on Firefox
  275. *
  276. * @return The browsers to run the test on
  277. */
  278. @BrowserConfiguration
  279. public List<DesiredCapabilities> getBrowsersToTest() {
  280. return Collections
  281. .singletonList(Browser.FIREFOX.getDesiredCapabilities());
  282. }
  283. /**
  284. * Finds an element based on the part of a TB2 style locator following the
  285. * :: (e.g. vaadin=runLabelModes::PID_Scheckboxaction-Enabled/domChild[0] ->
  286. * PID_Scheckboxaction-Enabled/domChild[0]).
  287. *
  288. * @param vaadinLocator
  289. * The part following :: of the vaadin locator string
  290. * @return
  291. */
  292. protected WebElement vaadinElement(String vaadinLocator) {
  293. return driver.findElement(vaadinLocator(vaadinLocator));
  294. }
  295. /**
  296. * Uses JavaScript to determine the currently focused element.
  297. *
  298. * @return Focused element or null
  299. */
  300. protected WebElement getFocusedElement() {
  301. Object focusedElement = executeScript("return document.activeElement");
  302. if (null != focusedElement) {
  303. return (WebElement) focusedElement;
  304. } else {
  305. return null;
  306. }
  307. }
  308. /**
  309. * Executes the given Javascript
  310. *
  311. * @param script
  312. * the script to execute
  313. * @return whatever
  314. * {@link org.openqa.selenium.JavascriptExecutor#executeScript(String, Object...)}
  315. * returns
  316. */
  317. protected Object executeScript(String script, Object... args) {
  318. return ((JavascriptExecutor) getDriver()).executeScript(script, args);
  319. }
  320. /**
  321. * Find a Vaadin element based on its id given using Component.setId
  322. *
  323. * @param id
  324. * The id to locate
  325. * @return
  326. */
  327. public WebElement vaadinElementById(String id) {
  328. return driver.findElement(By.id(id));
  329. }
  330. /**
  331. * Finds a {@link By} locator based on the part of a TB2 style locator
  332. * following the :: (e.g.
  333. * vaadin=runLabelModes::PID_Scheckboxaction-Enabled/domChild[0] ->
  334. * PID_Scheckboxaction-Enabled/domChild[0]).
  335. *
  336. * @param vaadinLocator
  337. * The part following :: of the vaadin locator string
  338. * @return
  339. */
  340. public org.openqa.selenium.By vaadinLocator(String vaadinLocator) {
  341. String base = getApplicationId(getDeploymentPath());
  342. base += "::";
  343. return com.vaadin.testbench.By.vaadin(base + vaadinLocator);
  344. }
  345. /**
  346. * Constructs a {@link By} locator for the id given using Component.setId
  347. *
  348. * @param id
  349. * The id to locate
  350. * @return a locator for the given id
  351. */
  352. public By vaadinLocatorById(String id) {
  353. return vaadinLocator("PID_S" + id);
  354. }
  355. /**
  356. * Waits up to 10s for the given condition to become false. Use e.g. as
  357. * {@link #waitUntilNot(ExpectedConditions.textToBePresentInElement(by,
  358. * text))}
  359. *
  360. * @param condition
  361. * the condition to wait for to become false
  362. */
  363. protected <T> void waitUntilNot(ExpectedCondition<T> condition) {
  364. waitUntilNot(condition, 10);
  365. }
  366. /**
  367. * Waits the given number of seconds for the given condition to become
  368. * false. Use e.g. as
  369. * {@link #waitUntilNot(ExpectedConditions.textToBePresentInElement(by,
  370. * text))}
  371. *
  372. * @param condition
  373. * the condition to wait for to become false
  374. */
  375. protected <T> void waitUntilNot(ExpectedCondition<T> condition,
  376. long timeoutInSeconds) {
  377. waitUntil(ExpectedConditions.not(condition), timeoutInSeconds);
  378. }
  379. protected void waitForElementPresent(final By by) {
  380. waitUntil(ExpectedConditions.presenceOfElementLocated(by));
  381. }
  382. protected void waitForElementNotPresent(final By by) {
  383. waitUntil(input -> input.findElements(by).isEmpty());
  384. }
  385. protected void waitForElementVisible(final By by) {
  386. waitUntil(ExpectedConditions.visibilityOfElementLocated(by));
  387. }
  388. /**
  389. * Checks if the given element has the given class name.
  390. *
  391. * Matches only full class names, i.e. has ("foo") does not match
  392. * class="foobar"
  393. *
  394. * @param element
  395. * @param className
  396. * @return
  397. */
  398. protected boolean hasCssClass(WebElement element, String className) {
  399. String classes = element.getAttribute("class");
  400. if (classes == null || classes.isEmpty()) {
  401. return (className == null || className.isEmpty());
  402. }
  403. for (String cls : classes.split(" ")) {
  404. if (className.equals(cls)) {
  405. return true;
  406. }
  407. }
  408. return false;
  409. }
  410. /**
  411. * For tests extending AbstractTestUIWithLog, returns the element for the
  412. * Nth log row
  413. *
  414. * @param rowNr
  415. * The log row to retrieve
  416. * @return the Nth log row
  417. */
  418. protected WebElement getLogRowElement(int rowNr) {
  419. return vaadinElementById("Log_row_" + rowNr);
  420. }
  421. /**
  422. * For tests extending AbstractTestUIWithLog, returns the text in the Nth
  423. * log row
  424. *
  425. * @param rowNr
  426. * The log row to retrieve text for
  427. * @return the text in the log row
  428. */
  429. protected String getLogRow(int rowNr) {
  430. return getLogRowElement(rowNr).getText();
  431. }
  432. /**
  433. * Asserts that {@literal a} is &gt;= {@literal b}
  434. *
  435. * @param message
  436. * The message to include in the {@link AssertionError}
  437. * @param a
  438. * @param b
  439. * @throws AssertionError
  440. * If comparison fails
  441. */
  442. public static final <T> void assertGreaterOrEqual(String message,
  443. Comparable<T> a, T b) throws AssertionError {
  444. if (a.compareTo(b) >= 0) {
  445. return;
  446. }
  447. throw new AssertionError(decorate(message, a, b));
  448. }
  449. /**
  450. * Asserts that {@literal a} is &gt; {@literal b}
  451. *
  452. * @param message
  453. * The message to include in the {@link AssertionError}
  454. * @param a
  455. * @param b
  456. * @throws AssertionError
  457. * If comparison fails
  458. */
  459. public static final <T> void assertGreater(String message, Comparable<T> a,
  460. T b) throws AssertionError {
  461. if (a.compareTo(b) > 0) {
  462. return;
  463. }
  464. throw new AssertionError(decorate(message, a, b));
  465. }
  466. /**
  467. * Asserts that {@literal a} is &lt;= {@literal b}
  468. *
  469. * @param message
  470. * The message to include in the {@link AssertionError}
  471. * @param a
  472. * @param b
  473. * @throws AssertionError
  474. * If comparison fails
  475. */
  476. public static final <T> void assertLessThanOrEqual(String message,
  477. Comparable<T> a, T b) throws AssertionError {
  478. if (a.compareTo(b) <= 0) {
  479. return;
  480. }
  481. throw new AssertionError(decorate(message, a, b));
  482. }
  483. /**
  484. * Asserts that {@literal a} is &lt; {@literal b}
  485. *
  486. * @param message
  487. * The message to include in the {@link AssertionError}
  488. * @param a
  489. * @param b
  490. * @throws AssertionError
  491. * If comparison fails
  492. */
  493. public static final <T> void assertLessThan(String message, Comparable<T> a,
  494. T b) throws AssertionError {
  495. if (a.compareTo(b) < 0) {
  496. return;
  497. }
  498. throw new AssertionError(decorate(message, a, b));
  499. }
  500. private static <T> String decorate(String message, Comparable<T> a, T b) {
  501. message = message.replace("{0}", a.toString());
  502. message = message.replace("{1}", b.toString());
  503. return message;
  504. }
  505. /**
  506. * Returns the path that should be used for the test. The path contains the
  507. * full path (appended to hostname+port) and must start with a slash.
  508. *
  509. * @param push
  510. * true if "?debug" should be added
  511. * @param debug
  512. * true if /run-push should be used instead of /run
  513. *
  514. * @return The URL path to the UI class to test
  515. */
  516. protected String getDeploymentPath() {
  517. Class<?> uiClass = getUIClass();
  518. if (uiClass != null) {
  519. return getDeploymentPath(uiClass);
  520. }
  521. throw new IllegalArgumentException("Unable to determine path for "
  522. + getClass().getCanonicalName());
  523. }
  524. /**
  525. * Returns the UI class the current test is connected to (or in special
  526. * cases UIProvider or LegacyApplication). Uses the enclosing class if the
  527. * test class is a static inner class to a UI class.
  528. *
  529. * Test which are not enclosed by a UI class must implement this method and
  530. * return the UI class they want to test.
  531. *
  532. * Note that this method will update the test name to the enclosing class to
  533. * be compatible with TB2 screenshot naming
  534. *
  535. * @return the UI class the current test is connected to
  536. */
  537. protected Class<?> getUIClass() {
  538. try {
  539. // Convention: SomeUITest uses the SomeUI UI class
  540. String uiClassName = getClass().getName().replaceFirst("Test$", "");
  541. Class<?> cls = Class.forName(uiClassName);
  542. if (isSupportedRunnerClass(cls)) {
  543. return cls;
  544. }
  545. } catch (Exception e) {
  546. }
  547. throw new RuntimeException(
  548. "Could not determine UI class. Ensure the test is named UIClassTest and is in the same package as the UIClass");
  549. }
  550. /**
  551. * @return true if the given class is supported by ApplicationServletRunner
  552. */
  553. @SuppressWarnings("deprecation")
  554. private boolean isSupportedRunnerClass(Class<?> cls) {
  555. if (UI.class.isAssignableFrom(cls)) {
  556. return true;
  557. }
  558. if (UIProvider.class.isAssignableFrom(cls)) {
  559. return true;
  560. }
  561. if (LegacyApplication.class.isAssignableFrom(cls)) {
  562. return true;
  563. }
  564. return false;
  565. }
  566. /**
  567. * Returns whether to run the test in debug mode (with the debug console
  568. * open) or not
  569. *
  570. * @return true to run with the debug window open, false by default
  571. */
  572. protected final boolean isDebug() {
  573. return debug;
  574. }
  575. /**
  576. * Sets whether to run the test in debug mode (with the debug console open)
  577. * or not.
  578. *
  579. * @param debug
  580. * true to open debug window, false otherwise
  581. */
  582. protected final void setDebug(boolean debug) {
  583. this.debug = debug;
  584. }
  585. /**
  586. * Returns whether to run the test with push enabled (using /run-push) or
  587. * not. Note that push tests can and should typically be created using @Push
  588. * on the UI instead of overriding this method
  589. *
  590. * @return true if /run-push is used, false otherwise
  591. */
  592. protected final boolean isPush() {
  593. return push;
  594. }
  595. /**
  596. * Sets whether to run the test with push enabled (using /run-push) or not.
  597. * Note that push tests can and should typically be created using @Push on
  598. * the UI instead of overriding this method
  599. *
  600. * @param push
  601. * true to use /run-push in the test, false otherwise
  602. */
  603. protected final void setPush(boolean push) {
  604. this.push = push;
  605. }
  606. /**
  607. * Returns the path for the given UI class when deployed on the test server.
  608. * The path contains the full path (appended to hostname+port) and must
  609. * start with a slash.
  610. *
  611. * This method takes into account {@link #isPush()} and {@link #isDebug()}
  612. * when the path is generated.
  613. *
  614. * @param uiClass
  615. * @param push
  616. * true if "?debug" should be added
  617. * @param debug
  618. * true if /run-push should be used instead of /run
  619. * @return The path to the given UI class
  620. */
  621. protected String getDeploymentPath(Class<?> uiClass) {
  622. String runPath = "/run";
  623. if (isPush()) {
  624. runPath = "/run-push";
  625. }
  626. if (UI.class.isAssignableFrom(uiClass)
  627. || UIProvider.class.isAssignableFrom(uiClass)
  628. || LegacyApplication.class.isAssignableFrom(uiClass)) {
  629. return runPath + "/" + uiClass.getCanonicalName();
  630. } else {
  631. throw new IllegalArgumentException(
  632. "Unable to determine path for enclosing class "
  633. + uiClass.getCanonicalName());
  634. }
  635. }
  636. /**
  637. * Used to determine what URL to initially open for the test
  638. *
  639. * @return The base URL for the test. Does not include a trailing slash.
  640. */
  641. protected String getBaseURL() {
  642. return "http://" + getDeploymentHostname() + ":" + getDeploymentPort();
  643. }
  644. /**
  645. * Generates the application id based on the URL in a way compatible with
  646. * VaadinServletService.
  647. *
  648. * @param pathWithQueryParameters
  649. * The path part of the URL, possibly still containing query
  650. * parameters
  651. * @return The application ID string used in Vaadin locators
  652. */
  653. private String getApplicationId(String pathWithQueryParameters) {
  654. // Remove any possible URL parameters
  655. String pathWithoutQueryParameters = pathWithQueryParameters
  656. .replaceAll("\\?.*", "");
  657. if (pathWithoutQueryParameters.isEmpty()) {
  658. return "ROOT";
  659. }
  660. // Retain only a-z and numbers
  661. return pathWithoutQueryParameters.replaceAll("[^a-zA-Z0-9]", "");
  662. }
  663. /**
  664. * Sleeps for the given number of ms but ensures that the browser connection
  665. * does not time out.
  666. *
  667. * @param timeoutMillis
  668. * Number of ms to wait
  669. */
  670. protected void sleep(int timeoutMillis) {
  671. while (timeoutMillis > 0) {
  672. int d = Math.min(BROWSER_TIMEOUT_IN_MS, timeoutMillis);
  673. try {
  674. Thread.sleep(d);
  675. } catch (InterruptedException e) {
  676. throw new RuntimeException(e);
  677. }
  678. timeoutMillis -= d;
  679. // Do something to keep the connection alive
  680. getDriver().getTitle();
  681. }
  682. }
  683. /**
  684. * Called by the test runner whenever there is an exception in the test that
  685. * will cause termination of the test
  686. *
  687. * @param t
  688. * the throwable which caused the termination
  689. */
  690. public void onUncaughtException(Throwable t) {
  691. // Do nothing by default
  692. }
  693. /**
  694. * Returns the mouse object for doing mouse commands
  695. *
  696. * @return Returns the mouse
  697. */
  698. public Mouse getMouse() {
  699. return ((HasInputDevices) getDriver()).getMouse();
  700. }
  701. /**
  702. * Returns the keyboard object for controlling keyboard events
  703. *
  704. * @return Return the keyboard
  705. */
  706. public Keyboard getKeyboard() {
  707. return ((HasInputDevices) getDriver()).getKeyboard();
  708. }
  709. public void hitButton(String id) {
  710. driver.findElement(By.id(id)).click();
  711. }
  712. protected void openDebugLogTab() {
  713. waitUntil(input -> {
  714. try {
  715. WebElement element = getDebugLogButton();
  716. return element != null;
  717. } catch (NoSuchElementException e) {
  718. return false;
  719. }
  720. }, 15);
  721. getDebugLogButton().click();
  722. }
  723. private WebElement getDebugLogButton() {
  724. return findElement(By.xpath("//button[@title='Debug message log']"));
  725. }
  726. protected void assertNoDebugMessage(Level level) {
  727. // class="v-debugwindow-row Level.getName()"
  728. List<WebElement> logElements = driver.findElements(By.xpath(String
  729. .format("//div[@class='v-debugwindow-row %s']/span[@class='v-debugwindow-message']",
  730. level.getName())));
  731. if (!logElements.isEmpty()) {
  732. String logRows = "";
  733. for (WebElement e : logElements) {
  734. logRows += "\n" + e.getText();
  735. }
  736. fail("Found debug messages with level " + level.getName() + ": "
  737. + logRows);
  738. }
  739. }
  740. /**
  741. * Should the "require window focus" be enabled for Internet Explorer.
  742. * RequireWindowFocus makes tests more stable but seems to be broken with
  743. * certain commands such as sendKeys. Therefore it is not enabled by default
  744. * for all tests
  745. *
  746. * @return true, to use the "require window focus" feature, false otherwise
  747. */
  748. protected boolean requireWindowFocusForIE() {
  749. return false;
  750. }
  751. /**
  752. * Should the "enable persistent hover" be enabled for Internet Explorer.
  753. *
  754. * Persistent hovering causes continuous firing of mouse over events at the
  755. * last location the mouse cursor has been moved to. This is to avoid
  756. * problems where the real mouse cursor is inside the browser window and
  757. * Internet Explorer uses that location for some undefined operation
  758. * (http://
  759. * jimevansmusic.blogspot.fi/2012/06/whats-wrong-with-internet-explorer
  760. * .html)
  761. *
  762. * @return true, to use the "persistent hover" feature, false otherwise
  763. */
  764. protected boolean usePersistentHoverForIE() {
  765. return true;
  766. }
  767. /**
  768. * Should the "native events" be enabled for Internet Explorer.
  769. * <p>
  770. * Native events sometimes cause failure in clicking on buttons/checkboxes
  771. * but are possibly needed for some operations.
  772. *
  773. * @return true, to use "native events", false to use generated Javascript
  774. * events
  775. */
  776. protected boolean useNativeEventsForIE() {
  777. return true;
  778. }
  779. // FIXME: Remove this once TB4 getRemoteControlName works properly
  780. private RemoteWebDriver getRemoteDriver() {
  781. WebDriver d = getDriver();
  782. if (d instanceof TestBenchDriverProxy) {
  783. try {
  784. Field f = TestBenchDriverProxy.class
  785. .getDeclaredField("actualDriver");
  786. f.setAccessible(true);
  787. return (RemoteWebDriver) f.get(d);
  788. } catch (Exception e) {
  789. e.printStackTrace();
  790. }
  791. }
  792. if (d instanceof RemoteWebDriver) {
  793. return (RemoteWebDriver) d;
  794. }
  795. return null;
  796. }
  797. // FIXME: Remove this once TB4 getRemoteControlName works properly
  798. protected String getRemoteControlName() {
  799. try {
  800. RemoteWebDriver d = getRemoteDriver();
  801. if (d == null) {
  802. return null;
  803. }
  804. HttpCommandExecutor ce = (HttpCommandExecutor) d
  805. .getCommandExecutor();
  806. String hostName = ce.getAddressOfRemoteServer().getHost();
  807. int port = ce.getAddressOfRemoteServer().getPort();
  808. HttpHost host = new HttpHost(hostName, port);
  809. try (DefaultHttpClient client = new DefaultHttpClient()) {
  810. URL sessionURL = new URL("http://" + hostName + ":" + port
  811. + "/grid/api/testsession?session=" + d.getSessionId());
  812. BasicHttpEntityEnclosingRequest r = new BasicHttpEntityEnclosingRequest(
  813. "POST", sessionURL.toExternalForm());
  814. HttpResponse response = client.execute(host, r);
  815. JsonObject object = extractObject(response);
  816. URL myURL = new URL(object.getString("proxyId"));
  817. if ((myURL.getHost() != null) && (myURL.getPort() != -1)) {
  818. return myURL.getHost();
  819. }
  820. }
  821. } catch (Exception e) {
  822. e.printStackTrace();
  823. }
  824. return null;
  825. }
  826. protected boolean logContainsText(String string) {
  827. List<String> logs = getLogs();
  828. for (String text : logs) {
  829. if (text.contains(string)) {
  830. return true;
  831. }
  832. }
  833. return false;
  834. }
  835. protected List<String> getLogs() {
  836. VerticalLayoutElement log = $(VerticalLayoutElement.class).id("Log");
  837. List<LabelElement> logLabels = log.$(LabelElement.class).all();
  838. List<String> logTexts = new ArrayList<>();
  839. for (LabelElement label : logLabels) {
  840. logTexts.add(label.getText());
  841. }
  842. return logTexts;
  843. }
  844. private static JsonObject extractObject(HttpResponse resp)
  845. throws IOException {
  846. InputStream contents = resp.getEntity().getContent();
  847. StringWriter writer = new StringWriter();
  848. IOUtils.copy(contents, writer, "UTF8");
  849. return JsonUtil.parse(writer.toString());
  850. }
  851. protected void click(CheckBoxElement checkbox) {
  852. WebElement cb = checkbox.findElement(By.xpath("input"));
  853. if (BrowserUtil.isChrome(getDesiredCapabilities())) {
  854. testBenchElement(cb).click(0, 0);
  855. } else if (BrowserUtil.isFirefox(getDesiredCapabilities())) {
  856. // Firefox workaround
  857. getCommandExecutor().executeScript("arguments[0].click()", cb);
  858. } else {
  859. cb.click();
  860. }
  861. }
  862. protected void clickElement(WebElement element) {
  863. if (BrowserUtil.isFirefox(getDesiredCapabilities())) {
  864. // Workaround for Selenium/TB and Firefox 45 issue
  865. ((TestBenchElement) (element)).clickHiddenElement();
  866. } else {
  867. element.click();
  868. }
  869. }
  870. protected void contextClickElement(WebElement element) {
  871. if (BrowserUtil.isFirefox(getDesiredCapabilities())) {
  872. // Workaround for Selenium/TB and Firefox 45 issue
  873. getCommandExecutor().executeScript(
  874. "var ev = document.createEvent('HTMLEvents'); ev.initEvent('contextmenu', true, false); arguments[0].dispatchEvent(ev);",
  875. element);
  876. } else {
  877. new Actions(getDriver()).contextClick(element).perform();
  878. }
  879. }
  880. protected boolean isLoadingIndicatorVisible() {
  881. WebElement loadingIndicator = findElement(
  882. By.className("v-loading-indicator"));
  883. return loadingIndicator.isDisplayed();
  884. }
  885. protected void waitUntilLoadingIndicatorVisible() {
  886. waitUntil(input -> isLoadingIndicatorVisible());
  887. }
  888. protected void waitUntilLoadingIndicatorNotVisible() {
  889. waitUntil(input -> !isLoadingIndicatorVisible());
  890. }
  891. /**
  892. * Selects a menu item. By default, this will click on the menu item.
  893. *
  894. * @param menuCaption
  895. * caption of the menu item
  896. */
  897. protected void selectMenu(String menuCaption) {
  898. selectMenu(menuCaption, true);
  899. }
  900. /**
  901. * Selects a menu item.
  902. *
  903. * @param menuCaption
  904. * caption of the menu item
  905. * @param click
  906. * <code>true</code> if should click the menu item;
  907. * <code>false</code> if not
  908. */
  909. protected void selectMenu(String menuCaption, boolean click) {
  910. WebElement menuElement = getMenuElement(menuCaption);
  911. new Actions(getDriver()).moveToElement(menuElement).perform();
  912. if (click) {
  913. new Actions(getDriver()).click().perform();
  914. }
  915. }
  916. /**
  917. * Finds the menu item from the DOM based on menu item caption.
  918. *
  919. * @param menuCaption
  920. * caption of the menu item
  921. * @return the found menu item
  922. * @throws NoSuchElementException
  923. * if menu item is not found
  924. */
  925. protected WebElement getMenuElement(String menuCaption)
  926. throws NoSuchElementException {
  927. // Need the parent span to obtain the correct size
  928. return getDriver().findElement(
  929. By.xpath("//span[text() = '" + menuCaption + "']/.."));
  930. }
  931. /**
  932. * Selects a submenu described by a path of menus from the first MenuBar in
  933. * the UI.
  934. *
  935. * @param menuCaptions
  936. * array of menu captions
  937. */
  938. protected void selectMenuPath(String... menuCaptions) {
  939. selectMenu(menuCaptions[0], true);
  940. // Make sure menu popup is opened.
  941. waitUntil(e -> isElementPresent(By.className("gwt-MenuBarPopup"))
  942. || isElementPresent(By.className("v-menubar-popup")));
  943. // Move to the menu item opened below the menu bar.
  944. new Actions(getDriver())
  945. .moveByOffset(0,
  946. getMenuElement(menuCaptions[0]).getSize().getHeight())
  947. .perform();
  948. for (int i = 1; i < menuCaptions.length - 1; i++) {
  949. selectMenu(menuCaptions[i]);
  950. new Actions(getDriver()).moveByOffset(
  951. getMenuElement(menuCaptions[i]).getSize().getWidth(), 0)
  952. .build().perform();
  953. }
  954. selectMenu(menuCaptions[menuCaptions.length - 1], true);
  955. }
  956. /**
  957. * Asserts that an element is present
  958. *
  959. * @param by
  960. * the locator for the element
  961. */
  962. protected void assertElementPresent(By by) {
  963. assertTrue("Element is not present", isElementPresent(by));
  964. }
  965. /**
  966. * Asserts that an element is not present
  967. *
  968. * @param by
  969. * the locator for the element
  970. */
  971. protected void assertElementNotPresent(By by) {
  972. assertFalse("Element is present", isElementPresent(by));
  973. }
  974. /**
  975. * Asserts that no error notifications are shown. Requires the use of
  976. * "?debug" as exceptions are otherwise not shown as notifications.
  977. */
  978. protected void assertNoErrorNotifications() {
  979. assertFalse("Error notification with client side exception is shown",
  980. isNotificationPresent("error"));
  981. }
  982. /**
  983. * Asserts that no system notifications are shown.
  984. */
  985. protected void assertNoSystemNotifications() {
  986. assertFalse("Error notification with system error exception is shown",
  987. isNotificationPresent("system"));
  988. }
  989. /**
  990. * Asserts that a system notification is shown.
  991. */
  992. protected void assertSystemNotification() {
  993. assertTrue(
  994. "Error notification with system error exception is not shown",
  995. isNotificationPresent("system"));
  996. }
  997. private boolean isNotificationPresent(String type) {
  998. if ("error".equals(type)) {
  999. assertTrue(
  1000. "Debug window must be open to be able to see error notifications",
  1001. isDebugWindowOpen());
  1002. }
  1003. return isElementPresent(By.className("v-Notification-" + type));
  1004. }
  1005. private boolean isDebugWindowOpen() {
  1006. return isElementPresent(By.className("v-debugwindow"));
  1007. }
  1008. protected void assertNoHorizontalScrollbar(WebElement element,
  1009. String errorMessage) {
  1010. assertHasHorizontalScrollbar(element, errorMessage, false);
  1011. }
  1012. protected void assertHorizontalScrollbar(WebElement element,
  1013. String errorMessage) {
  1014. assertHasHorizontalScrollbar(element, errorMessage, true);
  1015. }
  1016. private void assertHasHorizontalScrollbar(WebElement element,
  1017. String errorMessage, boolean expected) {
  1018. // IE rounds clientWidth/clientHeight down and scrollHeight/scrollWidth
  1019. // up, so using clientWidth/clientHeight will fail if the element height
  1020. // is not an integer
  1021. int clientWidth = getClientWidth(element);
  1022. int scrollWidth = getScrollWidth(element);
  1023. boolean hasScrollbar = scrollWidth > clientWidth;
  1024. String message = "The element should";
  1025. if (!expected) {
  1026. message += " not";
  1027. }
  1028. message += " have a horizontal scrollbar (scrollWidth: " + scrollWidth
  1029. + ", clientWidth: " + clientWidth + "): " + errorMessage;
  1030. assertEquals(message, expected, hasScrollbar);
  1031. }
  1032. protected void assertNoVerticalScrollbar(WebElement element,
  1033. String errorMessage) {
  1034. // IE rounds clientWidth/clientHeight down and scrollHeight/scrollWidth
  1035. // up, so using clientWidth/clientHeight will fail if the element height
  1036. // is not an integer
  1037. int clientHeight = getClientHeight(element);
  1038. int scrollHeight = getScrollHeight(element);
  1039. boolean hasScrollbar = scrollHeight > clientHeight;
  1040. assertFalse(
  1041. "The element should not have a vertical scrollbar (scrollHeight: "
  1042. + scrollHeight + ", clientHeight: " + clientHeight
  1043. + "): " + errorMessage,
  1044. hasScrollbar);
  1045. }
  1046. protected int getScrollHeight(WebElement element) {
  1047. return ((Number) executeScript("return arguments[0].scrollHeight;",
  1048. element)).intValue();
  1049. }
  1050. protected int getScrollWidth(WebElement element) {
  1051. return ((Number) executeScript("return arguments[0].scrollWidth;",
  1052. element)).intValue();
  1053. }
  1054. protected int getScrollTop(WebElement element) {
  1055. return ((Number) executeScript("return arguments[0].scrollTop;",
  1056. element)).intValue();
  1057. }
  1058. /**
  1059. * Gets the X offset for
  1060. * {@link Actions#moveToElement(WebElement, int, int)}. This method takes
  1061. * into account the W3C specification in browsers that properly implement
  1062. * it.
  1063. *
  1064. * @param element
  1065. * the element
  1066. * @param targetX
  1067. * the X coordinate where the move is wanted to go to
  1068. * @return the correct X offset
  1069. */
  1070. protected int getXOffset(WebElement element, int targetX) {
  1071. if (BrowserUtil.isFirefox(getDesiredCapabilities())) {
  1072. // Firefox follow W3C spec and moveToElement is relative to center
  1073. final int width = element.getSize().getWidth();
  1074. return targetX - ((width + width % 2) / 2);
  1075. }
  1076. if (BrowserUtil.isChrome(getDesiredCapabilities())) {
  1077. // Chrome follow W3C spec and moveToElement is relative to center
  1078. final int width = element.getSize().getWidth();
  1079. return targetX - ((width + width % 2) / 2);
  1080. }
  1081. return targetX;
  1082. }
  1083. /**
  1084. * Gets the Y offset for
  1085. * {@link Actions#moveToElement(WebElement, int, int)}. This method takes
  1086. * into account the W3C specification in browsers that properly implement
  1087. * it.
  1088. *
  1089. * @param element
  1090. * the element
  1091. * @param targetY
  1092. * the Y coordinate where the move is wanted to go to
  1093. * @return the correct Y offset
  1094. */
  1095. protected int getYOffset(WebElement element, int targetY) {
  1096. if (BrowserUtil.isFirefox(getDesiredCapabilities())) {
  1097. // Firefox follow W3C spec and moveToElement is relative to center
  1098. final int height = element.getSize().getHeight();
  1099. return targetY - ((height + height % 2) / 2);
  1100. }
  1101. if (BrowserUtil.isChrome(getDesiredCapabilities())) {
  1102. // Chrome follow W3C spec and moveToElement is relative to center
  1103. final int height = element.getSize().getHeight();
  1104. return targetY - ((height + height % 2) / 2);
  1105. }
  1106. return targetY;
  1107. }
  1108. /**
  1109. * Returns client height rounded up instead of as double because of IE9
  1110. * issues: https://dev.vaadin.com/ticket/18469
  1111. */
  1112. protected int getClientHeight(WebElement e) {
  1113. String script = "var cs = window.getComputedStyle(arguments[0]);"
  1114. + "return Math.ceil(parseFloat(cs.height)+parseFloat(cs.paddingTop)+parseFloat(cs.paddingBottom));";
  1115. return ((Number) executeScript(script, e)).intValue();
  1116. }
  1117. /**
  1118. * Returns client width rounded up instead of as double because of IE9
  1119. * issues: https://dev.vaadin.com/ticket/18469
  1120. */
  1121. protected int getClientWidth(WebElement e) {
  1122. String script = "var cs = window.getComputedStyle(arguments[0]);"
  1123. + "var h = parseFloat(cs.width)+parseFloat(cs.paddingLeft)+parseFloat(cs.paddingRight);"
  1124. + "return Math.ceil(h);";
  1125. return ((Number) executeScript(script, e)).intValue();
  1126. }
  1127. protected TimeZone getBrowserTimeZone() {
  1128. Assume.assumeFalse(
  1129. "Internet Explorer 11 does not support resolvedOptions timeZone",
  1130. BrowserUtil.isIE(getDesiredCapabilities(), 11));
  1131. // Ask TimeZone from browser
  1132. String browserTimeZone = ((JavascriptExecutor) getDriver())
  1133. .executeScript(
  1134. "return Intl.DateTimeFormat().resolvedOptions().timeZone;")
  1135. .toString();
  1136. return TimeZone.getTimeZone(browserTimeZone);
  1137. }
  1138. protected void assertElementsEquals(WebElement expectedElement,
  1139. WebElement actualElement) {
  1140. while (expectedElement instanceof WrapsElement) {
  1141. expectedElement = ((WrapsElement) expectedElement)
  1142. .getWrappedElement();
  1143. }
  1144. while (actualElement instanceof WrapsElement) {
  1145. actualElement = ((WrapsElement) actualElement).getWrappedElement();
  1146. }
  1147. assertEquals(expectedElement, actualElement);
  1148. }
  1149. protected WebElement getActiveElement() {
  1150. return (WebElement) executeScript("return document.activeElement;");
  1151. }
  1152. protected void waitForThemeToChange(final String theme) {
  1153. final WebElement rootDiv = findElement(
  1154. By.xpath("//div[contains(@class,'v-app')]"));
  1155. waitUntil(input -> {
  1156. String rootClass = rootDiv.getAttribute("class").trim();
  1157. return rootClass.contains(theme);
  1158. }, 30);
  1159. }
  1160. }