View Javadoc

1   /*
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package edu.samplu.common;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang3.exception.ExceptionUtils;
20  import org.openqa.selenium.By;
21  import org.openqa.selenium.JavascriptExecutor;
22  import org.openqa.selenium.NoSuchFrameException;
23  import org.openqa.selenium.Proxy;
24  import org.openqa.selenium.WebDriver;
25  import org.openqa.selenium.WebElement;
26  import org.openqa.selenium.chrome.ChromeDriver;
27  import org.openqa.selenium.chrome.ChromeDriverService;
28  import org.openqa.selenium.firefox.FirefoxDriver;
29  import org.openqa.selenium.firefox.FirefoxProfile;
30  import org.openqa.selenium.remote.CapabilityType;
31  import org.openqa.selenium.remote.DesiredCapabilities;
32  import org.openqa.selenium.remote.RemoteWebDriver;
33  import org.openqa.selenium.safari.SafariDriver;
34  
35  import com.thoughtworks.selenium.SeleneseTestBase;
36  
37  import java.io.BufferedReader;
38  import java.io.File;
39  import java.io.InputStream;
40  import java.io.InputStreamReader;
41  import java.net.MalformedURLException;
42  import java.net.URL;
43  import java.util.LinkedList;
44  import java.util.List;
45  import java.util.concurrent.TimeUnit;
46  
47  /**
48   * The goal of the WebDriverUtil class is to invert the dependencies on WebDriver from WebDriverLegacyITBase for reuse
49   * without having to extend WebDriverLegacyITBase.  For the first example see waitFor
50   *
51   * @see WebDriverLegacyITBase
52   * @author Kuali Rice Team (rice.collab@kuali.org)
53   */
54  public class WebDriverUtil {
55  
56      public static boolean jGrowlEnabled = false;
57  
58      public static boolean jsHighlightEnabled = false;
59  
60      /**
61       * TODO apparent dup WebDriverITBase.DEFAULT_WAIT_SEC
62       * TODO parametrize for JVM Arg
63       * 30 Seconds
64       */
65      public static int DEFAULT_IMPLICIT_WAIT_TIME = 30;
66  
67      /**
68       * false
69       * TODO upgrade to config via JVM param.
70       */
71      public static final boolean JGROWL_ERROR_FAILURE = false;
72  
73      /**
74       * green
75       */
76      public static final String JS_HIGHLIGHT_BACKGROUND = "#66FF33";
77  
78      /**
79       * green
80       */
81      public static final String JS_HIGHLIGHT_BOARDER = "#66FF33";
82  
83      /**
84       * 400 milliseconds
85       */
86      public static final int JS_HIGHLIGHT_MS = 400;
87  
88      /**
89       * -Dremote.driver.highlight.ms=
90       */
91      public static final String JS_HIGHLIGHT_MS_PROPERTY = "remote.driver.highlight.ms";
92  
93      /**
94       * -Dremote.driver.highlight=true to enable highlighting of elements as selenium runs
95       */
96      public static final String JS_HIGHLIGHT_PROPERTY = "remote.driver.highlight";
97  
98      /**
99       * -Dremote.driver.highlight.input=
100      */
101     public static final String JS_HIGHLIGHT_INPUT_PROPERTY = "remote.driver.highlight.input";
102 
103     /**
104      * TODO introduce SHORT_IMPLICIT_WAIT_TIME with param in WebDriverITBase
105      * TODO parametrize for JVM Arg
106      * 1 Second
107      */
108     public static int SHORT_IMPLICIT_WAIT_TIME = 1;
109 
110     /**
111      * Set -Dremote.driver.saucelabs for running on saucelabs
112      * @link https://wiki.kuali.org/display/KULRICE/How+To+Run+a+Selenium+Test for patch required
113      */
114     public static final String REMOTE_DRIVER_SAUCELABS_PROPERTY = "remote.driver.saucelabs";
115 
116     /**
117      * Selenium's webdriver.chrome.driver parameter, you can set -Dwebdriver.chrome.driver= or Rice's REMOTE_PUBLIC_CHROME
118      */
119     public static final String WEBDRIVER_CHROME_DRIVER = "webdriver.chrome.driver";
120 
121     /**
122      * Set -Dremote.jgrowl.enabled=
123      */
124     public static final String REMOTE_JGROWL_ENABLED = "remote.jgrowl.enabled";
125 
126     /**
127      * Set -Dremote.login.uif=KNS to use old login screen.  Default value = KRAD
128      */
129     public static final String REMOTE_LOGIN_UIF = "remote.login.uif";
130 
131     /**
132      * Set -Dremote.public.chrome= or WEBDRIVER_CHROME_DRIVER
133      */
134     public static final String REMOTE_PUBLIC_CHROME = "remote.public.chrome";
135 
136     /**
137      * 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
138      * longer than any other.  120 Seconds.
139      * TODO parametrize for JVM Arg
140      */
141     public static final int SETUP_URL_LOAD_WAIT_SECONDS = 120;
142 
143     /**
144      * local proxy used for running tests thru jmeter.
145      * Include host name and port number. Example: localhost:7777
146      */
147     public static final String PROXY_HOST_PROPERTY = "remote.public.proxy";
148 
149     /**
150      * Setup the WebDriver test, login, and load the given web page
151      *
152      * @param username
153      * @param url
154      * @return driver
155      * @throws Exception
156      */
157     public static WebDriver setUp(String username, String url) throws Exception {
158         return setUp(username, url, null, null);
159     }
160 
161     /**
162      * Setup the WebDriver test, login, and load the given web page
163      *
164      * @param username
165      * @param url
166      * @param className
167      * @param testName
168      * @return driver
169      * @throws Exception
170      */
171     public static WebDriver setUp(String username, String url, String className, String testName) throws Exception {
172         if ("true".equals(System.getProperty(REMOTE_JGROWL_ENABLED, "false"))) {
173             jGrowlEnabled = true;
174         }
175 
176         if ("true".equals(System.getProperty(JS_HIGHLIGHT_PROPERTY, "false"))) {
177             jsHighlightEnabled = true;
178             if (System.getProperty(JS_HIGHLIGHT_INPUT_PROPERTY) != null) {
179                 InputStream in = WebDriverUtil.class.getResourceAsStream(System.getProperty(JS_HIGHLIGHT_INPUT_PROPERTY));
180                 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
181                 String line = null;
182                 List<String> lines = new LinkedList<String>();
183                 while ((line = reader.readLine()) != null) {
184                     lines.add(line);
185                 }
186             }
187         }
188 
189         WebDriver driver = null;
190         if (System.getProperty(REMOTE_DRIVER_SAUCELABS_PROPERTY) == null) {
191             driver = getWebDriver();
192         } else {
193             SauceLabsWebDriverHelper saucelabs = new SauceLabsWebDriverHelper();
194             saucelabs.setUp(className, testName);
195             driver = saucelabs.getDriver();
196         }
197 
198         driver.manage().timeouts().implicitlyWait(SETUP_URL_LOAD_WAIT_SECONDS, TimeUnit.SECONDS);
199 
200         if (!System.getProperty(SauceLabsWebDriverHelper.SAUCE_BROWSER_PROPERTY,"ff").equals("opera")) {
201             driver.manage().window().maximize();
202         }
203 
204         // TODO Got into the situation where the first url doesn't expect server, but all others do.  Readdress once
205         // the NavIT WDIT conversion has been completed.
206         if (!url.startsWith("http")) {
207             url = ITUtil.getBaseUrlString() + url;
208         }
209 
210         driver.get(url);
211         driver.manage().timeouts().implicitlyWait(DEFAULT_IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
212         return driver;
213     }
214 
215     /**
216      *
217      * @param passed
218      * @param sessionId
219      * @param testParam
220      * @param userParam
221      * @throws Exception
222      */
223     public static void tearDown(boolean passed, String sessionId, String testParam, String userParam) throws Exception {
224 
225         if (System.getProperty(SauceLabsWebDriverHelper.SAUCE_PROPERTY) != null) {
226             SauceLabsWebDriverHelper.tearDown(passed, sessionId, System.getProperty(SauceLabsWebDriverHelper.SAUCE_USER_PROPERTY),
227                     System.getProperty(SauceLabsWebDriverHelper.SAUCE_KEY_PROPERTY));
228         }
229 
230         if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) {
231             ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test="
232                     + testParam + "&user=" + userParam));
233         }
234     }
235 
236     /**
237      *
238      * @param testParam
239      * @return
240      */
241     public static String determineUser(String testParam) {
242         String user = null;
243 
244         if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY) != null) {
245             return System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USER_PROPERTY);
246         } else if (System.getProperty(WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) { // deprecated
247             String userResponse = ITUtil.getHTML(ITUtil.prettyHttp(System.getProperty(
248                     WebDriverLegacyITBase.REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" + testParam.trim()));
249             return userResponse.substring(userResponse.lastIndexOf(":") + 2, userResponse.lastIndexOf("\""));
250         }
251 
252         return user;
253     }
254 
255     /***
256      * @link ITUtil#checkForIncidentReport
257      * @param driver
258      * @param locator
259      * @param message
260      */
261     public static void checkForIncidentReport(WebDriver driver, String locator, Failable failable,
262             String message) {
263         ITUtil.checkForIncidentReport(driver.getPageSource(), locator, failable, message);
264     }
265 
266     /**
267      * @link http://code.google.com/p/chromedriver/downloads/list
268      * @link #REMOTE_PUBLIC_CHROME
269      * @link #WEBDRIVER_CHROME_DRIVER
270      * @link ITUtil#HUB_DRIVER_PROPERTY
271      * @return chromeDriverService
272      */
273     public static ChromeDriverService chromeDriverCreateCheck() {
274         String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY);
275         // TODO can the saucelabs driver stuff be leveraged here?
276         if (driverParam != null && "chrome".equals(driverParam.toLowerCase())) {
277             if (System.getProperty(WEBDRIVER_CHROME_DRIVER) == null) {
278                 if (System.getProperty(REMOTE_PUBLIC_CHROME) != null) {
279                     System.setProperty(WEBDRIVER_CHROME_DRIVER, System.getProperty(REMOTE_PUBLIC_CHROME));
280                 }
281             }
282             try {
283                 ChromeDriverService chromeDriverService = new ChromeDriverService.Builder()
284                         .usingDriverExecutable(new File(System.getProperty(WEBDRIVER_CHROME_DRIVER)))
285                         .usingAnyFreePort()
286                         .build();
287                 return chromeDriverService;
288             } catch (Throwable t) {
289                 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)   ;
290             }
291         }
292         return null;
293     }
294 
295     public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message, Throwable t) {
296         if (jGrowlEnabled) { // check if jGrowl is enabled to skip over the stack trace extraction if it is not.
297             jGrowl(driver, jGrowlHeader, sticky, message + " " + t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t));
298         }
299     }
300 
301     public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message) {
302         if (jGrowlEnabled) {
303             try {
304                 String javascript="jQuery.jGrowl('" + message + "' , {sticky: " + sticky + ", header : '" + jGrowlHeader + "'});";
305                 ((JavascriptExecutor) driver).executeScript(javascript);
306             } catch (Throwable t) {
307                 jGrowlException(t);
308             }
309         }
310     }
311 
312     public static void jGrowlException(Throwable t) {
313         String failMessage = t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t);
314         System.out.println("jGrowl failure " + failMessage);
315         if (JGROWL_ERROR_FAILURE) {
316             SeleneseTestBase.fail(failMessage);
317         }
318     }
319 
320     public static void highlightElement(WebDriver webDriver, WebElement webElement) {
321         if (jsHighlightEnabled) {
322             try {
323 //                System.out.println("highlighting " + webElement.toString() + " on url " + webDriver.getCurrentUrl());
324                 JavascriptExecutor js = (JavascriptExecutor) webDriver;
325                 js.executeScript("element = arguments[0];\n"
326                         + "originalStyle = element.getAttribute('style');\n"
327                         + "element.setAttribute('style', originalStyle + \"; background: "
328                         + JS_HIGHLIGHT_BACKGROUND + "; border: 2px solid " + JS_HIGHLIGHT_BOARDER + ";\");\n"
329                         + "setTimeout(function(){\n"
330                         + "    element.setAttribute('style', originalStyle);\n"
331                         + "}, " + System.getProperty(JS_HIGHLIGHT_MS_PROPERTY, JS_HIGHLIGHT_MS + "") + ");", webElement);
332             } catch (Throwable t) {
333                 System.out.println("Throwable during javascript highlight element");
334                 t.printStackTrace();
335             }
336         }
337     }
338 
339 
340     /**
341      * remote.public.driver set to chrome or firefox (null assumes firefox)
342      * if remote.public.hub is set a RemoteWebDriver is created (Selenium Grid)
343      * if proxy.host is set, the web driver is setup to use a proxy
344      * @return WebDriver or null if unable to create
345      */
346     public static WebDriver getWebDriver() {
347         String driverParam = System.getProperty(ITUtil.HUB_DRIVER_PROPERTY);
348         String hubParam = System.getProperty(ITUtil.HUB_PROPERTY);
349         String proxyParam = System.getProperty(PROXY_HOST_PROPERTY);
350 
351         // setup proxy if specified as VM Arg
352         DesiredCapabilities capabilities = new DesiredCapabilities();
353         WebDriver webDriver = null;
354         if (StringUtils.isNotEmpty(proxyParam)) {
355             capabilities.setCapability(CapabilityType.PROXY, new Proxy().setHttpProxy(proxyParam));
356         }
357 
358         if (hubParam == null) {
359             if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
360                 FirefoxProfile profile = new FirefoxProfile();
361                 profile.setEnableNativeEvents(false);
362                 capabilities.setCapability(FirefoxDriver.PROFILE, profile);
363                 return new FirefoxDriver(capabilities);
364             } else if ("chrome".equalsIgnoreCase(driverParam)) {
365                 return new ChromeDriver(capabilities);
366             } else if ("safari".equals(driverParam)) {
367                 System.out.println("SafariDriver probably won't work, if it does please contact Erik M.");
368                 return new SafariDriver(capabilities);
369             }
370         } else {
371             try {
372                 if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
373                     return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.firefox());
374                 } else if ("chrome".equalsIgnoreCase(driverParam)) {
375                     return new RemoteWebDriver(new URL(ITUtil.getHubUrlString()), DesiredCapabilities.chrome());
376                 }
377             } catch (MalformedURLException mue) {
378                 System.out.println(ITUtil.getHubUrlString() + " " + mue.getMessage());
379                 mue.printStackTrace();
380             }
381         }
382         return null;
383     }
384 
385     /**
386      * Logs in using the KRAD Login Page
387      * If the JVM arg remote.autologin is set, auto login as admin will not be done.
388      * @param driver
389      * @param userName
390      * @param failable
391      * @throws InterruptedException
392      */
393     public static void kradLogin(WebDriver driver, String userName, Failable failable) throws InterruptedException {
394             driver.findElement(By.name("login_user")).clear();
395             driver.findElement(By.name("login_user")).sendKeys(userName);
396             driver.findElement(By.id("Rice-LoginButton")).click();
397             Thread.sleep(1000);
398             String contents = driver.getPageSource();
399             ITUtil.failOnInvalidUserName(userName, contents, failable);
400             ITUtil.checkForIncidentReport(driver.getPageSource(), "Krad Login", failable, "Krad Login failure");
401     }
402 
403     /**
404      * Logs into the Rice portal using the KNS Style Login Page.
405      * @param driver
406      * @param userName
407      * @param failable
408      * @throws InterruptedException
409      */
410     public static void login(WebDriver driver, String userName, Failable failable) throws InterruptedException {
411             driver.findElement(By.name("__login_user")).clear();
412             driver.findElement(By.name("__login_user")).sendKeys(userName);
413             driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
414             Thread.sleep(1000);
415             String contents = driver.getPageSource();
416             ITUtil.failOnInvalidUserName(userName, contents, failable);
417             ITUtil.checkForIncidentReport(driver.getPageSource(), "KNS Login", failable, "KNS Login failure");
418     }
419 
420     public static void loginKradOrKns(WebDriver driver, String user, Failable failable) throws InterruptedException {// login via either KRAD or KNS login page
421         if ("true".equalsIgnoreCase(System.getProperty(ITUtil.REMOTE_AUTOLOGIN_PROPERTY, "true"))) {
422             if (isKradLogin()){
423                 WebDriverUtil.kradLogin(driver, user, failable);
424             } else {
425                 WebDriverUtil.login(driver, user, failable);
426             }
427         }
428     }
429 
430     /**
431      * Use the KRAD Login Screen or the old KNS Login Screen
432      */
433     public static boolean isKradLogin(){
434         // check system property, default to KRAD
435         String loginUif = System.getProperty(REMOTE_LOGIN_UIF);
436         if (loginUif == null) {
437             loginUif = ITUtil.REMOTE_UIF_KRAD;
438         }
439 
440         return (ITUtil.REMOTE_UIF_KRAD.equalsIgnoreCase(loginUif));
441     }
442 
443     protected static void selectFrameSafe(WebDriver driver, String locator) {
444         try {
445             driver.switchTo().frame(locator);
446         } catch (NoSuchFrameException nsfe) {
447             // don't fail
448         }
449     }
450 
451     /**
452      * Wait for the given amount of seconds, for the given by, using the given driver.  The message is displayed if the
453      * by cannot be found.  No action is performed on the by, so it is possible that the by found is not visible or enabled.
454      *
455      * @param driver WebDriver
456      * @param waitSeconds int
457      * @param by By
458      * @param message String
459      * @throws InterruptedException
460      */
461     public static void waitFor(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
462         driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
463         Thread.sleep(1000);
464         driver.findElement(by);  // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
465         driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
466     }
467 
468     /**
469      * Wait for the given amount of seconds, for the given by, using the given driver.  The message is displayed if the
470      * by cannot be found.  No action is performed on the by, so it is possible that the by found is not visible or enabled.
471      *
472      * @param driver WebDriver
473      * @param waitSeconds int
474      * @param by By
475      * @param message String
476      * @throws InterruptedException
477      */
478     public static void waitFors(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
479         driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
480         Thread.sleep(1000);
481         driver.findElements(by);  // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
482         driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
483     }
484 
485 
486     public static void failOnMatchedJira(String contents, Failable failable) {
487         JiraAwareFailureUtil.failOnMatchedJira(contents, failable);
488     }
489     
490     private static void failWithReportInfoForKim(String contents, String linkLocator, String message) {
491         final String kimIncidentReport = extractIncidentReportKim(contents, linkLocator, message);
492         SeleneseTestBase.fail(kimIncidentReport);
493     }
494     
495     private static String extractIncidentReportKim(String contents, String linkLocator, String message) {
496         String chunk =  contents.substring(contents.indexOf("id=\"headerarea\""), contents.lastIndexOf("</div>") );
497         String docIdPre = "type=\"hidden\" value=\"";
498         String docId = chunk.substring(chunk.indexOf(docIdPre) + docIdPre.length(), chunk.indexOf("\" name=\"documentId\""));
499 
500         String stackTrace = chunk.substring(chunk.lastIndexOf("name=\"displayMessage\""), chunk.length());
501         String stackTracePre = "value=\"";
502         stackTrace = stackTrace.substring(stackTrace.indexOf(stackTracePre) + stackTracePre.length(), stackTrace.indexOf("name=\"stackTrace\"") - 2);
503 
504         return "\nIncident report "+ message+ " navigating to "+ linkLocator + " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim().replace(" at ","");
505     }
506     
507     private static void processIncidentReport(String contents, String linkLocator, Failable failable, String message) {
508         failOnMatchedJira(contents, failable);
509 
510         if (contents.indexOf("Incident Feedback") > -1) {
511             failWithReportInfo(contents, linkLocator, message);
512         }
513 
514         if (contents.indexOf("Incident Report") > -1) { // KIM incident report
515             failWithReportInfoForKim(contents, linkLocator, message);
516         }
517 
518         SeleneseTestBase.fail("\nIncident report detected " + message + "\n Unable to parse out details for the contents that triggered exception: " + deLinespace(
519                 contents));
520     }
521 
522     private static void failWithReportInfo(String contents, String linkLocator, String message) {
523         final String incidentReportInformation = extractIncidentReportInfo(contents, linkLocator, message);
524         SeleneseTestBase.fail(incidentReportInformation);
525     }
526     
527     private static String extractIncidentReportInfo(String contents, String linkLocator, String message) {
528         String chunk =  contents.substring(contents.indexOf("Incident Feedback"), contents.lastIndexOf("</div>") );
529         String docId = chunk.substring(chunk.lastIndexOf("Document Id"), chunk.indexOf("View Id"));
530         docId = docId.substring(0, docId.indexOf("</span>"));
531         docId = docId.substring(docId.lastIndexOf(">") + 2, docId.length());
532 
533         String viewId = chunk.substring(chunk.lastIndexOf("View Id"), chunk.indexOf("Error Message"));
534         viewId = viewId.substring(0, viewId.indexOf("</span>"));
535         viewId = viewId.substring(viewId.lastIndexOf(">") + 2, viewId.length());
536 
537         String stackTrace = chunk.substring(chunk.lastIndexOf("(only in dev mode)"), chunk.length());
538         stackTrace = stackTrace.substring(stackTrace.indexOf("<span id=\"") + 3, stackTrace.length());
539         stackTrace = stackTrace.substring(stackTrace.indexOf("\">") + 2, stackTrace.indexOf("</span>"));
540     
541         return "\nIncident report "+ message+ " navigating to "+ linkLocator+ " : View Id: "+ viewId.trim()+ " Doc Id: "+ docId.trim()+ "\nStackTrace: "+ stackTrace.trim().replace(" at ", "");
542     }
543     
544     public static String deLinespace(String contents) {
545         while (contents.contains("\n\n")) {
546             contents = contents.replaceAll("\n\n", "\n");
547         }
548         
549         return contents;
550     }
551 }