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