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 */
016package edu.samplu.common;
017
018import com.thoughtworks.selenium.SeleneseTestBase;
019import org.apache.commons.lang.StringUtils;
020import org.apache.commons.lang3.exception.ExceptionUtils;
021import org.openqa.selenium.By;
022import org.openqa.selenium.JavascriptExecutor;
023import org.openqa.selenium.NoSuchFrameException;
024import org.openqa.selenium.Proxy;
025import org.openqa.selenium.WebDriver;
026import org.openqa.selenium.WebElement;
027import org.openqa.selenium.chrome.ChromeDriver;
028import org.openqa.selenium.chrome.ChromeDriverService;
029import org.openqa.selenium.firefox.FirefoxDriver;
030import org.openqa.selenium.firefox.FirefoxProfile;
031import org.openqa.selenium.remote.CapabilityType;
032import org.openqa.selenium.remote.DesiredCapabilities;
033import org.openqa.selenium.remote.RemoteWebDriver;
034import org.openqa.selenium.safari.SafariDriver;
035
036import java.io.BufferedReader;
037import java.io.File;
038import java.io.InputStream;
039import java.io.InputStreamReader;
040import java.net.MalformedURLException;
041import java.net.URL;
042import java.util.LinkedList;
043import java.util.List;
044import 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 */
053public 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}