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