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 40KB

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