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