001/**
002 * Copyright 2005-2015 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 org.kuali.rice.testtools.selenium;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.commons.lang3.exception.ExceptionUtils;
020import org.junit.Assert;
021import org.openqa.selenium.Alert;
022import org.openqa.selenium.By;
023import org.openqa.selenium.JavascriptExecutor;
024import org.openqa.selenium.NoSuchFrameException;
025import org.openqa.selenium.Proxy;
026import org.openqa.selenium.WebDriver;
027import org.openqa.selenium.WebElement;
028import org.openqa.selenium.chrome.ChromeDriver;
029import org.openqa.selenium.chrome.ChromeDriverService;
030import org.openqa.selenium.firefox.FirefoxDriver;
031import org.openqa.selenium.firefox.FirefoxProfile;
032import org.openqa.selenium.remote.CapabilityType;
033import org.openqa.selenium.remote.DesiredCapabilities;
034import org.openqa.selenium.remote.RemoteWebDriver;
035import org.openqa.selenium.safari.SafariDriver;
036
037import java.io.BufferedReader;
038import java.io.File;
039import java.io.InputStream;
040import java.io.InputStreamReader;
041import java.net.MalformedURLException;
042import java.net.URL;
043import java.text.SimpleDateFormat;
044import java.util.Calendar;
045import java.util.Date;
046import java.util.LinkedList;
047import java.util.List;
048import java.util.concurrent.TimeUnit;
049
050/**
051 * <p>
052 * The goal of the WebDriverUtils class is to invert the dependencies on WebDriver from {@see WebDriverLegacyITBase} for reuse
053 * without having to extend WebDriverLegacyITBase.
054 * </p><p>
055 * For compatibility with {@see JiraAwareFailureUtils}, external test framework asserts and fails should not be called from
056 * WebDriverUtils, instead use {@see JiraAwareAftBase}.
057 * </p><p>
058 * For the first example see waitFor
059 * </p>
060 * @see WebDriverLegacyITBase
061 * @author Kuali Rice Team (rice.collab@kuali.org)
062 */
063public class WebDriverUtils {
064
065    protected static SauceLabsWebDriverHelper saucelabs;
066
067    public static boolean jGrowlEnabled = false;
068
069    public static boolean jsHighlightEnabled = false;
070
071    /**
072     * http://localhost:8080/kr-dev
073     */
074    public static final String DEFAULT_BASE_URL = "http://localhost:8080/kr-dev";
075
076    /**
077     * http://localhost:8080/krad-dev
078     */
079    public static final String DEFAULT_BASE_URL_KRAD = "http://localhost:8080/krad-dev";
080
081    /**
082     * <p>
083     * Set to true to not close the browser after the test has run.
084     * </p><p>
085     * -Dremote.driver.dontTearDown=true
086     * </p>
087     */
088    public static final String DONT_TEAR_DOWN_PROPERTY = "remote.driver.dontTearDown";
089
090    /**
091     * <p>
092     * Set to true to not close the browser after the test has if the test failed.
093     * </p><p>
094     * -Dremote.driver.dontTearDownOnFailure=true
095     * </p>
096     */
097    public static final String DONT_TEAR_DOWN_ON_FAILURE_PROPERTY = "remote.driver.dontTearDownOnFailure";
098
099    /**
100     * remote.public.driver
101     */
102    public static final String HUB_DRIVER_PROPERTY = "remote.public.driver";
103
104    /**
105     * For use when running Selenium tests through a Selenium Hub.
106     * -Dremote.public.hub=
107     */
108    public static final String HUB_PROPERTY = "remote.public.hub";
109
110    /**
111     * http://localhost:4444/wd/hub
112     */
113    public static final String HUB_URL_PROPERTY = "http://localhost:4444/wd/hub";
114
115    /**
116     * wait Methods inter loop sleep period, default of 1000 Milliseconds.
117     * TODO parametrize for JVM Arg
118     */
119    public static int IMPLICIT_WAIT_TIME_LOOP_MS = 1000;
120
121    /**
122     * <p>
123     * {@see IMPLICIT_WAIT_TIME_SECONDS_DEFAULT} to configure, default 30 seconds.
124     * </p><p>
125     * In code don't use this variable but call {@see configuredImplicityWait} to get the configured value.
126     * </p>
127     */
128    public static int IMPLICIT_WAIT_TIME_SECONDS_DEFAULT = 30;
129
130    /**
131     * If true tests will fail on jGrowl errors, default of false
132     * TODO upgrade to config via JVM param.
133     */
134    public static final boolean JGROWL_ERROR_FAILURE = false;
135
136    /**
137     * green (#66FF33)
138     */
139    public static final String JS_HIGHLIGHT_BACKGROUND = "#66FF33";
140
141    /**
142     * green (#66FF33)
143     */
144    public static final String JS_HIGHLIGHT_BOARDER = "#66FF33";
145
146    /**
147     * 400 milliseconds.
148     */
149    public static final int JS_HIGHLIGHT_MS = 400;
150
151    /**
152     * <p>
153     * {@see JS_HIGHLIGHT_MS} as default.
154     * </p><p>
155     * -Dremote.driver.highlight.ms=
156     * </p>
157     */
158    public static final String JS_HIGHLIGHT_MS_PROPERTY = "remote.driver.highlight.ms";
159
160    /**
161     * <p>
162     * Highlighting of elements as selenium runs.
163     * </p><p>
164     * -Dremote.driver.highlight=true
165     * </p>
166     */
167    public static final String JS_HIGHLIGHT_PROPERTY = "remote.driver.highlight";
168
169    /**
170     * TODO: playback for javascript highlighting.
171     *
172     * -Dremote.driver.highlight.input=
173     */
174    public static final String JS_HIGHLIGHT_INPUT_PROPERTY = "remote.driver.highlight.input";
175
176    /**
177     * <p>
178     * Local proxy used for running tests thru jmeter.
179     * </p><p>
180     * Include host name and port number. Example: localhost:7777
181     * </p><p>
182     * -Dremote.public.proxy=
183     * </p>
184     */
185    public static final String PROXY_HOST_PROPERTY = "remote.public.proxy";
186
187    /**
188     * <p>
189     * Skip automatice login if set to anything other than true/
190     * </p><p>
191     * -Dremote.autologin=false
192     * </p>
193     */
194    public static final String REMOTE_AUTOLOGIN_PROPERTY = "remote.autologin";
195
196    /**
197     * <p>
198     * Set to true to enable jGrowl test messages.
199     * </p><p>
200     * When enabled, jGrowl messages will be sent when clicking on buttons and links identified by their text.
201     * </p><p>
202     * -Dremote.jgrowl.enabled=true
203     * </p>
204     */
205    public static final String REMOTE_JGROWL_ENABLED = "remote.jgrowl.enabled";
206
207    /**
208     * Set -Dremote.login.uif=KNS to use old login screen.  Default value = KRAD
209     */
210    public static final String REMOTE_LOGIN_UIF = "remote.login.uif";
211
212    /**
213     * Set -Dremote.property.file= to load proprties from file
214     */
215    public static final String REMOTE_PROPERTIES_PROPERTY = "remote.property.file";
216
217    /**
218     * Set -Dremote.public.chrome= or WEBDRIVER_CHROME_DRIVER
219     */
220    public static final String REMOTE_PUBLIC_CHROME = "remote.public.chrome";
221
222    /**
223     * -Dremote.public.url=
224     */
225    public static final String REMOTE_PUBLIC_URL_PROPERTY = "remote.public.url";
226
227    /**
228     * <p>
229     * Set -Dremote.public.wait.seconds to override DEFAULT_WAIT_SEC.
230     * </p><p>
231     * {@see IMPLICIT_WAIT_TIME_SECONDS_DEFAULT}
232     * </p>
233     */
234    public static final String REMOTE_PUBLIC_WAIT_SECONDS_PROPERTY = "remote.public.wait.seconds";
235
236    /**
237     * Set -Dremote.public.user= to the username to login as
238     */
239    public static final String REMOTE_PUBLIC_USER_PROPERTY = "remote.public.user";
240
241    /**
242     * You probably don't want to really be using a userpool, set -Dremote.public.userpool= to base url if you must.
243     */
244    public static final String REMOTE_PUBLIC_USERPOOL_PROPERTY = "remote.public.userpool";
245
246    /**
247     * <p>
248     * Time to wait for the URL used in setup to load, 120 seconds by default.
249     * </p><p>
250     * Sometimes this is the first hit on the app and it needs a bit longer than any other.
251     * </p><p>
252     * TODO parametrize for JVM Arg
253     * <p>
254     */
255    public static final int SETUP_URL_LOAD_WAIT_SECONDS = 120;
256
257    /**
258     * Selenium's webdriver.chrome.driver parameter, you can set -Dwebdriver.chrome.driver= or Rice's REMOTE_PUBLIC_CHROME.
259     */
260    public static final String WEBDRIVER_CHROME_DRIVER = "webdriver.chrome.driver";
261
262    /**
263     * Setup the WebDriver test, login, and load the given web page
264     *
265     * @param className
266     * @param testName
267     * @return driver
268     * @throws Exception
269     */
270    public static WebDriver setUp(String className, String testName) throws Exception {
271        if ("true".equals(System.getProperty(REMOTE_JGROWL_ENABLED, "false"))) {
272            jGrowlEnabled = true;
273        }
274
275        if ("true".equals(System.getProperty(JS_HIGHLIGHT_PROPERTY, "false"))) {
276            jsHighlightEnabled = true;
277            if (System.getProperty(JS_HIGHLIGHT_INPUT_PROPERTY) != null) {
278                InputStream in = WebDriverUtils.class.getResourceAsStream(System.getProperty(JS_HIGHLIGHT_INPUT_PROPERTY));
279                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
280                String line = null;
281                List<String> lines = new LinkedList<String>();
282                while ((line = reader.readLine()) != null) {
283                    lines.add(line);
284                }
285            }
286        }
287
288        WebDriver driver = null;
289        if (System.getProperty(SauceLabsWebDriverHelper.REMOTE_DRIVER_SAUCELABS_PROPERTY) == null) {
290            driver = getWebDriver();
291        } else {
292            saucelabs = new SauceLabsWebDriverHelper();
293            saucelabs.setUp(className, testName);
294            driver = saucelabs.getDriver();
295        }
296
297        return driver;
298    }
299
300    /**
301     *<p>
302     * Calls {@see SauceLabsWebDriverHelper#tearDown} if {@see #REMOTE_PUBLIC_USERPOOL_PROPERTY} is enabled, calls a user pool
303     * url with the given poolParamTest and poolParamUser.
304     *</p>
305     *
306     * @param passed used by {@see SauceLabsWebDriverHelper#tearDown} to record Saucelabs test status can be null if Saucelabs
307     * is not being used
308     * @param sessionId used by {@see SauceLabsWebDriverHelper#tearDown} to record Saucelabs sessionId status can be null if Saucelabs
309     * is not being used
310     * @param poolParamTest can be null unless a user pool is being used
311     * @param poolParamUser can be null unless a user pool is being used
312     * @throws Exception
313     */
314    public static void tearDown(boolean passed, String sessionId, String poolParamTest, String poolParamUser, String className, String testName) throws Exception {
315
316        if (passed) {
317            System.out.println("Registering session passed " + sessionId);
318        } else {
319            System.out.println("Registering session failed " + sessionId);
320        }
321
322        if (System.getProperty(SauceLabsWebDriverHelper.REMOTE_DRIVER_SAUCELABS_PROPERTY) != null) {
323            saucelabs.tearDown(passed, sessionId, className, testName);
324        }
325
326        if (System.getProperty(REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) {
327            AutomatedFunctionalTestUtils.getHTML(AutomatedFunctionalTestUtils.prettyHttp(System.getProperty(
328                    REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" + poolParamTest + "&user=" + poolParamUser));
329        }
330    }
331
332    /**
333     * <p>
334     * If an alert is present accept it print the alert text to System.out
335     * </p>
336     *
337     * @param driver to accept alert on
338     */
339    public static void acceptAlertIfPresent(WebDriver driver) {
340        if (WebDriverUtils.isAlertPresent(driver)) {
341            System.out.println("Alert present " + WebDriverUtils.alertText(driver));
342            try {
343                alertAccept(driver);
344            } catch (Exception e) {
345                // Don't fail on alert exception
346            }
347        }
348    }
349
350    /**
351     * <p>
352     * Accept the javascript alert (clicking OK).
353     * </p>
354     *
355     * @param driver WebDriver to accept alert on
356     */
357    public static void alertAccept(WebDriver driver) {
358        Alert alert = driver.switchTo().alert();
359        jGrowl(driver, "AFT Step", false, "AFT Step: Accept Alert " + WebDriverUtils.alertText(driver));
360        alert.accept();
361    }
362
363    /**
364     * <p>
365     * Dismiss the javascript alert (clicking Cancel).
366     * </p>
367     *
368     * @param driver WebDriver to dismiss alert on
369     */
370    public static void alertDismiss(WebDriver driver) {
371        Alert alert = driver.switchTo().alert();
372        jGrowl(driver, "AFT Step", false, "AFT Step: Dismiss Alert " + WebDriverUtils.alertText(driver));
373        alert.dismiss();
374    }
375
376    /**
377     * <p>
378     * Return alert text.
379     * </p>
380     *
381     * @param driver to get alert text from
382     * @return alert text
383     */
384    public static String alertText(WebDriver driver) {
385        return driver.switchTo().alert().getText();
386    }
387
388    /**
389     * <p>
390     * <a href="http://code.google.com/p/chromedriver/downloads/list">ChromeDriver downloads</a>, {@see #REMOTE_PUBLIC_CHROME},
391     * {@see #WEBDRIVER_CHROME_DRIVER}, and {@see #HUB_DRIVER_PROPERTY}
392     * </p>
393     *
394     * @return chromeDriverService
395     */
396    public static ChromeDriverService chromeDriverCreateCheck() {
397        String driverParam = System.getProperty(HUB_DRIVER_PROPERTY);
398        // TODO can the saucelabs driver stuff be leveraged here?
399        if (driverParam != null && "chrome".equals(driverParam.toLowerCase())) {
400            if (System.getProperty(WEBDRIVER_CHROME_DRIVER) == null) {
401                if (System.getProperty(REMOTE_PUBLIC_CHROME) != null) {
402                    System.setProperty(WEBDRIVER_CHROME_DRIVER, System.getProperty(REMOTE_PUBLIC_CHROME));
403                }
404            }
405            try {
406                ChromeDriverService chromeDriverService = new ChromeDriverService.Builder()
407                        .usingDriverExecutable(new File(System.getProperty(WEBDRIVER_CHROME_DRIVER)))
408                        .usingAnyFreePort()
409                        .build();
410                return chromeDriverService;
411            } catch (Throwable t) {
412                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)   ;
413            }
414        }
415        return null;
416    }
417
418    /**
419     * <p>
420     * Return the configured implicity wait seconds, {@see #REMOTE_PUBLIC_WAIT_SECONDS_PROPERTY} and {@see #IMPLICIT_WAIT_TIME_SECONDS_DEFAULT}.
421     * </p>
422     *
423     * @return seconds for implicity wait
424     */
425    public static int configuredImplicityWait() {
426        return Integer.parseInt(System.getProperty(REMOTE_PUBLIC_WAIT_SECONDS_PROPERTY, IMPLICIT_WAIT_TIME_SECONDS_DEFAULT + ""));
427    }
428
429    /**
430     * <p>
431     * Remove double line spacing.
432     * </p>
433     *
434     * @param contents String to remove double line spacing from
435     * @return String with double line spacing removed.
436     */
437    public static String deLinespace(String contents) {
438        while (contents.contains("\n\n")) {
439            contents = contents.replaceAll("\n\n", "\n");
440        }
441
442        return contents;
443    }
444
445    /**
446     * <p>
447     * If {@see #REMOTE_PUBLIC_USER_PROPERTY} property is set, return its value, else if {@see #REMOTE_PUBLIC_USERPOOL_PROPERTY}
448     * is set use it to query the userpool service passing the testParam.
449     * </p>
450     * *
451     * @param testParam to use if using a user pool
452     * @return user
453     */
454    public static String determineUser(String testParam) {
455        String user = null;
456
457        if (System.getProperty(REMOTE_PUBLIC_USER_PROPERTY) != null) {
458            return System.getProperty(REMOTE_PUBLIC_USER_PROPERTY);
459        } else if (System.getProperty(REMOTE_PUBLIC_USERPOOL_PROPERTY) != null) { // deprecated
460            String userResponse = AutomatedFunctionalTestUtils.getHTML(AutomatedFunctionalTestUtils.prettyHttp(
461                    System.getProperty(REMOTE_PUBLIC_USERPOOL_PROPERTY) + "?test=" + testParam.trim()));
462            return userResponse.substring(userResponse.lastIndexOf(":") + 2, userResponse.lastIndexOf("\""));
463        }
464
465        return user;
466    }
467
468    /**
469     * <p>
470     * Setting the JVM arg remote.driver.dontTearDown to y or t leaves the browser window open when the test has completed.
471     * <p></p>
472     * Valuable when debugging, updating, or creating new tests.  When implementing your own tearDown method rather than an
473     * inherited one, it is a common courtesy to include this check and not stop and shutdown the browser window to make it
474     * easy debug or update your test.
475     * </p>
476     *
477     * @return true if the dontTearDownProperty is not set.
478     */
479    public static boolean dontTearDownPropertyNotSet() {
480        return System.getProperty(DONT_TEAR_DOWN_PROPERTY) == null ||
481                "f".startsWith(System.getProperty(DONT_TEAR_DOWN_PROPERTY).toLowerCase()) ||
482                "n".startsWith(System.getProperty(DONT_TEAR_DOWN_PROPERTY).toLowerCase());
483    }
484
485    /**
486     * Given the boolean parameter and depending on if {@see #DONT_TEAR_DOWN_ON_FAILURE_PROPERTY} is set to something other
487     * than n, don't tear down the browser window on a test failure.
488
489     * @param passed
490     * @return
491     */
492    public static boolean dontTearDownOnFailure(boolean passed) {
493        if (!"n".equalsIgnoreCase(System.getProperty(DONT_TEAR_DOWN_ON_FAILURE_PROPERTY, "n"))) {
494            return passed;
495        }
496        return true;
497    }
498
499    /**
500     * <p>
501     * Find Button by text.
502     * </p>
503     *
504     * @param driver to find button on
505     * @param buttonText text to find button by
506     * @return WebElement of button with button text
507     */
508    public static WebElement findButtonByText(WebDriver driver, String buttonText) {
509        return findElement(driver, By.xpath("//button[contains(text(), '" + buttonText + "')]"));
510    }
511
512    /**
513     * <p>
514     * Find and highlight the WebElement using the given WebDriver and By.
515     * </p>
516     *
517     * @param driver driver to find on
518     * @param by selector to find
519     * @return
520     */
521    public static WebElement findElement(WebDriver driver, By by) {
522        WebElement found = driver.findElement(by);
523        WebDriverUtils.highlightElement(driver, found);
524        return found;
525    }
526
527    /**
528     * <p>
529     * In order to run as a smoke test the ability to set the baseUrl via the JVM arg remote.public.url is required.
530     * </p><p>
531     * Trailing slashes are trimmed.  If the remote.public.url does not start with http:// it will be added.
532     * </p>
533     *
534     * @return http://localhost:8080/kr-dev by default else the value of remote.public.url
535     */
536    public static String getBaseUrlString() {
537        String baseUrl = System.getProperty(REMOTE_PUBLIC_URL_PROPERTY);
538        if (baseUrl == null) {
539            baseUrl = DEFAULT_BASE_URL;
540        }
541        baseUrl = AutomatedFunctionalTestUtils.prettyHttp(baseUrl);
542        return baseUrl;
543    }
544
545    /**
546     * yyyy-MM-dd-HH-mm-ss
547     *
548     * @return Date formatted as yyyy-MM-dd-HH-mm-ss
549     */
550    public static String getDateTimeStampFormatted() {
551        Date now = Calendar.getInstance().getTime();
552        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
553        return sdf.format(now);
554    }
555
556    /**
557     * <p>
558     * Return the WebElement that has the attribute name with the given value.
559     * </p>
560     *
561     * @param driver to get element from
562     * @param attributeName attribute name to find element by
563     * @param value for the attribute name to find element by
564     * @return WebElement
565     */
566    public static WebElement getElementByAttributeValue(WebDriver driver, String attributeName, String value){
567        return findElement(driver, By.cssSelector("[" + attributeName + "='" + value +"']"));
568    }
569
570    /**
571     * <p>
572     * In order to run as a smoke test under selenium grid the ability to set the hubUrl via the JVM arg remote.public.hub is required.
573     * </p><p>
574     * Trailing slashes are trimmed.  If the remote.public.hub does not start with http:// it will be added.
575     * </p>
576     *
577     * @return http://localhost:4444/wd/hub by default else the value of remote.public.hub
578     */
579    public static String getHubUrlString() {
580        String hubUrl = System.getProperty(HUB_PROPERTY);
581        if (hubUrl == null) {
582            hubUrl = HUB_URL_PROPERTY;
583        }
584        hubUrl = AutomatedFunctionalTestUtils.prettyHttp(hubUrl);
585        if (!hubUrl.endsWith("/wd/hub")) {
586            hubUrl = hubUrl + "/wd/hub";
587        }
588        return hubUrl;
589    }
590
591    /**
592     * <p>
593     * remote.public.driver set to chrome or firefox (null assumes firefox).
594     * </p><p>
595     * if remote.public.hub is set a RemoteWebDriver is created (Selenium Grid)
596     * if proxy.host is set, the web driver is setup to use a proxy
597     * </p>
598     *
599     * @return WebDriver or null if unable to create
600     */
601    public static WebDriver getWebDriver() {
602        String driverParam = System.getProperty(HUB_DRIVER_PROPERTY);
603        String hubParam = System.getProperty(HUB_PROPERTY);
604        String proxyParam = System.getProperty(PROXY_HOST_PROPERTY);
605
606        // setup proxy if specified as VM Arg
607        DesiredCapabilities capabilities = new DesiredCapabilities();
608        WebDriver webDriver = null;
609        if (StringUtils.isNotEmpty(proxyParam)) {
610            capabilities.setCapability(CapabilityType.PROXY, new Proxy().setHttpProxy(proxyParam));
611        }
612
613        if (hubParam == null) {
614            if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
615                FirefoxProfile profile = new FirefoxProfile();
616                profile.setEnableNativeEvents(false);
617                capabilities.setCapability(FirefoxDriver.PROFILE, profile);
618                return new FirefoxDriver(capabilities);
619            } else if ("chrome".equalsIgnoreCase(driverParam)) {
620                return new ChromeDriver(capabilities);
621            } else if ("safari".equals(driverParam)) {
622                System.out.println("SafariDriver probably won't work, if it does please contact Erik M.");
623                return new SafariDriver(capabilities);
624            }
625        } else {
626            try {
627                if (driverParam == null || "firefox".equalsIgnoreCase(driverParam)) {
628                    return new RemoteWebDriver(new URL(getHubUrlString()), DesiredCapabilities.firefox());
629                } else if ("chrome".equalsIgnoreCase(driverParam)) {
630                    return new RemoteWebDriver(new URL(getHubUrlString()), DesiredCapabilities.chrome());
631                }
632            } catch (MalformedURLException mue) {
633                System.out.println(getHubUrlString() + " " + mue.getMessage());
634                mue.printStackTrace();
635            }
636        }
637        return null;
638    }
639
640    public static void highlightElement(WebDriver webDriver, By by) {
641        List<WebElement> elements = webDriver.findElements(by);
642        for (WebElement element : elements) {
643            WebDriverUtils.highlightElement(webDriver, element);
644        }
645    }
646
647
648    public static void highlightElements(WebDriver webDriver, List<WebElement> webElements) {
649        for (WebElement webElement: webElements) {
650            highlightElement(webDriver, webElement);
651        }
652    }
653
654    /**
655     * <p>
656     * Highlight given WebElement.
657     * </p>
658     *
659     * @param webDriver to execute highlight on
660     * @param webElement to highlight
661     */
662    public static void highlightElement(WebDriver webDriver, WebElement webElement) {
663        if (jsHighlightEnabled && webElement != null) {
664            try {
665                //                System.out.println("highlighting " + webElement.toString() + " on url " + webDriver.getCurrentUrl());
666                JavascriptExecutor js = (JavascriptExecutor) webDriver;
667                String jsHighlight = "element = arguments[0];\n"
668                        + "originalStyle = element.getAttribute('style');\n"
669                        + "element.setAttribute('style', originalStyle + \"; background: "
670                        + JS_HIGHLIGHT_BACKGROUND + "; border: 2px solid " + JS_HIGHLIGHT_BOARDER + ";\");\n"
671                        + "setTimeout(function(){\n"
672                        + "    element.setAttribute('style', originalStyle);\n"
673                        + "}, " + System.getProperty(JS_HIGHLIGHT_MS_PROPERTY, JS_HIGHLIGHT_MS + "") + ");";
674                js.executeScript(jsHighlight, webElement);
675            } catch (Throwable t) {
676                System.out.println("Throwable during javascript highlight element");
677                t.printStackTrace();
678            }
679        }
680    }
681
682    /**
683     * <p>
684     * Return true if an alert is present, false if not.
685     * </p>
686     *
687     * @param driver to check for presents of alert on
688     * @return true if there is an alert present, false if not
689     */
690    public static boolean isAlertPresent(WebDriver driver) {
691        try {
692            driver.switchTo().alert();
693            return true;
694        } catch (Exception e) {
695            return false;
696        }
697    }
698
699    public static Boolean isTextPresent(WebDriver driver, String pageText, String text) {
700        boolean textPresent = Boolean.FALSE;
701        if (pageText.contains(text)) {
702            WebDriverUtils.highlightElement(driver, By.xpath("//*[contains(text(), '" + text + "')]"));
703            textPresent = Boolean.TRUE;
704        }
705        WebDriverUtils.jGrowl(driver, "Is Text Present?", false, "Is text '" + text + "' present?" + " " + textPresent);
706        return textPresent;
707    }
708
709    /**
710     * assumes javascript window.ALL_ERRORS contains errors
711     */
712    public static List javascriptErrors(WebDriver driver) {
713        List errors = new LinkedList();
714
715        // guard against a NPE below is due to a null WebDriver being passed in
716        if (driver == null) {
717            System.out.println("WebDriver to use to retrieve javascript errors was null!");
718            return errors;
719        }
720
721        try {
722            String javascript = "return window.ALL_ERRORS;";
723            List javascriptErrors = (List)((JavascriptExecutor) driver).executeScript(javascript);
724            if (javascriptErrors != null) {
725                errors.addAll(javascriptErrors);
726            }
727        } catch (Throwable t) {
728            // don't fail on problems retrieving javascript errors
729            System.out.println("Problem retrieving javascript errors: " + t.toString());
730            t.printStackTrace(System.out);
731        }
732
733        return errors;
734    }
735
736    public static String javascriptErrorsToString(List errors) {
737        StringBuilder sb = new StringBuilder();
738
739        if (errors != null && errors.size() > 0) {
740            for (Object error: errors) {
741                sb.append((String)error).append("\n");
742            }
743        }
744
745        return sb.toString();
746    }
747
748    /**
749     * <p>
750     * Display jGrowl.
751     * </p>
752     *
753     * @param driver WebDriver to execute jGrowl on
754     * @param jGrowlHeader header text for jGrowl
755     * @param sticky true to set the jGrowl to sticky, false for not sticky
756     * @param message message to display in the jGrowl
757     * @param throwable message and stacktrace to included in jGrowl
758     */
759    public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message, Throwable throwable) {
760        if (jGrowlEnabled) { // check if jGrowl is enabled to skip over the stack trace extraction if it is not.
761            jGrowl(driver, jGrowlHeader, sticky, message + " " + throwable.getMessage() + "\n" + ExceptionUtils.getStackTrace(throwable));
762        }
763    }
764
765    /**
766     * <p>
767     * Display jGrowl.
768     * </p>
769     *
770     * @param driver WebDriver to execute jGrowl on
771     * @param jGrowlHeader header text for jGrowl
772     * @param sticky true to set the jGrowl to sticky, false for not sticky
773     * @param message message to display in the jGrowl
774     */
775    public static void jGrowl(WebDriver driver, String jGrowlHeader, boolean sticky, String message) {
776        stepMessage(message);
777        if (jGrowlEnabled) {
778            String javascript="jQuery.jGrowl('" + message + "' , {sticky: " + sticky + ", header : '" + jGrowlHeader + "'});";
779            try {
780                ((JavascriptExecutor) driver).executeScript(javascript);
781            } catch (Throwable t) {
782                jGrowlException(t, javascript);
783            }
784        }
785    }
786
787    /**
788     * <p>
789     * Print jGrowl Exception to System.out, if {@see #JGROWL_ERROR_FAILURE} is set to true, fail.
790     * </p>
791     *
792     * @param throwable message and stack trace to print and if configured fail with
793     */
794    public static void jGrowlException(Throwable throwable, String jGrowlJavascript) {
795        String failMessage = jGrowlJavascript + " failed with " + throwable.getMessage() + "\n"
796                + ExceptionUtils.getStackTrace(throwable);
797        System.out.println("jGrowl failure " + failMessage);
798        if (JGROWL_ERROR_FAILURE) {
799            Assert.fail(failMessage); // Assert fail okay here as jGrowl failures are not Jira worthy yet
800        }
801    }
802
803    public static void openTestUrl(WebDriver driver, String testUrl) {
804        driver.manage().timeouts().implicitlyWait(WebDriverUtils.SETUP_URL_LOAD_WAIT_SECONDS, TimeUnit.SECONDS);
805
806        driver.get(testUrl);
807        if (!System.getProperty(SauceLabsWebDriverHelper.SAUCE_BROWSER_PROPERTY,"ff").equals("opera")) {
808            driver.manage().window().maximize();
809            //            driver.manage().window().setSize(new Dimension(800,600));
810        }
811
812        WebDriverUtils.jGrowl(driver, "Open URL", false, "Open " + testUrl);
813
814        driver.manage().timeouts().implicitlyWait(WebDriverUtils.configuredImplicityWait(), TimeUnit.SECONDS);
815    }
816
817    /**
818     * <p>
819     * Select frame defined by locator without throwing an Exception if it doesn't exist.
820     * </p>
821     *
822     * @param driver to select frame on
823     * @param locator to identify frame to select
824     */
825    public static void selectFrameSafe(WebDriver driver, String locator) {
826        try {
827            driver.switchTo().frame(locator);
828        } catch (NoSuchFrameException nsfe) {
829            // don't fail
830        }
831    }
832
833    public static void stepMessage(String message) {
834        System.out.println("AFT Step: " + message);
835    }
836
837    /**
838     * <p>
839     * Return the WebElement that has the attribute name with the given value within the given seconds to wait.
840     * </p>
841     *
842     * @param driver to get element from
843     * @param attribute name to find element by
844     * @param attributeValue for the attribute name to find element by
845     * @param waitSeconds number of seconds to wait
846     * @return WebElement
847     * @throws InterruptedException
848     */
849    public static WebElement waitAndGetElementByAttributeValue(WebDriver driver, String attribute, String attributeValue, int waitSeconds) throws InterruptedException {
850        // jenkins implies that implicitlyWait is worse than sleep loop for finding elements by 100+ test failures on the old sampleapp
851        //        driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
852        //        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
853
854        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
855
856        boolean failed = false;
857
858        for (int second = 0;; second++) {
859            Thread.sleep(1000);
860            if (second >= waitSeconds) {
861                failed = true;
862            }
863            try {
864                if (failed || (getElementByAttributeValue(driver, attribute, attributeValue) != null)) {
865                    break;
866                }
867            } catch (Exception e) {}
868        }
869
870        WebElement element = getElementByAttributeValue(driver, attribute, attributeValue);
871        driver.manage().timeouts().implicitlyWait(WebDriverUtils.configuredImplicityWait(), TimeUnit.SECONDS);
872        return element;
873    }
874
875    public static void waitToAcceptAlert(WebDriver driver, int waitSeconds, String message) throws InterruptedException {
876        driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME_LOOP_MS, TimeUnit.MILLISECONDS);
877
878        boolean failed = false;
879
880        for (int second = 0;; second++) {
881            Thread.sleep(1000);
882            if (second >= waitSeconds) {
883                failed = true;
884            }
885            try {
886                if (failed) {
887                    break;
888                } else if (isAlertPresent(driver)) {
889                    acceptAlertIfPresent(driver);
890                    break;
891                }
892            } catch (Exception e) {}
893        }
894
895        driver.manage().timeouts().implicitlyWait(configuredImplicityWait(), TimeUnit.SECONDS);
896    }
897
898    /**
899     * <p>
900     * Wait for the given amount of seconds, for the given by, using the given driver.  The message is displayed if the
901     * by cannot be found.  No action is performed on the by, so it is possible that the by found is not visible or enabled.
902     * </p>
903     *
904     * @param driver WebDriver to wait on
905     * @param waitSeconds seconds to wait
906     * @param by By to wait for
907     * @param message to display if by is not found in waitSeconds
908     * @throws InterruptedException
909     */
910    public static WebElement waitFor(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
911        // jenkins implies that implicitlyWait is worse than sleep loop for finding elements by 100+ test failures on the old sampleapp
912        //        driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
913        //        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
914
915        driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME_LOOP_MS, TimeUnit.MILLISECONDS);
916
917        boolean failed = false;
918        WebElement element = null;
919
920        for (int second = 0;; second++) {
921            Thread.sleep(1000);
922            if (second >= waitSeconds) {
923                failed = true;
924            }
925            try {
926                if (failed) {
927                    break;
928                } else if ((driver.findElements(by)).size() > 0) {
929                    element = findElement(driver, by);  // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
930                    highlightElement(driver, element);
931                    break;
932                }
933            } catch (Exception e) {}
934        }
935
936        driver.manage().timeouts().implicitlyWait(configuredImplicityWait(), TimeUnit.SECONDS);
937        return element;
938    }
939
940    /**
941     * <p>
942     * Wait for WebElements.
943     * </p>
944     *
945     * @param driver WebDriver to wait on
946     * @param by By to wait for
947     * @return
948     * @throws InterruptedException
949     */
950    public static List<WebElement> waitFors(WebDriver driver, By by) throws InterruptedException {
951        return waitFors(driver, configuredImplicityWait(), by, "");
952    }
953
954    /**
955     * <p>
956     * Wait for WebElements.
957     * </p>
958     *
959     * @param driver WebDriver to wait on
960     * @param by By to wait for
961     * @param message to display if by is not found in waitSeconds
962     * @return List of WebElements found
963     * @throws InterruptedException
964     */
965    public static List<WebElement> waitFors(WebDriver driver, By by, String message) throws InterruptedException {
966        return waitFors(driver, configuredImplicityWait(), by, message);
967    }
968
969   /**
970    * <p>
971    * Wait for the given amount of seconds, for the given by, using the given driver.  The message is displayed if the
972    * by cannot be found.  No action is performed on the by, so it is possible that the by found is not visible or enabled.
973    * </p>
974    *
975    * @param driver WebDriver to wait on
976    * @param waitSeconds seconds to wait
977    * @param by By to wait for
978    * @param message to display if by is not found in waitSeconds
979    * @return List of WebElements found
980    * @throws InterruptedException
981    */
982    public static List<WebElement> waitFors(WebDriver driver, int waitSeconds, By by, String message) throws InterruptedException {
983        // jenkins implies that implicitlyWait is worse than sleep loop for finding elements by 100+ test failures on the old sampleapp
984        //        driver.manage().timeouts().implicitlyWait(waitSeconds, TimeUnit.SECONDS);
985        //        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
986
987        driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME_LOOP_MS, TimeUnit.MILLISECONDS);
988
989        boolean failed = false;
990
991        for (int second = 0;; second++) {
992            Thread.sleep(1000);
993            if (second >= waitSeconds) {
994                failed = true;
995            }
996            try {
997                if (failed || (driver.findElements(by)).size() > 0) {
998                    break;
999                }
1000            } catch (Exception e) {}
1001        }
1002
1003        driver.manage().timeouts().implicitlyWait(configuredImplicityWait(), TimeUnit.SECONDS);
1004        return driver.findElements(by);  // NOTICE just the find, no action, so by is found, but might not be visible or enabled.
1005    }
1006}