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
176 if (!System.getProperty(SauceLabsWebDriverHelper.SAUCE_BROWSER_PROPERTY,"ff").equals("opera")) {
177 driver.manage().window().maximize();
178 }
179
180 // TODO Got into the situation where the first url doesn't expect server, but all others do. Readdress once
181 // the NavIT WDIT conversion has been completed.
182 if (!url.startsWith("http")) {
183 url = ITUtil.getBaseUrlString() + url;
184 }
185
186 driver.get(url);
187 driver.manage().timeouts().implicitlyWait(DEFAULT_IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
188 return driver;
189 }
190
191 /**
192 *
193 * @param passed
194 * @param sessionId
195 * @param testParam
196 * @param userParam
197 * @throws Exception
198 */
199 public static void tearDown(boolean passed, String sessionId, String testParam, String userParam) throws Exception {
200
201 if (System.getProperty(SauceLabsWebDriverHelper.SAUCE_PROPERTY) != null) {
202 SauceLabsWebDriverHelper.tearDown(passed, sessionId, System.getProperty(SauceLabsWebDriverHelper.SAUCE_USER_PROPERTY),
203 System.getProperty(SauceLabsWebDriverHelper.SAUCE_KEY_PROPERTY));
204 }
205
206 if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) {
207 ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test="
208 + testParam + "&user=" + userParam));
209 }
210 }
211
212 /**
213 *
214 * @param testParam
215 * @return
216 */
217 public static String determineUser(String testParam) {
218 String user = null;
219
220 if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY) != null) {
221 return System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY);
222 } else if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) { // deprecated
223 String userResponse = ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(
224 WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" + testParam.trim()));
225 return userResponse.substring(userResponse.lastIndexOf(":") + 2, userResponse.lastIndexOf("\""));
226 }
227
228 return user;
229 }
230
231 /***
232 * @link ITUtil#checkForIncidentReport
233 * @param driver
234 * @param locator
235 * @param message
236 */
237 public static void checkForIncidentReport(WebDriver driver, String locator, Failable failable,
238 String message) {
239 ITUtil.checkForIncidentReport(driver.getPageSource(), locator, failable, message);
240 }
241
242 /**
243 * @link http://code.google.com/p/chromedriver/downloads/list
244 * @link #REMOTE_PUBLIC_CHROME
245 * @link #WEBDRIVER_CHROME_DRIVER
246 * @link ITUtil#HUB_DRIVER_PROPERTY
247 * @return chromeDriverService
248 */
249 public static ChromeDriverService chromeDriverCreateCheck() {
250 String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY);
251 // TODO can the saucelabs driver stuff be leveraged here?
252 if (driverParam != null && "chrome".equals(driverParam.toLowerCase())) {
253 if (System.getProperty(WEBDRIVER_CHROME_DRIVER) == null) {
254 if (System.getProperty(REMOTE_PUBLIC_CHROME) != null) {
255 System.setProperty(WEBDRIVER_CHROME_DRIVER, System.getProperty(REMOTE_PUBLIC_CHROME));
256 }
257 }
258 try {
259 ChromeDriverService chromeDriverService = new ChromeDriverService.Builder()
260 .usingDriverExecutable(new File(System.getProperty(WEBDRIVER_CHROME_DRIVER)))
261 .usingAnyFreePort()
262 .build();
263 return chromeDriverService;
264 } catch (Throwable t) {
265 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) ;
266 }
267 }
268 return null;
269 }
270
271 public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message, Throwable t) {
272 if (jGrowlEnabled) { // check if jGrowl is enabled to skip over the stack trace extraction if it is not.
273 jGrowl(driver, jGrowlHeader, sticky, message + " " + t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t));
274 }
275 }
276
277 public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message) {
278 if (jGrowlEnabled) {
279 try {
280 String javascript="jQuery.jGrowl('" + message + "' , {sticky: " + sticky + ", header : '" + jGrowlHeader + "'});";
281 ((JavascriptExecutor) driver).executeScript(javascript);
282 } catch (Throwable t) {
283 jGrowlException(t);
284 }
285 }
286 }
287
288 public static void jGrowlException(Throwable t) {
289 String failMessage = t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t);
290 System.out.println("jGrowl failure " + failMessage);
291 if (JGROWL_ERROR_FAILURE) {
292 SeleneseTestBase.fail(failMessage);
293 }
294 }
295
296 public static void highlightElement(WebDriver webDriver, WebElement webElement) {
297 if (jsHighlightEnabled) {
298 try {
299 JavascriptExecutor js = (JavascriptExecutor) webDriver;
300 js.executeScript("element = arguments[0];\n"
301 + "originalStyle = element.getAttribute('style');\n"
302 + "element.setAttribute('style', originalStyle + \"; background: "
303 + JS_HIGHLIGHT_BACKGROUND + "; border: 2px solid " + JS_HIGHLIGHT_BOARDER + ";\");\n"
304 + "setTimeout(function(){\n"
305 + " element.setAttribute('style', originalStyle);\n"
306 + "}, " + JS_HIGHLIGHT_MS + ");", webElement);
307 } catch (Throwable t) {
308 System.out.println("Throwable during javascript highlight element");
309 t.printStackTrace();
310 }
311 }
312 }
313
314
315 /**
316 * remote.public.driver set to chrome or firefox (null assumes firefox)
317 * if remote.public.hub is set a RemoteWebDriver is created (Selenium Grid)
318 * if proxy.host is set, the web driver is setup to use a proxy
319 * @return WebDriver or null if unable to create
320 */
321 public static WebDriver getWebDriver() {
322 String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY);
323 String hubParam = System.getProperty(ITUtil.HUB_PROPERTY);
324 String proxyParam = System.getProperty(PROXY_HOST_PROPERTY);
325
326 // setup proxy if specified as VM Arg
327 DesiredCapabilities capabilities = new DesiredCapabilities();
328 WebDriver webDriver = null;
329 if (StringUtils.isNotEmpty(proxyParam)) {
330 capabilities.setCapability(CapabilityType.PROXY, new Proxy().setHttpProxy(proxyParam));
331 }
332
333 if (hubParam == null) {
334 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
335 FirefoxProfile profile = new FirefoxProfile();
336 profile.setEnableNativeEvents(false);
337 capabilities.setCapability(FirefoxDriver.PROFILE, profile);
338 return new FirefoxDriver(capabilities);
339 } else if ("chrome".equalsIgnoreCase(driverParam)) {
340 return new ChromeDriver(capabilities);
341 } else if ("safari".equals(driverParam)) {
342 System.out.println("SafariDriver probably won't work, if it does please contact Erik M.");
343 return new SafariDriver(capabilities);
344 }
345 } else {
346 try {
347 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
348 return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.firefox());
349 } else if ("chrome".equalsIgnoreCase(driverParam)) {
350 return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.chrome());
351 }
352 } catch (MalformedURLException mue) {
353 System.out.println(ITUtil.getHubUrlString() + " " + mue.getMessage());
354 mue.printStackTrace();
355 }
356 }
357 return null;
358 }
359
360 /**
361 * Logs in using the KRAD Login Page
362 * If the JVM arg remote.autologin is set, auto login as admin will not be done.
363 * @param driver
364 * @param userName
365 * @param failable
366 * @throws InterruptedException
367 */
368 public static void kradLogin(WebDriver driver, String userName, Failable failable) throws InterruptedException {
369 driver.findElement(By.name("login_user")).clear();
370 driver.findElement(By.name("login_user")).sendKeys(userName);
371 driver.findElement(By.id("Rice-LoginButton")).click();
372 Thread.sleep(1000);
373 String contents = driver.getPageSource();
374 ITUtil.failOnInvalidUserName(userName, contents, failable);
375 ITUtil.checkForIncidentReport(driver.getPageSource(), "Krad Login", failable, "Krad Login failure");
376 }
377
378 /**
379 * Logs into the Rice portal using the KNS Style Login Page.
380 * @param driver
381 * @param userName
382 * @param failable
383 * @throws InterruptedException
384 */
385 public static void login(WebDriver driver, String userName, Failable failable) throws InterruptedException {
386 driver.findElement(By.name("__login_user")).clear();
387 driver.findElement(By.name("__login_user")).sendKeys(userName);
388 driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
389 Thread.sleep(1000);
390 String contents = driver.getPageSource();
391 ITUtil.failOnInvalidUserName(userName, contents, failable);
392 ITUtil.checkForIncidentReport(driver.getPageSource(), "KNS Login", failable, "KNS Login failure");
393 }
394
395 public static void loginKradOrKns(WebDriver driver, String user, Failable failable) throws InterruptedException {// login via either KRAD or KNS login page
396 if ("true".equalsIgnoreCase(System.getProperty(ITUtil.REMOTE_AUTOLOGIN_PROPERTY, "true"))) {
397 if (isKradLogin()){
398 WebDriverUtil.kradLogin(driver, user, failable);
399 } else {
400 WebDriverUtil.login(driver, user, failable);
401 }
402 }
403 }
404
405 /**
406 * Use the KRAD Login Screen or the old KNS Login Screen
407 */
408 public static boolean isKradLogin(){
409 // check system property, default to KRAD
410 String loginUif = System.getProperty(REMOTE_LOGIN_UIF);
411 if (loginUif == null) {
412 loginUif = ITUtil.REMOTE_UIF_KRAD;
413 }
414
415 return (ITUtil.REMOTE_UIF_KRAD.equalsIgnoreCase(loginUif));
416 }
417
418 protected static void selectFrameSafe(WebDriver driver, String locator) {
419 try {
420 driver.switchTo().frame(locator);
421 } catch (NoSuchFrameException nsfe) {
422 // don't fail
423 }
424 }
425
426 /**
427 * Wait for the given amount of seconds, for the given by, using the given driver. The message is displayed if the
428 * by cannot be found. No action is performed on the by, so it is possible that the by found is not visible or enabled.
429 *
430 * @param driver WebDriver
431 * @param waitSeconds int
432 * @param by By
433 * @param message String
434 * @throws InterruptedException
435 */
436 public static void waitFor(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
437 driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
438 Thread.sleep(1000);
439 driver.findElement(by); // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
440 driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
441 }
442
443 /**
444 * Wait for the given amount of seconds, for the given by, using the given driver. The message is displayed if the
445 * by cannot be found. No action is performed on the by, so it is possible that the by found is not visible or enabled.
446 *
447 * @param driver WebDriver
448 * @param waitSeconds int
449 * @param by By
450 * @param message String
451 * @throws InterruptedException
452 */
453 public static void waitFors(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
454 driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
455 Thread.sleep(1000);
456 driver.findElements(by); // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
457 driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
458 }
459
460
461 public static void failOnMatchedJira(String contents, Failable failable) {
462 JiraAwareFailureUtil.failOnMatchedJira(contents, failable);
463 }
464
465 private static void failWithReportInfoForKim(String contents, String linkLocator, String message) {
466 final String kimIncidentReport = extractIncidentReportKim(contents, linkLocator, message);
467 SeleneseTestBase.fail(kimIncidentReport);
468 }
469
470 private static String extractIncidentReportKim(String contents, String linkLocator, String message) {
471 String chunk = contents.substring(contents.indexOf("id=\"headerarea\""), contents.lastIndexOf("</div>") );
472 String docIdPre = "type=\"hidden\" value=\"";
473 String docId = chunk.substring(chunk.indexOf(docIdPre) + docIdPre.length(), chunk.indexOf("\" name=\"documentId\""));
474
475 String stackTrace = chunk.substring(chunk.lastIndexOf("name=\"displayMessage\""), chunk.length());
476 String stackTracePre = "value=\"";
477 stackTrace = stackTrace.substring(stackTrace.indexOf(stackTracePre) + stackTracePre.length(), stackTrace.indexOf("name=\"stackTrace\"") - 2);
478
479 return "\nIncident report "+ message+ " navigating to "+ linkLocator + " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim().replace(" at ","");
480 }
481
482 private static void processIncidentReport(String contents, String linkLocator, Failable failable, String message) {
483 failOnMatchedJira(contents, failable);
484
485 if (contents.indexOf("Incident Feedback") > -1) {
486 failWithReportInfo(contents, linkLocator, message);
487 }
488
489 if (contents.indexOf("Incident Report") > -1) { // KIM incident report
490 failWithReportInfoForKim(contents, linkLocator, message);
491 }
492
493 SeleneseTestBase.fail("\nIncident report detected " + message + "\n Unable to parse out details for the contents that triggered exception: " + deLinespace(
494 contents));
495 }
496
497 private static void failWithReportInfo(String contents, String linkLocator, String message) {
498 final String incidentReportInformation = extractIncidentReportInfo(contents, linkLocator, message);
499 SeleneseTestBase.fail(incidentReportInformation);
500 }
501
502 private static String extractIncidentReportInfo(String contents, String linkLocator, String message) {
503 String chunk = contents.substring(contents.indexOf("Incident Feedback"), contents.lastIndexOf("</div>") );
504 String docId = chunk.substring(chunk.lastIndexOf("Document Id"), chunk.indexOf("View Id"));
505 docId = docId.substring(0, docId.indexOf("</span>"));
506 docId = docId.substring(docId.lastIndexOf(">") + 2, docId.length());
507
508 String viewId = chunk.substring(chunk.lastIndexOf("View Id"), chunk.indexOf("Error Message"));
509 viewId = viewId.substring(0, viewId.indexOf("</span>"));
510 viewId = viewId.substring(viewId.lastIndexOf(">") + 2, viewId.length());
511
512 String stackTrace = chunk.substring(chunk.lastIndexOf("(only in dev mode)"), chunk.length());
513 stackTrace = stackTrace.substring(stackTrace.indexOf("<span id=\"") + 3, stackTrace.length());
514 stackTrace = stackTrace.substring(stackTrace.indexOf("\">") + 2, stackTrace.indexOf("</span>"));
515
516 return "\nIncident report "+ message+ " navigating to "+ linkLocator+ " : View Id: "+ viewId.trim()+ " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim().replace(" at ", "");
517 }
518
519 public static String deLinespace(String contents) {
520 while (contents.contains("\n\n")) {
521 contents = contents.replaceAll("\n\n", "\n");
522 }
523
524 return contents;
525 }
526 }