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