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