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