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.BufferedReader;
038    import java.io.File;
039    import java.io.InputStream;
040    import java.io.InputStreamReader;
041    import java.net.MalformedURLException;
042    import java.net.URL;
043    import java.util.LinkedList;
044    import java.util.List;
045    import java.util.concurrent.TimeUnit;
046    
047    /**
048     * The goal of the WebDriverUtil class is to invert the dependencies on WebDriver from WebDriverLegacyITBase for reuse
049     * without having to extend WebDriverLegacyITBase.  For the first example see waitFor
050     *
051     * @see WebDriverLegacyITBase
052     * @author Kuali Rice Team (rice.collab@kuali.org)
053     */
054    public class WebDriverUtil {
055    
056        public static boolean jGrowlEnabled = false;
057    
058        public static boolean jsHighlightEnabled = false;
059    
060        /**
061         * TODO apparent dup WebDriverITBase.DEFAULT_WAIT_SEC
062         * TODO parametrize for JVM Arg
063         * 30 Seconds
064         */
065        public static int DEFAULT_IMPLICIT_WAIT_TIME = 30;
066    
067        /**
068         * false
069         * TODO upgrade to config via JVM param.
070         */
071        public static final boolean JGROWL_ERROR_FAILURE = false;
072    
073        /**
074         * green
075         */
076        public static final String JS_HIGHLIGHT_BACKGROUND = "#66FF33";
077    
078        /**
079         * green
080         */
081        public static final String JS_HIGHLIGHT_BOARDER = "#66FF33";
082    
083        /**
084         * 400 milliseconds
085         */
086        public static final int JS_HIGHLIGHT_MS = 400;
087    
088        /**
089         * -Dremote.driver.highlight.ms=
090         */
091        public static final String JS_HIGHLIGHT_MS_PROPERTY = "remote.driver.highlight.ms";
092    
093        /**
094         * -Dremote.driver.highlight=true to enable highlighting of elements as selenium runs
095         */
096        public static final String JS_HIGHLIGHT_PROPERTY = "remote.driver.highlight";
097    
098        /**
099         * -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    }