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