001 /* 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package edu.samplu.common; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.commons.lang3.exception.ExceptionUtils; 020 import org.openqa.selenium.By; 021 import org.openqa.selenium.JavascriptExecutor; 022 import org.openqa.selenium.NoSuchFrameException; 023 import org.openqa.selenium.Proxy; 024 import org.openqa.selenium.WebDriver; 025 import org.openqa.selenium.WebElement; 026 import org.openqa.selenium.chrome.ChromeDriver; 027 import org.openqa.selenium.chrome.ChromeDriverService; 028 import org.openqa.selenium.firefox.FirefoxDriver; 029 import org.openqa.selenium.firefox.FirefoxProfile; 030 import org.openqa.selenium.remote.CapabilityType; 031 import org.openqa.selenium.remote.DesiredCapabilities; 032 import org.openqa.selenium.remote.RemoteWebDriver; 033 import org.openqa.selenium.safari.SafariDriver; 034 035 import com.thoughtworks.selenium.SeleneseTestBase; 036 037 import java.io.File; 038 import java.net.MalformedURLException; 039 import java.net.URL; 040 import java.util.concurrent.TimeUnit; 041 042 /** 043 * The goal of the WebDriverUtil class is to invert the dependencies on WebDriver from WebDriverLegacyITBase for reuse 044 * without having to extend WebDriverLegacyITBase. For the first example see waitFor 045 * 046 * @see WebDriverLegacyITBase 047 * @author Kuali Rice Team (rice.collab@kuali.org) 048 */ 049 public class WebDriverUtil { 050 051 public static boolean jGrowlEnabled = false; 052 053 public static boolean jsHighlightEnabled = false; 054 055 /** 056 * TODO apparent dup WebDriverITBase.DEFAULT_WAIT_SEC 057 * TODO parametrize for JVM Arg 058 * 30 Seconds 059 */ 060 public static int DEFAULT_IMPLICIT_WAIT_TIME = 30; 061 062 /** 063 * false 064 * TODO upgrade to config via JVM param. 065 */ 066 public static final boolean JGROWL_ERROR_FAILURE = false; 067 068 /** 069 * green 070 */ 071 public static final String JS_HIGHLIGHT_BACKGROUND = "green"; 072 073 /** 074 * green 075 */ 076 public static final String JS_HIGHLIGHT_BOARDER = "green"; 077 078 /** 079 * 400 milliseconds 080 */ 081 public static final int JS_HIGHLIGHT_MS = 400; 082 083 /** 084 * -Dremote.driver.highlight=true to enable highlighting of elements as selenium runs 085 */ 086 public static final String JS_HIGHLIGHT_PROPERTY = "remote.driver.highlight"; 087 088 /** 089 * TODO introduce SHORT_IMPLICIT_WAIT_TIME with param in WebDriverITBase 090 * TODO parametrize for JVM Arg 091 * 1 Second 092 */ 093 public static int SHORT_IMPLICIT_WAIT_TIME = 1; 094 095 /** 096 * Set -Dremote.driver.saucelabs for running on saucelabs 097 * @link https://wiki.kuali.org/display/KULRICE/How+To+Run+a+Selenium+Test for patch required 098 */ 099 public static final String REMOTE_DRIVER_SAUCELABS_PROPERTY = "remote.driver.saucelabs"; 100 101 /** 102 * Selenium's webdriver.chrome.driver parameter, you can set -Dwebdriver.chrome.driver= or Rice's REMOTE_PUBLIC_CHROME 103 */ 104 public static final String WEBDRIVER_CHROME_DRIVER = "webdriver.chrome.driver"; 105 106 /** 107 * Set -Dremote.jgrowl.enabled= 108 */ 109 public static final String REMOTE_JGROWL_ENABLED = "remote.jgrowl.enabled"; 110 111 /** 112 * Set -Dremote.login.uif=KNS to use old login screen. Default value = KRAD 113 */ 114 public static final String REMOTE_LOGIN_UIF = "remote.login.uif"; 115 116 /** 117 * Set -Dremote.public.chrome= or WEBDRIVER_CHROME_DRIVER 118 */ 119 public static final String REMOTE_PUBLIC_CHROME = "remote.public.chrome"; 120 121 /** 122 * Time to wait for the URL used in setup to load. Sometimes this is the first hit on the app and it needs a bit 123 * longer than any other. 120 Seconds. 124 * TODO parametrize for JVM Arg 125 */ 126 public static final int SETUP_URL_LOAD_WAIT_SECONDS = 120; 127 128 /** 129 * local proxy used for running tests thru jmeter. 130 * Include host name and port number. Example: localhost:7777 131 */ 132 public static final String PROXY_HOST_PROPERTY = "remote.public.proxy"; 133 134 /** 135 * Setup the WebDriver test, login, and load the given web page 136 * 137 * @param username 138 * @param url 139 * @return driver 140 * @throws Exception 141 */ 142 public static WebDriver setUp(String username, String url) throws Exception { 143 return setUp(username, url, null, null); 144 } 145 146 /** 147 * Setup the WebDriver test, login, and load the given web page 148 * 149 * @param username 150 * @param url 151 * @param className 152 * @param testName 153 * @return driver 154 * @throws Exception 155 */ 156 public static WebDriver setUp(String username, String url, String className, String testName) throws Exception { 157 if ("true".equals(System.getProperty(REMOTE_JGROWL_ENABLED, "false"))) { 158 jGrowlEnabled = true; 159 } 160 161 if ("true".equals(System.getProperty(JS_HIGHLIGHT_PROPERTY, "false"))) { 162 jsHighlightEnabled = true; 163 } 164 165 WebDriver driver = null; 166 if (System.getProperty(REMOTE_DRIVER_SAUCELABS_PROPERTY) == null) { 167 driver = getWebDriver(); 168 } else { 169 SauceLabsWebDriverHelper saucelabs = new SauceLabsWebDriverHelper(); 170 saucelabs.setUp(className, testName); 171 driver = saucelabs.getDriver(); 172 } 173 174 driver.manage().timeouts().implicitlyWait(SETUP_URL_LOAD_WAIT_SECONDS, TimeUnit.SECONDS); 175 driver.manage().window().maximize(); 176 177 // TODO Got into the situation where the first url doesn't expect server, but all others do. Readdress once 178 // the NavIT WDIT conversion has been completed. 179 if (!url.startsWith("http")) { 180 url = ITUtil.getBaseUrlString() + url; 181 } 182 183 driver.get(url); 184 driver.manage().timeouts().implicitlyWait(DEFAULT_IMPLICIT_WAIT_TIME, TimeUnit.SECONDS); 185 return driver; 186 } 187 188 /** 189 * 190 * @param passed 191 * @param sessionId 192 * @param testParam 193 * @param userParam 194 * @throws Exception 195 */ 196 public static void tearDown(boolean passed, String sessionId, String testParam, String userParam) throws Exception { 197 198 if (System.getProperty(SauceLabsWebDriverHelper.SAUCE_PROPERTY) != null) { 199 SauceLabsWebDriverHelper.tearDown(passed, sessionId, System.getProperty(SauceLabsWebDriverHelper.SAUCE_USER_PROPERTY), 200 System.getProperty(SauceLabsWebDriverHelper.SAUCE_KEY_PROPERTY)); 201 } 202 203 if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) { 204 ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" 205 + testParam + "&user=" + userParam)); 206 } 207 } 208 209 /** 210 * 211 * @param testParam 212 * @return 213 */ 214 public static String determineUser(String testParam) { 215 String user = null; 216 217 if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY) != null) { 218 return System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY); 219 } else if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) { // deprecated 220 String userResponse = ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty( 221 WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" + testParam.trim())); 222 return userResponse.substring(userResponse.lastIndexOf(":") + 2, userResponse.lastIndexOf("\"")); 223 } 224 225 return user; 226 } 227 228 /*** 229 * @link ITUtil#checkForIncidentReport 230 * @param driver 231 * @param locator 232 * @param message 233 */ 234 public static void checkForIncidentReport(WebDriver driver, String locator, Failable failable, 235 String message) { 236 ITUtil.checkForIncidentReport(driver.getPageSource(), locator, failable, message); 237 } 238 239 /** 240 * @link http://code.google.com/p/chromedriver/downloads/list 241 * @link #REMOTE_PUBLIC_CHROME 242 * @link #WEBDRIVER_CHROME_DRIVER 243 * @link ITUtil#HUB_DRIVER_PROPERTY 244 * @return chromeDriverService 245 */ 246 public static ChromeDriverService chromeDriverCreateCheck() { 247 String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY); 248 // TODO can the saucelabs driver stuff be leveraged here? 249 if (driverParam != null && "chrome".equals(driverParam.toLowerCase())) { 250 if (System.getProperty(WEBDRIVER_CHROME_DRIVER) == null) { 251 if (System.getProperty(REMOTE_PUBLIC_CHROME) != null) { 252 System.setProperty(WEBDRIVER_CHROME_DRIVER, System.getProperty(REMOTE_PUBLIC_CHROME)); 253 } 254 } 255 try { 256 ChromeDriverService chromeDriverService = new ChromeDriverService.Builder() 257 .usingDriverExecutable(new File(System.getProperty(WEBDRIVER_CHROME_DRIVER))) 258 .usingAnyFreePort() 259 .build(); 260 return chromeDriverService; 261 } catch (Throwable t) { 262 throw new RuntimeException("Exception starting chrome driver service, is chromedriver ( http://code.google.com/p/chromedriver/downloads/list ) installed? You can include the path to it using -Dremote.public.chrome", t) ; 263 } 264 } 265 return null; 266 } 267 268 public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message, Throwable t) { 269 if (jGrowlEnabled) { // check if jGrowl is enabled to skip over the stack trace extraction if it is not. 270 jGrowl(driver, jGrowlHeader, sticky, message + " " + t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t)); 271 } 272 } 273 274 public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message) { 275 if (jGrowlEnabled) { 276 try { 277 String javascript="jQuery.jGrowl('" + message + "' , {sticky: " + sticky + ", header : '" + jGrowlHeader + "'});"; 278 ((JavascriptExecutor) driver).executeScript(javascript); 279 } catch (Throwable t) { 280 jGrowlException(t); 281 } 282 } 283 } 284 285 public static void jGrowlException(Throwable t) { 286 String failMessage = t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t); 287 System.out.println("jGrowl failure " + failMessage); 288 if (JGROWL_ERROR_FAILURE) { 289 SeleneseTestBase.fail(failMessage); 290 } 291 } 292 293 public static void highlightElement(WebDriver webDriver, WebElement webElement) { 294 if (jsHighlightEnabled) { 295 try { 296 JavascriptExecutor js = (JavascriptExecutor) webDriver; 297 js.executeScript("element = arguments[0];\n" 298 + "originalStyle = element.getAttribute('style');\n" 299 + "element.setAttribute('style', originalStyle + \"; background: " 300 + JS_HIGHLIGHT_BACKGROUND + "; border: 2px solid " + JS_HIGHLIGHT_BOARDER + ";\");\n" 301 + "setTimeout(function(){\n" 302 + " element.setAttribute('style', originalStyle);\n" 303 + "}, " + JS_HIGHLIGHT_MS + ");", webElement); 304 } catch (Throwable t) { 305 System.out.println("Throwable during javascript highlight element"); 306 t.printStackTrace(); 307 } 308 } 309 } 310 311 312 /** 313 * remote.public.driver set to chrome or firefox (null assumes firefox) 314 * if remote.public.hub is set a RemoteWebDriver is created (Selenium Grid) 315 * if proxy.host is set, the web driver is setup to use a proxy 316 * @return WebDriver or null if unable to create 317 */ 318 public static WebDriver getWebDriver() { 319 String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY); 320 String hubParam = System.getProperty(ITUtil.HUB_PROPERTY); 321 String proxyParam = System.getProperty(PROXY_HOST_PROPERTY); 322 323 // setup proxy if specified as VM Arg 324 DesiredCapabilities capabilities = new DesiredCapabilities(); 325 WebDriver webDriver = null; 326 if (StringUtils.isNotEmpty(proxyParam)) { 327 capabilities.setCapability(CapabilityType.PROXY, new Proxy().setHttpProxy(proxyParam)); 328 } 329 330 if (hubParam == null) { 331 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) { 332 FirefoxProfile profile = new FirefoxProfile(); 333 profile.setEnableNativeEvents(false); 334 capabilities.setCapability(FirefoxDriver.PROFILE, profile); 335 return new FirefoxDriver(capabilities); 336 } else if ("chrome".equalsIgnoreCase(driverParam)) { 337 return new ChromeDriver(capabilities); 338 } else if ("safari".equals(driverParam)) { 339 System.out.println("SafariDriver probably won't work, if it does please contact Erik M."); 340 return new SafariDriver(capabilities); 341 } 342 } else { 343 try { 344 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) { 345 return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.firefox()); 346 } else if ("chrome".equalsIgnoreCase(driverParam)) { 347 return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.chrome()); 348 } 349 } catch (MalformedURLException mue) { 350 System.out.println(ITUtil.getHubUrlString() + " " + mue.getMessage()); 351 mue.printStackTrace(); 352 } 353 } 354 return null; 355 } 356 357 /** 358 * Logs in using the KRAD Login Page 359 * If the JVM arg remote.autologin is set, auto login as admin will not be done. 360 * @param driver 361 * @param userName 362 * @param failable 363 * @throws InterruptedException 364 */ 365 public static void kradLogin(WebDriver driver, String userName, Failable failable) throws InterruptedException { 366 driver.findElement(By.name("login_user")).clear(); 367 driver.findElement(By.name("login_user")).sendKeys(userName); 368 driver.findElement(By.id("Rice-LoginButton")).click(); 369 Thread.sleep(1000); 370 String contents = driver.getPageSource(); 371 ITUtil.failOnInvalidUserName(userName, contents, failable); 372 ITUtil.checkForIncidentReport(driver.getPageSource(), "Krad Login", failable, "Krad Login failure"); 373 } 374 375 /** 376 * Logs into the Rice portal using the KNS Style Login Page. 377 * @param driver 378 * @param userName 379 * @param failable 380 * @throws InterruptedException 381 */ 382 public static void login(WebDriver driver, String userName, Failable failable) throws InterruptedException { 383 driver.findElement(By.name("__login_user")).clear(); 384 driver.findElement(By.name("__login_user")).sendKeys(userName); 385 driver.findElement(By.cssSelector("input[type=\"submit\"]")).click(); 386 Thread.sleep(1000); 387 String contents = driver.getPageSource(); 388 ITUtil.failOnInvalidUserName(userName, contents, failable); 389 ITUtil.checkForIncidentReport(driver.getPageSource(), "KNS Login", failable, "KNS Login failure"); 390 } 391 392 public static void loginKradOrKns(WebDriver driver, String user, Failable failable) throws InterruptedException {// login via either KRAD or KNS login page 393 if ("true".equalsIgnoreCase(System.getProperty(ITUtil.REMOTE_AUTOLOGIN_PROPERTY, "true"))) { 394 if (isKradLogin()){ 395 WebDriverUtil.kradLogin(driver, user, failable); 396 } else { 397 WebDriverUtil.login(driver, user, failable); 398 } 399 } 400 } 401 402 /** 403 * Use the KRAD Login Screen or the old KNS Login Screen 404 */ 405 public static boolean isKradLogin(){ 406 // check system property, default to KRAD 407 String loginUif = System.getProperty(REMOTE_LOGIN_UIF); 408 if (loginUif == null) { 409 loginUif = ITUtil.REMOTE_UIF_KRAD; 410 } 411 412 return (ITUtil.REMOTE_UIF_KRAD.equalsIgnoreCase(loginUif)); 413 } 414 415 protected static void selectFrameSafe(WebDriver driver, String locator) { 416 try { 417 driver.switchTo().frame(locator); 418 } catch (NoSuchFrameException nsfe) { 419 // don't fail 420 } 421 } 422 423 /** 424 * Wait for the given amount of seconds, for the given by, using the given driver. The message is displayed if the 425 * by cannot be found. No action is performed on the by, so it is possible that the by found is not visible or enabled. 426 * 427 * @param driver WebDriver 428 * @param waitSeconds int 429 * @param by By 430 * @param message String 431 * @throws InterruptedException 432 */ 433 public static void waitFor(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException { 434 driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS); 435 Thread.sleep(1000); 436 driver.findElement(by); // NOTICE just the find, no action, so by is found, but might not be visible or enabled. 437 driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS); 438 } 439 440 /** 441 * Wait for the given amount of seconds, for the given by, using the given driver. The message is displayed if the 442 * by cannot be found. No action is performed on the by, so it is possible that the by found is not visible or enabled. 443 * 444 * @param driver WebDriver 445 * @param waitSeconds int 446 * @param by By 447 * @param message String 448 * @throws InterruptedException 449 */ 450 public static void waitFors(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException { 451 driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS); 452 Thread.sleep(1000); 453 driver.findElements(by); // NOTICE just the find, no action, so by is found, but might not be visible or enabled. 454 driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS); 455 } 456 457 458 public static void failOnMatchedJira(String contents, Failable failable) { 459 JiraAwareFailureUtil.failOnMatchedJira(contents, failable); 460 } 461 462 private static void failWithReportInfoForKim(String contents, String linkLocator, String message) { 463 final String kimIncidentReport = extractIncidentReportKim(contents, linkLocator, message); 464 SeleneseTestBase.fail(kimIncidentReport); 465 } 466 467 private static String extractIncidentReportKim(String contents, String linkLocator, String message) { 468 String chunk = contents.substring(contents.indexOf("id=\"headerarea\""), contents.lastIndexOf("</div>") ); 469 String docIdPre = "type=\"hidden\" value=\""; 470 String docId = chunk.substring(chunk.indexOf(docIdPre) + docIdPre.length(), chunk.indexOf("\" name=\"documentId\"")); 471 472 String stackTrace = chunk.substring(chunk.lastIndexOf("name=\"displayMessage\""), chunk.length()); 473 String stackTracePre = "value=\""; 474 stackTrace = stackTrace.substring(stackTrace.indexOf(stackTracePre) + stackTracePre.length(), stackTrace.indexOf("name=\"stackTrace\"") - 2); 475 476 return "\nIncident report "+ message+ " navigating to "+ linkLocator + " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim().replace(" at ",""); 477 } 478 479 private static void processIncidentReport(String contents, String linkLocator, Failable failable, String message) { 480 failOnMatchedJira(contents, failable); 481 482 if (contents.indexOf("Incident Feedback") > -1) { 483 failWithReportInfo(contents, linkLocator, message); 484 } 485 486 if (contents.indexOf("Incident Report") > -1) { // KIM incident report 487 failWithReportInfoForKim(contents, linkLocator, message); 488 } 489 490 SeleneseTestBase.fail("\nIncident report detected " + message + "\n Unable to parse out details for the contents that triggered exception: " + deLinespace( 491 contents)); 492 } 493 494 private static void failWithReportInfo(String contents, String linkLocator, String message) { 495 final String incidentReportInformation = extractIncidentReportInfo(contents, linkLocator, message); 496 SeleneseTestBase.fail(incidentReportInformation); 497 } 498 499 private static String extractIncidentReportInfo(String contents, String linkLocator, String message) { 500 String chunk = contents.substring(contents.indexOf("Incident Feedback"), contents.lastIndexOf("</div>") ); 501 String docId = chunk.substring(chunk.lastIndexOf("Document Id"), chunk.indexOf("View Id")); 502 docId = docId.substring(0, docId.indexOf("</span>")); 503 docId = docId.substring(docId.lastIndexOf(">") + 2, docId.length()); 504 505 String viewId = chunk.substring(chunk.lastIndexOf("View Id"), chunk.indexOf("Error Message")); 506 viewId = viewId.substring(0, viewId.indexOf("</span>")); 507 viewId = viewId.substring(viewId.lastIndexOf(">") + 2, viewId.length()); 508 509 String stackTrace = chunk.substring(chunk.lastIndexOf("(only in dev mode)"), chunk.length()); 510 stackTrace = stackTrace.substring(stackTrace.indexOf("<span id=\"") + 3, stackTrace.length()); 511 stackTrace = stackTrace.substring(stackTrace.indexOf("\">") + 2, stackTrace.indexOf("</span>")); 512 513 return "\nIncident report "+ message+ " navigating to "+ linkLocator+ " : View Id: "+ viewId.trim()+ " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim().replace(" at ", ""); 514 } 515 516 public static String deLinespace(String contents) { 517 while (contents.contains("\n\n")) { 518 contents = contents.replaceAll("\n\n", "\n"); 519 } 520 521 return contents; 522 } 523 }