001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.test;
014
015import com.thoughtworks.selenium.CommandProcessor;
016import com.thoughtworks.selenium.Selenium;
017import com.thoughtworks.selenium.webdriven.WebDriverBackedSelenium;
018import com.thoughtworks.selenium.webdriven.WebDriverCommandProcessor;
019
020import org.apache.tapestry5.test.constants.TapestryRunnerConstants;
021import org.openqa.selenium.By;
022import org.openqa.selenium.JavascriptExecutor;
023import org.openqa.selenium.NoSuchElementException;
024import org.openqa.selenium.StaleElementReferenceException;
025import org.openqa.selenium.WebDriver;
026import org.openqa.selenium.WebElement;
027import org.openqa.selenium.firefox.FirefoxDriver;
028import org.openqa.selenium.firefox.FirefoxOptions;
029import org.openqa.selenium.firefox.FirefoxProfile;
030import org.openqa.selenium.internal.WrapsDriver;
031import org.openqa.selenium.remote.DesiredCapabilities;
032import org.openqa.selenium.support.ui.ExpectedCondition;
033import org.openqa.selenium.support.ui.ExpectedConditions;
034import org.openqa.selenium.support.ui.WebDriverWait;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037import org.testng.Assert;
038import org.testng.ITestContext;
039import org.testng.annotations.*;
040import org.testng.xml.XmlTest;
041
042import io.github.bonigarcia.wdm.FirefoxDriverManager;
043
044import java.io.File;
045import java.lang.reflect.Method;
046import java.util.concurrent.TimeUnit;
047
048/**
049 * Base class for creating Selenium-based integration test cases. This class implements all the
050 * methods of {@link Selenium} and delegates to an instance (setup once per test by
051 * {@link #testStartup(org.testng.ITestContext, org.testng.xml.XmlTest)}.
052 *
053 * @since 5.2.0
054 */
055public abstract class SeleniumTestCase extends Assert implements Selenium
056{
057    public final static Logger LOGGER = LoggerFactory.getLogger(SeleniumTestCase.class);
058
059    /**
060     * 15 seconds
061     */
062    public static final String PAGE_LOAD_TIMEOUT = "15000";
063
064    public static final String TOMCAT_6 = "tomcat6";
065
066    public static final String JETTY_7 = "jetty7";
067
068    /**
069     * An XPath expression for locating a submit element (very commonly used
070     * with {@link #clickAndWait(String)}.
071     *
072     * @since 5.3
073     */
074    public static final String SUBMIT = "//input[@type='submit']";
075
076    /**
077     * The underlying {@link Selenium} instance that all the methods of this class delegate to;
078     * this can be useful when attempting to use SeleniumTestCase with a newer version of Selenium which
079     * has added some methods to the interface. This field will not be set until the test case instance
080     * has gone through its full initialization.
081     *
082     * @since 5.3
083     */
084    @Deprecated
085    protected Selenium selenium;
086
087    protected WebDriver webDriver;
088
089    private String baseURL;
090
091    private ErrorReporter errorReporter;
092
093    private ITestContext testContext;
094
095    /**
096     * Starts up the servers for the entire test (i.e., for multiple TestCases). By placing <parameter> elements
097     * inside the appropriate <test> (of your testng.xml configuration
098     * file), you can change the configuration or behavior of the servers. It is common to have two
099     * or more identical tests that differ only in terms of the <code>tapestry.browser-start-command</code> parameter,
100     * to run tests against multiple browsers.
101     * <table>
102     * <tr>
103     * <th>Parameter</th>
104     * <th>Name</th>
105     * <th>Default</th>
106     * <th>Description</th>
107     * </tr>
108     * <tr>
109     * <td>container</td>
110     * <td>tapestry.servlet-container</td>
111     * <td>JETTY_7</td>
112     * <td>The Servlet container to use for the tests. Currently {@link #JETTY_7} or {@link #TOMCAT_6}</td>
113     * </tr>
114     * <tr>
115     * <td>webAppFolder</td>
116     * <td>tapestry.web-app-folder</td>
117     * <td>src/main/webapp</td>
118     * <td>Location of web application context</td>
119     * </tr>
120     * <tr>
121     * <td>contextPath</td>
122     * <td>tapestry.context-path</td>
123     * <td><em>empty string</em></td>
124     * <td>Context path (defaults to root). As elsewhere, the context path should be blank, or start with a slash (but
125     * not end with one).</td>
126     * </tr>
127     * <tr>
128     * <td>port</td>
129     * <td>tapestry.port</td>
130     * <td>9090</td>
131     * <td>Port number for web server to listen to</td>
132     * </tr>
133     * <tr>
134     * <td>sslPort</td>
135     * <td>tapestry.ssl-port</td>
136     * <td>8443</td>
137     * <td>Port number for web server to listen to for secure requests</td>
138     * </tr>
139     * <tr>
140     * <td>browserStartCommand</td>
141     * <td>tapestry.browser-start-command</td>
142     * <td>*firefox</td>
143     * <td>Command string used to launch the browser, as defined by Selenium</td>
144     * </tr>
145     * <caption>Options and defaults</caption>
146     * </table>
147     *
148     * Tests in the <em>beforeStartup</em> group will be run before the start of Selenium. This can be used to
149     * programmatically override the above parameter values.
150     *
151     * This method will be invoked in <em>each</em> subclass, but is set up to only startup the servers once (it checks
152     * the {@link ITestContext} to see if the necessary keys are already present).
153     *
154     * @param testContext
155     *         Used to share objects between the launcher and the test suites
156     * @throws Exception
157     */
158    @BeforeTest(dependsOnGroups =
159            {"beforeStartup"})
160    public void testStartup(final ITestContext testContext, XmlTest xmlTest) throws Exception
161    {
162        // This is not actually necessary, because TestNG will only invoke this method once
163        // even when multiple test cases within the test extend from SeleniumTestCase. TestNG
164        // just invokes it on the "first" TestCase instance it has test methods for.
165
166        if (testContext.getAttribute(TapestryTestConstants.SHUTDOWN_ATTRIBUTE) != null)
167        {
168            return;
169        }
170
171        // If a parameter is overridden in another test method, TestNG won't pass the
172        // updated value via a parameter, but still passes the original (coming from testng.xml or the default).
173        // Seems like a TestNG bug.
174
175        // Map<String, String> testParameters = xmlTest.getParameters();
176
177        TapestryTestConfiguration annotation = this.getClass().getAnnotation(TapestryTestConfiguration.class);
178        if (annotation == null)
179        {
180            @TapestryTestConfiguration
181            final class EmptyInnerClass
182            {
183            }
184
185            annotation = EmptyInnerClass.class.getAnnotation(TapestryTestConfiguration.class);
186        }
187
188        String webAppFolder = getParameter(xmlTest, TapestryTestConstants.WEB_APP_FOLDER_PARAMETER,
189                annotation.webAppFolder());
190        String container = getParameter(xmlTest, TapestryTestConstants.SERVLET_CONTAINER_PARAMETER,
191                annotation.container());
192        String contextPath = getParameter(xmlTest, TapestryTestConstants.CONTEXT_PATH_PARAMETER,
193                annotation.contextPath());
194        int port = getIntParameter(xmlTest, TapestryTestConstants.PORT_PARAMETER, annotation.port());
195        int sslPort = getIntParameter(xmlTest, TapestryTestConstants.SSL_PORT_PARAMETER, annotation.sslPort());
196        String browserStartCommand = getParameter(xmlTest, TapestryTestConstants.BROWSER_START_COMMAND_PARAMETER,
197                annotation.browserStartCommand());
198
199        String baseURL = String.format("http://localhost:%d%s/", port, contextPath);
200
201        String sep = System.getProperty("line.separator");
202
203        LOGGER.info("Starting SeleniumTestCase:" + sep +
204                "    currentDir: " + System.getProperty("user.dir") + sep +
205                "  webAppFolder: " + webAppFolder + sep +
206                "     container: " + container + sep +
207                "   contextPath: " + contextPath + sep +
208                String.format("         ports: %d / %d", port, sslPort) + sep +
209                "  browserStart: " + browserStartCommand + sep +
210                "       baseURL: " + baseURL);
211
212        final Runnable stopWebServer = launchWebServer(container, webAppFolder, contextPath, port, sslPort);
213
214        FirefoxDriverManager.getInstance().setup();
215
216        File ffProfileTemplate = new File(TapestryRunnerConstants.MODULE_BASE_DIR, "src/test/conf/ff_profile_template");
217        DesiredCapabilities desiredCapabilities = DesiredCapabilities.firefox();
218        desiredCapabilities.setCapability(FirefoxDriver.MARIONETTE, true);
219
220        FirefoxOptions options = new FirefoxOptions(desiredCapabilities);
221
222        if (ffProfileTemplate.isDirectory())
223        {
224            FirefoxProfile profile = new FirefoxProfile(ffProfileTemplate);
225            options.setProfile(profile);
226        }
227
228        FirefoxDriver driver = new FirefoxDriver(options);
229        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
230
231        CommandProcessor webDriverCommandProcessor = new WebDriverCommandProcessor(baseURL, driver);
232
233
234        final ErrorReporterImpl errorReporter = new ErrorReporterImpl(driver, testContext);
235
236        ErrorReportingCommandProcessor commandProcessor = new ErrorReportingCommandProcessor(webDriverCommandProcessor,
237                errorReporter);
238
239        Selenium selenium = new WebDriverBackedSelenium(driver, baseURL);
240
241        testContext.setAttribute(TapestryTestConstants.BASE_URL_ATTRIBUTE, baseURL);
242        testContext.setAttribute(TapestryTestConstants.SELENIUM_ATTRIBUTE, selenium);
243        testContext.setAttribute(TapestryTestConstants.ERROR_REPORTER_ATTRIBUTE, errorReporter);
244        testContext.setAttribute(TapestryTestConstants.COMMAND_PROCESSOR_ATTRIBUTE, commandProcessor);
245
246        testContext.setAttribute(TapestryTestConstants.SHUTDOWN_ATTRIBUTE, new Runnable()
247        {
248            @Override
249            public void run()
250            {
251                try
252                {
253                    LOGGER.info("Shutting down selenium client ...");
254
255                    try
256                    {
257                        selenium.stop();
258                    } catch (RuntimeException e)
259                    {
260                        LOGGER.error("Selenium client shutdown failure.", e);
261                    }
262
263                    LOGGER.info("Shutting down webdriver ...");
264
265                    try
266                    {
267                        if (webDriver != null) { // is sometimes null... but why?
268                            webDriver.quit();
269                        }
270                    } catch (RuntimeException e)
271                    {
272                        LOGGER.error("Webdriver shutdown failure.", e);
273                    }
274
275                    LOGGER.info("Shutting down selenium server ...");
276
277                    LOGGER.info("Shutting web server ...");
278
279                    try
280                    {
281                        stopWebServer.run();
282                    } catch (RuntimeException e)
283                    {
284                        LOGGER.error("Web server shutdown failure.", e);
285                    }
286
287                    // Output, at the end of the Test, any html capture or screen shots (this makes it much easier
288                    // to locate them at the end of the run; there's such a variance on where they end up based
289                    // on whether the tests are running from inside an IDE or via one of the command line
290                    // builds.
291
292                    errorReporter.writeOutputPaths();
293                } finally
294                {
295                    testContext.removeAttribute(TapestryTestConstants.BASE_URL_ATTRIBUTE);
296                    testContext.removeAttribute(TapestryTestConstants.SELENIUM_ATTRIBUTE);
297                    testContext.removeAttribute(TapestryTestConstants.ERROR_REPORTER_ATTRIBUTE);
298                    testContext.removeAttribute(TapestryTestConstants.COMMAND_PROCESSOR_ATTRIBUTE);
299                    testContext.removeAttribute(TapestryTestConstants.SHUTDOWN_ATTRIBUTE);
300                }
301            }
302        });
303    }
304
305    private final String getParameter(XmlTest xmlTest, String key, String defaultValue)
306    {
307        String value = xmlTest.getParameter(key);
308
309        return value != null ? value : defaultValue;
310    }
311
312    private final int getIntParameter(XmlTest xmlTest, String key, int defaultValue)
313    {
314        String value = xmlTest.getParameter(key);
315
316        return value != null ? Integer.parseInt(value) : defaultValue;
317    }
318
319    /**
320     * Like {@link #testStartup(org.testng.ITestContext, org.testng.xml.XmlTest)} , this may
321     * be called multiple times against multiple instances, but only does work the first time.
322     */
323    @AfterTest
324    public void testShutdown(ITestContext context)
325    {
326        // Likewise, this method should only be invoked once.
327        Runnable r = (Runnable) context.getAttribute(TapestryTestConstants.SHUTDOWN_ATTRIBUTE);
328
329        // This test is still useful, however, because testStartup() may not have completed properly,
330        // and the runnable is the last thing it puts into the test context.
331
332        if (r != null)
333        {
334            LOGGER.info("Shutting down integration test support ...");
335            r.run();
336        }
337    }
338
339    /**
340     * Invoked from {@link #testStartup(org.testng.ITestContext, org.testng.xml.XmlTest)} to launch the web
341     * server to be tested. The return value is a Runnable that can be invoked later to cleanly shut down the launched
342     * server at the end of the test.
343     *
344     * @param container
345     *         identifies which web server should be launched
346     * @param webAppFolder
347     *         path to the web application context
348     * @param contextPath
349     *         the path the context is mapped to, usually the empty string
350     * @param port
351     *         the port number the server should handle
352     * @param sslPort
353     *         the port number on which the server should handle secure requests
354     * @return Runnable used to shut down the server
355     * @throws Exception
356     */
357    protected Runnable launchWebServer(String container, String webAppFolder, String contextPath, int port, int sslPort)
358            throws Exception
359    {
360        final ServletContainerRunner runner = createWebServer(container, webAppFolder, contextPath, port, sslPort);
361
362        return new Runnable()
363        {
364            @Override
365            public void run()
366            {
367                runner.stop();
368            }
369        };
370    }
371
372    private ServletContainerRunner createWebServer(String container, String webAppFolder, String contextPath, int port, int sslPort) throws Exception
373    {
374        if (TOMCAT_6.equals(container))
375        {
376            return new TomcatRunner(webAppFolder, contextPath, port, sslPort);
377        }
378
379        if (JETTY_7.equals(container))
380        {
381            return new JettyRunner(webAppFolder, contextPath, port, sslPort);
382        }
383
384        throw new RuntimeException("Unknown servlet container: " + container);
385    }
386
387    @BeforeClass
388    public void setup(ITestContext context)
389    {
390        this.testContext = context;
391
392        selenium = (Selenium) context.getAttribute(TapestryTestConstants.SELENIUM_ATTRIBUTE);
393        webDriver = ((WebDriverBackedSelenium) selenium).getWrappedDriver();
394        baseURL = (String) context.getAttribute(TapestryTestConstants.BASE_URL_ATTRIBUTE);
395        errorReporter = (ErrorReporter) context.getAttribute(TapestryTestConstants.ERROR_REPORTER_ATTRIBUTE);
396    }
397
398    @AfterClass
399    public void cleanup()
400    {
401        selenium = null;
402        baseURL = null;
403        errorReporter = null;
404        testContext = null;
405    }
406
407    /**
408     * Delegates to {@link ErrorReporter#writeErrorReport(String)} to capture the current page markup in a
409     * file for later analysis.
410     */
411    protected void writeErrorReport(String reportText)
412    {
413        errorReporter.writeErrorReport(reportText);
414    }
415
416    /**
417     * Returns the base URL for the application. This is of the typically <code>http://localhost:9999/</code> (i.e., it
418     * includes a trailing slash).
419     *
420     * Generally, you should use {@link #openLinks(String...)} to start from your application's home page.
421     */
422    public String getBaseURL()
423    {
424        return baseURL;
425    }
426
427    @BeforeMethod
428    public void indicateTestMethodName(Method testMethod)
429    {
430        LOGGER.info("Executing " + testMethod);
431
432        testContext.setAttribute(TapestryTestConstants.CURRENT_TEST_METHOD_ATTRIBUTE, testMethod);
433
434        String className = testMethod.getDeclaringClass().getSimpleName();
435        String testName = testMethod.getName().replace("_", " ");
436
437        selenium.setContext(className + ": " + testName);
438    }
439
440    @AfterMethod
441    public void cleanupTestMethod()
442    {
443        testContext.setAttribute(TapestryTestConstants.CURRENT_TEST_METHOD_ATTRIBUTE, null);
444    }
445
446    // ---------------------------------------------------------------------
447    // Start of delegate methods
448    //
449    // When upgrading to a new version of Selenium, it is probably easiest
450    // to delete all these methods and use the Generate Delegate Methods
451    // refactoring.
452    // ---------------------------------------------------------------------
453
454    @Override
455    public void addCustomRequestHeader(String key, String value)
456    {
457        selenium.addCustomRequestHeader(key, value);
458    }
459
460    @Override
461    public void addLocationStrategy(String strategyName, String functionDefinition)
462    {
463        selenium.addLocationStrategy(strategyName, functionDefinition);
464    }
465
466    @Override
467    public void addScript(String scriptContent, String scriptTagId)
468    {
469        selenium.addScript(scriptContent, scriptTagId);
470    }
471
472    @Override
473    public void addSelection(String locator, String optionLocator)
474    {
475        selenium.addSelection(locator, optionLocator);
476    }
477
478    @Override
479    public void allowNativeXpath(String allow)
480    {
481        selenium.allowNativeXpath(allow);
482    }
483
484    @Override
485    public void altKeyDown()
486    {
487        selenium.altKeyDown();
488    }
489
490    @Override
491    public void altKeyUp()
492    {
493        selenium.altKeyUp();
494    }
495
496    @Override
497    public void answerOnNextPrompt(String answer)
498    {
499        selenium.answerOnNextPrompt(answer);
500    }
501
502    @Override
503    public void assignId(String locator, String identifier)
504    {
505        selenium.assignId(locator, identifier);
506    }
507
508    @Override
509    public void attachFile(String fieldLocator, String fileLocator)
510    {
511        selenium.attachFile(fieldLocator, fileLocator);
512    }
513
514    @Override
515    public void captureEntirePageScreenshot(String filename, String kwargs)
516    {
517        selenium.captureEntirePageScreenshot(filename, kwargs);
518    }
519
520    @Override
521    public String captureEntirePageScreenshotToString(String kwargs)
522    {
523        return selenium.captureEntirePageScreenshotToString(kwargs);
524    }
525
526    @Override
527    public String captureNetworkTraffic(String type)
528    {
529        return selenium.captureNetworkTraffic(type);
530    }
531
532    @Override
533    public void captureScreenshot(String filename)
534    {
535        selenium.captureScreenshot(filename);
536    }
537
538    @Override
539    public String captureScreenshotToString()
540    {
541        return selenium.captureScreenshotToString();
542    }
543
544    @Override
545    public void check(String locator)
546    {
547        WebElement element = webDriver.findElement(convertLocator(locator));
548        if (!element.isSelected())
549        {
550            scrollIntoView(element);
551            element.click();
552        }
553    }
554
555    @Override
556    public void chooseCancelOnNextConfirmation()
557    {
558        selenium.chooseCancelOnNextConfirmation();
559    }
560
561    @Override
562    public void chooseOkOnNextConfirmation()
563    {
564        selenium.chooseOkOnNextConfirmation();
565    }
566
567    @Override
568    public void click(String locator)
569    {
570        WebElement element = webDriver.findElement(convertLocator(locator));
571        scrollIntoView(element);
572        JavascriptExecutor executor = (JavascriptExecutor)webDriver;
573        executor.executeScript("arguments[0].click();", element);
574//      element.click(); // failing as of Aug 2018
575    }
576
577    @Override
578    public void clickAt(String locator, String coordString)
579    {
580        selenium.clickAt(locator, coordString);
581    }
582
583    @Override
584    public void close()
585    {
586        selenium.close();
587    }
588
589    @Override
590    public void contextMenu(String locator)
591    {
592        selenium.contextMenu(locator);
593    }
594
595    @Override
596    public void contextMenuAt(String locator, String coordString)
597    {
598        selenium.contextMenuAt(locator, coordString);
599    }
600
601    @Override
602    public void controlKeyDown()
603    {
604        selenium.controlKeyDown();
605    }
606
607    @Override
608    public void controlKeyUp()
609    {
610        selenium.controlKeyUp();
611    }
612
613    @Override
614    public void createCookie(String nameValuePair, String optionsString)
615    {
616        selenium.createCookie(nameValuePair, optionsString);
617    }
618
619    @Override
620    public void deleteAllVisibleCookies()
621    {
622        selenium.deleteAllVisibleCookies();
623    }
624
625    @Override
626    public void deleteCookie(String name, String optionsString)
627    {
628        selenium.deleteCookie(name, optionsString);
629    }
630
631    @Override
632    public void deselectPopUp()
633    {
634        selenium.deselectPopUp();
635    }
636
637    @Override
638    public void doubleClick(String locator)
639    {
640        selenium.doubleClick(locator);
641    }
642
643    @Override
644    public void doubleClickAt(String locator, String coordString)
645    {
646        selenium.doubleClickAt(locator, coordString);
647    }
648
649    @Override
650    public void dragAndDrop(String locator, String movementsString)
651    {
652        selenium.dragAndDrop(locator, movementsString);
653    }
654
655    @Override
656    public void dragAndDropToObject(String locatorOfObjectToBeDragged, String locatorOfDragDestinationObject)
657    {
658        selenium.dragAndDropToObject(locatorOfObjectToBeDragged, locatorOfDragDestinationObject);
659    }
660
661    @Override
662    public void dragdrop(String locator, String movementsString)
663    {
664        selenium.dragdrop(locator, movementsString);
665    }
666
667    @Override
668    public void fireEvent(String locator, String eventName)
669    {
670        selenium.fireEvent(locator, eventName);
671    }
672
673    @Override
674    public void focus(String locator)
675    {
676        selenium.focus(locator);
677    }
678
679    @Override
680    public String getAlert()
681    {
682        return selenium.getAlert();
683    }
684
685    @Override
686    public String[] getAllButtons()
687    {
688        return selenium.getAllButtons();
689    }
690
691    @Override
692    public String[] getAllFields()
693    {
694        return selenium.getAllFields();
695    }
696
697    @Override
698    public String[] getAllLinks()
699    {
700        return selenium.getAllLinks();
701    }
702
703    @Override
704    public String[] getAllWindowIds()
705    {
706        return selenium.getAllWindowIds();
707    }
708
709    @Override
710    public String[] getAllWindowNames()
711    {
712        return selenium.getAllWindowNames();
713    }
714
715    @Override
716    public String[] getAllWindowTitles()
717    {
718        return selenium.getAllWindowTitles();
719    }
720
721    @Override
722    public String getAttribute(String attributeLocator)
723    {
724        return selenium.getAttribute(attributeLocator);
725    }
726
727    @Override
728    public String[] getAttributeFromAllWindows(String attributeName)
729    {
730        return selenium.getAttributeFromAllWindows(attributeName);
731    }
732
733    @Override
734    public String getBodyText()
735    {
736        return selenium.getBodyText();
737    }
738
739    @Override
740    public String getConfirmation()
741    {
742        return selenium.getConfirmation();
743    }
744
745    @Override
746    public String getCookie()
747    {
748        return selenium.getCookie();
749    }
750
751    @Override
752    public String getCookieByName(String name)
753    {
754        return selenium.getCookieByName(name);
755    }
756
757    @Override
758    public Number getCursorPosition(String locator)
759    {
760        return selenium.getCursorPosition(locator);
761    }
762
763    @Override
764    public Number getElementHeight(String locator)
765    {
766        return selenium.getElementHeight(locator);
767    }
768
769    @Override
770    public Number getElementIndex(String locator)
771    {
772        return selenium.getElementIndex(locator);
773    }
774
775    @Override
776    public Number getElementPositionLeft(String locator)
777    {
778        return selenium.getElementPositionLeft(locator);
779    }
780
781    @Override
782    public Number getElementPositionTop(String locator)
783    {
784        return selenium.getElementPositionTop(locator);
785    }
786
787    @Override
788    public Number getElementWidth(String locator)
789    {
790        return selenium.getElementWidth(locator);
791    }
792
793    @Override
794    public String getEval(String script)
795    {
796        return selenium.getEval(script);
797    }
798
799    @Override
800    public String getExpression(String expression)
801    {
802        return selenium.getExpression(expression);
803    }
804
805    @Override
806    public String getHtmlSource()
807    {
808        return selenium.getHtmlSource();
809    }
810
811    @Override
812    public String getLocation()
813    {
814        return selenium.getLocation();
815    }
816
817    @Override
818    public String getLog()
819    {
820        return selenium.getLog();
821    }
822
823    @Override
824    public Number getMouseSpeed()
825    {
826        return selenium.getMouseSpeed();
827    }
828
829    @Override
830    public String getPrompt()
831    {
832        return selenium.getPrompt();
833    }
834
835    @Override
836    public String getSelectedId(String selectLocator)
837    {
838        return selenium.getSelectedId(selectLocator);
839    }
840
841    @Override
842    public String[] getSelectedIds(String selectLocator)
843    {
844        return selenium.getSelectedIds(selectLocator);
845    }
846
847    @Override
848    public String getSelectedIndex(String selectLocator)
849    {
850        return selenium.getSelectedIndex(selectLocator);
851    }
852
853    @Override
854    public String[] getSelectedIndexes(String selectLocator)
855    {
856        return selenium.getSelectedIndexes(selectLocator);
857    }
858
859    @Override
860    public String getSelectedLabel(String selectLocator)
861    {
862        return selenium.getSelectedLabel(selectLocator);
863    }
864
865    @Override
866    public String[] getSelectedLabels(String selectLocator)
867    {
868        return selenium.getSelectedLabels(selectLocator);
869    }
870
871    @Override
872    public String getSelectedValue(String selectLocator)
873    {
874        return selenium.getSelectedValue(selectLocator);
875    }
876
877    @Override
878    public String[] getSelectedValues(String selectLocator)
879    {
880        return selenium.getSelectedValues(selectLocator);
881    }
882
883    @Override
884    public String[] getSelectOptions(String selectLocator)
885    {
886        return selenium.getSelectOptions(selectLocator);
887    }
888
889    @Override
890    public String getSpeed()
891    {
892        return selenium.getSpeed();
893    }
894
895    @Override
896    public String getTable(String tableCellAddress)
897    {
898        return selenium.getTable(tableCellAddress);
899    }
900
901    @Override
902    public String getText(String locator)
903    {
904        return selenium.getText(locator);
905    }
906
907    @Override
908    public String getTitle()
909    {
910        return selenium.getTitle();
911    }
912
913    @Override
914    public String getValue(String locator)
915    {
916        return selenium.getValue(locator);
917    }
918
919    @Override
920    public boolean getWhetherThisFrameMatchFrameExpression(String currentFrameString, String target)
921    {
922        return selenium.getWhetherThisFrameMatchFrameExpression(currentFrameString, target);
923    }
924
925    @Override
926    public boolean getWhetherThisWindowMatchWindowExpression(String currentWindowString, String target)
927    {
928        return selenium.getWhetherThisWindowMatchWindowExpression(currentWindowString, target);
929    }
930
931    @Override
932    public Number getXpathCount(String xpath)
933    {
934        return selenium.getXpathCount(xpath);
935    }
936
937    @Override
938    public void goBack()
939    {
940        selenium.goBack();
941    }
942
943    @Override
944    public void highlight(String locator)
945    {
946        selenium.highlight(locator);
947    }
948
949    @Override
950    public void ignoreAttributesWithoutValue(String ignore)
951    {
952        selenium.ignoreAttributesWithoutValue(ignore);
953    }
954
955    @Override
956    public boolean isAlertPresent()
957    {
958        return selenium.isAlertPresent();
959    }
960
961    @Override
962    public boolean isChecked(String locator)
963    {
964        return selenium.isChecked(locator);
965    }
966
967    @Override
968    public boolean isConfirmationPresent()
969    {
970        return selenium.isConfirmationPresent();
971    }
972
973    @Override
974    public boolean isCookiePresent(String name)
975    {
976        return selenium.isCookiePresent(name);
977    }
978
979    @Override
980    public boolean isEditable(String locator)
981    {
982        return selenium.isEditable(locator);
983    }
984
985    @Override
986    public boolean isElementPresent(String locator)
987    {
988        return !webDriver.findElements(convertLocator(locator)).isEmpty();
989    }
990
991    @Override
992    public boolean isOrdered(String locator1, String locator2)
993    {
994        return selenium.isOrdered(locator1, locator2);
995    }
996
997    @Override
998    public boolean isPromptPresent()
999    {
1000        return selenium.isPromptPresent();
1001    }
1002
1003    @Override
1004    public boolean isSomethingSelected(String selectLocator)
1005    {
1006        return selenium.isSomethingSelected(selectLocator);
1007    }
1008
1009    @Override
1010    public boolean isTextPresent(String pattern)
1011    {
1012        return selenium.isTextPresent(pattern);
1013    }
1014
1015    @Override
1016    public boolean isVisible(String locator)
1017    {
1018        return selenium.isVisible(locator);
1019    }
1020
1021    @Override
1022    public void keyDown(String locator, String keySequence)
1023    {
1024        selenium.keyDown(locator, keySequence);
1025    }
1026
1027    @Override
1028    public void keyDownNative(String keycode)
1029    {
1030        selenium.keyDownNative(keycode);
1031    }
1032
1033    @Override
1034    public void keyPress(String locator, String keySequence)
1035    {
1036        selenium.keyPress(locator, keySequence);
1037    }
1038
1039    @Override
1040    public void keyPressNative(String keycode)
1041    {
1042        selenium.keyPressNative(keycode);
1043    }
1044
1045    @Override
1046    public void keyUp(String locator, String keySequence)
1047    {
1048        selenium.keyUp(locator, keySequence);
1049    }
1050
1051    @Override
1052    public void keyUpNative(String keycode)
1053    {
1054        selenium.keyUpNative(keycode);
1055    }
1056
1057    @Override
1058    public void metaKeyDown()
1059    {
1060        selenium.metaKeyDown();
1061    }
1062
1063    @Override
1064    public void metaKeyUp()
1065    {
1066        selenium.metaKeyUp();
1067    }
1068
1069    @Override
1070    public void mouseDown(String locator)
1071    {
1072        selenium.mouseDown(locator);
1073    }
1074
1075    @Override
1076    public void mouseDownAt(String locator, String coordString)
1077    {
1078        selenium.mouseDownAt(locator, coordString);
1079    }
1080
1081    @Override
1082    public void mouseDownRight(String locator)
1083    {
1084        selenium.mouseDownRight(locator);
1085    }
1086
1087    @Override
1088    public void mouseDownRightAt(String locator, String coordString)
1089    {
1090        selenium.mouseDownRightAt(locator, coordString);
1091    }
1092
1093    @Override
1094    public void mouseMove(String locator)
1095    {
1096        selenium.mouseMove(locator);
1097    }
1098
1099    @Override
1100    public void mouseMoveAt(String locator, String coordString)
1101    {
1102        selenium.mouseMoveAt(locator, coordString);
1103    }
1104
1105    @Override
1106    public void mouseOut(String locator)
1107    {
1108        selenium.mouseOut(locator);
1109    }
1110
1111    @Override
1112    public void mouseOver(String locator)
1113    {
1114        selenium.mouseOver(locator);
1115    }
1116
1117    @Override
1118    public void mouseUp(String locator)
1119    {
1120        selenium.mouseUp(locator);
1121    }
1122
1123    @Override
1124    public void mouseUpAt(String locator, String coordString)
1125    {
1126        selenium.mouseUpAt(locator, coordString);
1127    }
1128
1129    @Override
1130    public void mouseUpRight(String locator)
1131    {
1132        selenium.mouseUpRight(locator);
1133    }
1134
1135    @Override
1136    public void mouseUpRightAt(String locator, String coordString)
1137    {
1138        selenium.mouseUpRightAt(locator, coordString);
1139    }
1140
1141    @Override
1142    public void open(String url)
1143    {
1144        selenium.open(url);
1145    }
1146
1147    @Override
1148    public void open(String url, String ignoreResponseCode)
1149    {
1150        selenium.open(url, ignoreResponseCode);
1151    }
1152
1153    @Override
1154    public void openWindow(String url, String windowID)
1155    {
1156        selenium.openWindow(url, windowID);
1157    }
1158
1159    @Override
1160    public void refresh()
1161    {
1162        selenium.refresh();
1163    }
1164
1165    @Override
1166    public void removeAllSelections(String locator)
1167    {
1168        selenium.removeAllSelections(locator);
1169    }
1170
1171    @Override
1172    public void removeScript(String scriptTagId)
1173    {
1174        selenium.removeScript(scriptTagId);
1175    }
1176
1177    @Override
1178    public void removeSelection(String locator, String optionLocator)
1179    {
1180        selenium.removeSelection(locator, optionLocator);
1181    }
1182
1183    @Override
1184    public String retrieveLastRemoteControlLogs()
1185    {
1186        return selenium.retrieveLastRemoteControlLogs();
1187    }
1188
1189    @Override
1190    public void rollup(String rollupName, String kwargs)
1191    {
1192        selenium.rollup(rollupName, kwargs);
1193    }
1194
1195    @Override
1196    public void runScript(String script)
1197    {
1198        selenium.runScript(script);
1199    }
1200
1201    @Override
1202    public void select(String selectLocator, String optionLocator)
1203    {
1204        selenium.select(selectLocator, optionLocator);
1205    }
1206
1207    @Override
1208    public void selectFrame(String locator)
1209    {
1210        selenium.selectFrame(locator);
1211    }
1212
1213    @Override
1214    public void selectPopUp(String windowID)
1215    {
1216        selenium.selectPopUp(windowID);
1217    }
1218
1219    @Override
1220    public void selectWindow(String windowID)
1221    {
1222        selenium.selectWindow(windowID);
1223    }
1224
1225    @Override
1226    public void setBrowserLogLevel(String logLevel)
1227    {
1228        selenium.setBrowserLogLevel(logLevel);
1229    }
1230
1231    @Override
1232    public void setContext(String context)
1233    {
1234        selenium.setContext(context);
1235    }
1236
1237    @Override
1238    public void setCursorPosition(String locator, String position)
1239    {
1240        selenium.setCursorPosition(locator, position);
1241    }
1242
1243    @Override
1244    public void setExtensionJs(String extensionJs)
1245    {
1246        selenium.setExtensionJs(extensionJs);
1247    }
1248
1249    @Override
1250    public void setMouseSpeed(String pixels)
1251    {
1252        selenium.setMouseSpeed(pixels);
1253    }
1254
1255    @Override
1256    public void setSpeed(String value)
1257    {
1258        selenium.setSpeed(value);
1259    }
1260
1261    @Override
1262    public void setTimeout(String timeout)
1263    {
1264        selenium.setTimeout(timeout);
1265    }
1266
1267    @Override
1268    public void shiftKeyDown()
1269    {
1270        selenium.shiftKeyDown();
1271    }
1272
1273    @Override
1274    public void shiftKeyUp()
1275    {
1276        selenium.shiftKeyUp();
1277    }
1278
1279    @Override
1280    public void showContextualBanner()
1281    {
1282        selenium.showContextualBanner();
1283    }
1284
1285    @Override
1286    public void showContextualBanner(String className, String methodName)
1287    {
1288        selenium.showContextualBanner(className, methodName);
1289    }
1290
1291    @Override
1292    public void shutDownSeleniumServer()
1293    {
1294        selenium.shutDownSeleniumServer();
1295    }
1296
1297    @Override
1298    public void start()
1299    {
1300        selenium.start();
1301    }
1302
1303    @Override
1304    public void start(Object optionsObject)
1305    {
1306        selenium.start(optionsObject);
1307    }
1308
1309    @Override
1310    public void start(String optionsString)
1311    {
1312        selenium.start(optionsString);
1313    }
1314
1315    @Override
1316    public void stop()
1317    {
1318        selenium.stop();
1319    }
1320
1321    @Override
1322    public void submit(String formLocator)
1323    {
1324        selenium.submit(formLocator);
1325    }
1326
1327    @Override
1328    public void type(String locator, String value)
1329    {
1330        WebElement element = webDriver.findElement(convertLocator(locator));
1331        ((JavascriptExecutor) webDriver).executeScript("arguments[0].value = arguments[1];", element, value);
1332    }
1333
1334    @Override
1335    public void typeKeys(String locator, String value)
1336    {
1337        WebElement element = webDriver.findElement(convertLocator(locator));
1338        element.sendKeys(value);
1339    }
1340
1341    @Override
1342    public void uncheck(String locator)
1343    {
1344        selenium.uncheck(locator);
1345    }
1346
1347    @Override
1348    public void useXpathLibrary(String libraryName)
1349    {
1350        selenium.useXpathLibrary(libraryName);
1351    }
1352
1353    @Override
1354    public void waitForCondition(String script, String timeout)
1355    {
1356        selenium.waitForCondition(script, timeout);
1357    }
1358
1359    protected void waitForCondition(ExpectedCondition condition)
1360    {
1361      waitForCondition(condition, 10l);
1362    }
1363
1364    protected void waitForCondition(ExpectedCondition condition, long timeoutSeconds)
1365    {
1366      WebDriverWait wait = new WebDriverWait(webDriver, timeoutSeconds);
1367      wait.until(condition);
1368    }
1369
1370    @Override
1371    public void waitForFrameToLoad(String frameAddress, String timeout)
1372    {
1373        selenium.waitForFrameToLoad(frameAddress, timeout);
1374    }
1375
1376    /**
1377     * Waits for page  to load, then waits for initialization to finish, which is recognized by the {@code data-page-initialized} attribute
1378     * being set to true on the body element. Polls at increasing intervals, for up-to 30 seconds (that's extraordinarily long, but helps sometimes
1379     * when manually debugging a page that doesn't have the floating console enabled)..
1380     */
1381    @Override
1382    public void waitForPageToLoad(String timeout)
1383    {
1384        selenium.waitForPageToLoad(timeout);
1385
1386        // In a limited number of cases, a "page" is an container error page or raw HTML content
1387        // that does not include the body element and data-page-initialized element. In those cases,
1388        // there will never be page initialization in the Tapestry sense and we return immediately.
1389        try
1390        {
1391            WebElement body = webDriver.findElement(By.cssSelector("body"));
1392
1393            if (body.getAttribute("data-page-initialized") == null)
1394            {
1395                return;
1396            }
1397            
1398            // Attempt to fix StaleElementReferenceException: The element reference of <body> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed
1399            // waitForCondition(ExpectedConditions.attributeToBe(body, "data-page-initialized", "true"), 30);
1400            waitForCssSelectorToAppear("body[data-page-initialized='true']");
1401        } catch (NoSuchElementException e)
1402        {
1403            // no body element found, there's nothing to wait for
1404        } catch (StaleElementReferenceException e) {
1405            e.printStackTrace();
1406            System.out.println("Continuing execution after exception above.");
1407        }
1408        
1409    }
1410
1411    @Override
1412    public void waitForPopUp(String windowID, String timeout)
1413    {
1414        selenium.waitForPopUp(windowID, timeout);
1415    }
1416
1417    @Override
1418    public void windowFocus()
1419    {
1420        selenium.windowFocus();
1421    }
1422
1423    @Override
1424    public void windowMaximize()
1425    {
1426        selenium.windowMaximize();
1427    }
1428
1429    // ---------------------------------------------------------------------
1430    // End of delegate methods
1431    // ---------------------------------------------------------------------
1432
1433
1434    public void scrollIntoView(WebElement element)
1435    {
1436        ((JavascriptExecutor) webDriver).executeScript("arguments[0].scrollIntoView(true);", element);
1437    }
1438
1439    /**
1440     * Formats a message from the provided arguments, which is written to System.err. In addition,
1441     * captures the AUT's markup, screenshot, and a report to the output directory.
1442     *
1443     * @param message
1444     * @param arguments
1445     * @since 5.4
1446     */
1447    protected final void reportAndThrowAssertionError(String message, Object... arguments)
1448    {
1449        StringBuilder builder = new StringBuilder(5000);
1450
1451        String formatted = String.format(message, arguments);
1452
1453        builder.append(formatted);
1454
1455        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
1456
1457        StringBuilder buffer = new StringBuilder(5000);
1458
1459        boolean enabled = false;
1460
1461        for (StackTraceElement e : stackTrace)
1462        {
1463            if (enabled)
1464            {
1465                buffer.append("\n- ");
1466                buffer.append(e);
1467                continue;
1468            }
1469
1470            if (e.getMethodName().equals("reportAndThrowAssertionError"))
1471            {
1472                enabled = true;
1473            }
1474        }
1475
1476        writeErrorReport(builder.toString());
1477
1478        throw new AssertionError(formatted);
1479    }
1480
1481    protected final void unreachable()
1482    {
1483        reportAndThrowAssertionError("An unreachable statement was reached.");
1484    }
1485
1486    /**
1487     * Open the {@linkplain #getBaseURL()}, and waits for the page to load.
1488     */
1489    protected final void openBaseURL()
1490    {
1491        open(baseURL);
1492
1493        waitForPageToLoad();
1494    }
1495
1496    /**
1497     * Asserts the text of an element, identified by the locator.
1498     *
1499     * @param locator
1500     *         identifies the element whose text value is to be asserted
1501     * @param expected
1502     *         expected value for the element's text
1503     */
1504    protected final void assertText(String locator, String expected)
1505    {
1506        String actual = null;
1507
1508        try
1509        {
1510            actual = getText(locator);
1511        } catch (RuntimeException ex)
1512        {
1513            System.err.printf("Error accessing %s: %s, in:\n\n%s\n\n", locator, ex.getMessage(), getHtmlSource());
1514
1515            throw ex;
1516        }
1517
1518        if (actual.equals(expected))
1519        {
1520            return;
1521        }
1522
1523        reportAndThrowAssertionError("%s was '%s' not '%s'", locator, actual, expected);
1524    }
1525
1526    protected final void assertTextPresent(String... text)
1527    {
1528        for (String item : text)
1529        {
1530            if (isTextPresent(item))
1531            {
1532                continue;
1533            }
1534
1535            reportAndThrowAssertionError("Page did not contain '" + item + "'.");
1536        }
1537    }
1538
1539    protected final void assertTextNotPresent(String... text)
1540    {
1541        for (String item : text)
1542        {
1543            if (isTextPresent(item))
1544            {
1545                reportAndThrowAssertionError("Page did contain '" + item + "'.");
1546            }
1547        }
1548    }
1549
1550    /**
1551     * Assets that each string provided is present somewhere in the current document.
1552     *
1553     * @param expected
1554     *         string expected to be present
1555     */
1556    protected final void assertSourcePresent(String... expected)
1557    {
1558        String source = getHtmlSource();
1559
1560        for (String snippet : expected)
1561        {
1562            if (source.contains(snippet))
1563            {
1564                continue;
1565            }
1566
1567            reportAndThrowAssertionError("Page did not contain source '" + snippet + "'.");
1568        }
1569    }
1570
1571    /**
1572     * Click a link identified by a locator, then wait for the resulting page to load.
1573     * This is not useful for Ajax updates, just normal full-page refreshes.
1574     *
1575     * @param locator
1576     *         identifies the link to click
1577     */
1578    protected final void clickAndWait(String locator)
1579    {
1580        click(locator);
1581        waitForPageToLoad();
1582    }
1583
1584    /**
1585     * Waits for the page to load (up to 15 seconds). This is invoked after clicking on an element
1586     * that forces a full page refresh.
1587     */
1588    protected final void waitForPageToLoad()
1589    {
1590        waitForPageToLoad(PAGE_LOAD_TIMEOUT);
1591    }
1592
1593    /**
1594     * Used when the locator identifies an attribute, not an element.
1595     *
1596     * @param locator
1597     *         identifies the attribute whose value is to be asserted
1598     * @param expected
1599     *         expected value for the attribute
1600     */
1601    protected final void assertAttribute(String locator, String expected)
1602    {
1603        String actual = null;
1604
1605        try
1606        {
1607            actual = getAttribute(locator);
1608        } catch (RuntimeException ex)
1609        {
1610
1611            reportAndThrowAssertionError("Error accessing %s: %s", locator, ex.getMessage());
1612        }
1613
1614        if (actual.equals(expected))
1615        {
1616            return;
1617        }
1618
1619        reportAndThrowAssertionError("%s was '%s' not '%s'", locator, actual, expected);
1620    }
1621
1622    /**
1623     * Assets that the value in the field matches the expectation
1624     *
1625     * @param locator
1626     *         identifies the field
1627     * @param expected
1628     *         expected value for the field
1629     * @since 5.3
1630     */
1631    protected final void assertFieldValue(String locator, String expected)
1632    {
1633        try
1634        {
1635            assertEquals(getValue(locator), expected);
1636        } catch (AssertionError ex)
1637        {
1638            reportAndThrowAssertionError("Failure accessing %s: %s", locator, ex);
1639        }
1640    }
1641
1642    /**
1643     * Opens the base URL, then clicks through a series of links to get to a desired application
1644     * state.
1645     *
1646     * @since 5.3
1647     */
1648    protected final void openLinks(String... linkText)
1649    {
1650        openBaseURL();
1651        
1652        if (getTitle().toLowerCase().contains("service unavailable")) {
1653            throw new RuntimeException("Webapp didn't start correctly. HTML contents: " + getHtmlSource());
1654        }
1655
1656        for (String text : linkText)
1657        {
1658            clickAndWait("link=" + text);
1659        }
1660    }
1661
1662    /**
1663     * Sleeps for the indicated number of seconds.
1664     *
1665     * @since 5.3
1666     */
1667    protected final void sleep(long millis)
1668    {
1669        try
1670        {
1671            Thread.sleep(millis);
1672        } catch (InterruptedException ex)
1673        {
1674            // Ignore.
1675        }
1676    }
1677
1678    /**
1679     * Waits for the element with the given client-side id to be present in the DOM (
1680     * does not assure that the element is visible).
1681     *
1682     * @param elementId
1683     *         identifies the element
1684     * @since 5.3
1685     */
1686    protected final void waitForElementToAppear(String elementId)
1687    {
1688
1689        String condition = String.format("selenium.browserbot.getCurrentWindow().document.getElementById(\"%s\")", elementId);
1690
1691        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1692    }
1693    
1694    /**
1695     * Waits for an element with a given CSS selector to appear.
1696     *
1697     * @param selector
1698     *         the CSS selector to wait.
1699     * @since 5.5
1700     */
1701    protected final void waitForCssSelectorToAppear(String selector)
1702    {
1703
1704        String condition = String.format("selenium.browserbot.getCurrentWindow().document.querySelector(\"%s\")", selector);
1705
1706        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1707    }
1708
1709    /**
1710     * Waits for the element to be removed from the DOM.
1711     *
1712     *
1713     * This implementation depends on window being extended with testSupport.isNotVisible().
1714     *
1715     * @param elementId
1716     *         client-side id of element
1717     * @since 5.3
1718     * @deprecated Deprecated in 5.4 with no replacement
1719     */
1720    protected final void waitForElementToDisappear(String elementId)
1721    {
1722        String condition = String.format("selenium.browserbot.getCurrentWindow().testSupport.doesNotExist(\"%s\")", elementId);
1723
1724        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1725    }
1726
1727    /**
1728     * Waits for the element specified by the selector to become visible
1729     * Note that waitForElementToAppear waits for the element to be present in the dom, visible or not. waitForVisible
1730     * waits for an element that already exists in the dom to become visible.
1731     *
1732     * @param selector
1733     *         element selector
1734     * @since 5.3
1735     */
1736    protected final void waitForVisible(String selector)
1737    {
1738        waitForCondition(ExpectedConditions.visibilityOfElementLocated(convertLocator(selector)));
1739    }
1740
1741    /**
1742     * Waits for the element specified by the selector to become invisible
1743     * Note that waitForElementToDisappear waits for the element to be absent from the dom, visible or not. waitForInvisible
1744     * waits for an existing element to become invisible.
1745     *
1746     * @param selector
1747     *         element selector
1748     * @since 5.3
1749     */
1750    protected final void waitForInvisible(String selector)
1751    {
1752        waitForCondition(ExpectedConditions.invisibilityOfElementLocated(convertLocator(selector)));
1753    }
1754
1755    /**
1756     * Asserts that the current page's title matches the expected value.
1757     *
1758     * @param expected
1759     *         value for title
1760     * @since 5.3
1761     */
1762    protected final void assertTitle(String expected)
1763    {
1764        try
1765        {
1766            assertEquals(getTitle(), expected);
1767        } catch (AssertionError ex)
1768        {
1769            reportAndThrowAssertionError("Unexpected title: %s", ex);
1770
1771            throw ex;
1772        }
1773    }
1774
1775    /**
1776     * Waits until all active XHR requests are completed.
1777     *
1778     * @param timeout
1779     *         timeout to wait for (no longer used)
1780     * @since 5.3
1781     * @deprecated Deprecated in 5.4 in favor of the version without a timeout
1782     */
1783    protected final void waitForAjaxRequestsToComplete(String timeout)
1784    {
1785        waitForAjaxRequestsToComplete();
1786    }
1787
1788
1789    /**
1790     * Waits until all active XHR requests (as noted by the t5/core/dom module)
1791     * have completed.
1792     *
1793     * @since 5.4
1794     */
1795    protected final void waitForAjaxRequestsToComplete()
1796    {
1797        // Ugly but necessary. Give the Ajax operation sufficient time to execute normally, then start
1798        // polling to see if it has complete.
1799        sleep(250);
1800
1801        // The t5/core/dom module tracks how many Ajax requests are active
1802        // and body[data-ajax-active] as appropriate.
1803
1804        for (int i = 0; i < 10; i++)
1805        {
1806            if (i > 0)
1807            {
1808                sleep(100);
1809            }
1810
1811            if (getCssCount("body[data-ajax-active='0']").equals(1))
1812            {
1813                return;
1814            }
1815        }
1816
1817        reportAndThrowAssertionError("Body 'data-ajax-active' attribute never reverted to '0'.");
1818    }
1819
1820    @Override
1821    public Number getCssCount(String str)
1822    {
1823        return selenium.getCssCount(str);
1824    }
1825
1826    protected static By convertLocator(String locator)
1827    {
1828        if (locator.startsWith("link="))
1829        {
1830            return By.linkText(locator.substring(5));
1831        }
1832        else if (locator.startsWith("css="))
1833        {
1834            return By.cssSelector(locator.substring(4));
1835        }
1836        else if (locator.startsWith("xpath="))
1837        {
1838            return By.xpath(locator.substring(6));
1839        }
1840        else if (locator.startsWith("id="))
1841        {
1842            return By.id(locator.substring(3));
1843        }
1844        else if (locator.startsWith("//"))
1845        {
1846            return By.xpath(locator);
1847        }
1848        else
1849        {
1850            return By.id(locator);
1851        }
1852    }
1853}