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            public void run()
226            {
227                try
228                {
229                    LOGGER.info("Shutting down selenium client ...");
230
231                    try
232                    {
233                        selenium.stop();
234                    } catch (RuntimeException e)
235                    {
236                        LOGGER.error("Selenium client shutdown failure.", e);
237                    }
238
239                    LOGGER.info("Shutting down selenium server ...");
240
241                    try
242                    {
243                        seleniumServer.stop();
244                    } catch (RuntimeException e)
245                    {
246                        LOGGER.error("Selenium server shutdown failure.", e);
247                    }
248
249                    LOGGER.info("Shutting web server ...");
250
251                    try
252                    {
253                        stopWebServer.run();
254                    } catch (RuntimeException e)
255                    {
256                        LOGGER.error("Web server shutdown failure.", e);
257                    }
258
259                    // Output, at the end of the Test, any html capture or screen shots (this makes it much easier
260                    // to locate them at the end of the run; there's such a variance on where they end up based
261                    // on whether the tests are running from inside an IDE or via one of the command line
262                    // builds.
263
264                    errorReporter.writeOutputPaths();
265                } finally
266                {
267                    testContext.removeAttribute(TapestryTestConstants.BASE_URL_ATTRIBUTE);
268                    testContext.removeAttribute(TapestryTestConstants.SELENIUM_ATTRIBUTE);
269                    testContext.removeAttribute(TapestryTestConstants.ERROR_REPORTER_ATTRIBUTE);
270                    testContext.removeAttribute(TapestryTestConstants.COMMAND_PROCESSOR_ATTRIBUTE);
271                    testContext.removeAttribute(TapestryTestConstants.SHUTDOWN_ATTRIBUTE);
272                }
273            }
274        });
275    }
276
277    private final String getParameter(XmlTest xmlTest, String key, String defaultValue)
278    {
279        String value = xmlTest.getParameter(key);
280
281        return value != null ? value : defaultValue;
282    }
283
284    private final int getIntParameter(XmlTest xmlTest, String key, int defaultValue)
285    {
286        String value = xmlTest.getParameter(key);
287
288        return value != null ? Integer.parseInt(value) : defaultValue;
289    }
290
291    /**
292     * Like {@link #testStartup(org.testng.ITestContext, org.testng.xml.XmlTest)} , this may
293     * be called multiple times against multiple instances, but only does work the first time.
294     */
295    @AfterTest
296    public void testShutdown(ITestContext context)
297    {
298        // Likewise, this method should only be invoked once.
299        Runnable r = (Runnable) context.getAttribute(TapestryTestConstants.SHUTDOWN_ATTRIBUTE);
300
301        // This test is still useful, however, because testStartup() may not have completed properly,
302        // and the runnable is the last thing it puts into the test context.
303
304        if (r != null)
305        {
306            LOGGER.info("Shutting down integration test support ...");
307            r.run();
308        }
309    }
310
311    /**
312     * Invoked from {@link #testStartup(org.testng.ITestContext, org.testng.xml.XmlTest)} to launch the web
313     * server to be tested. The return value is a Runnable that can be invoked later to cleanly shut down the launched
314     * server at the end of the test.
315     *
316     * @param container
317     *         identifies which web server should be launched
318     * @param webAppFolder
319     *         path to the web application context
320     * @param contextPath
321     *         the path the context is mapped to, usually the empty string
322     * @param port
323     *         the port number the server should handle
324     * @param sslPort
325     *         the port number on which the server should handle secure requests
326     * @return Runnable used to shut down the server
327     * @throws Exception
328     */
329    protected Runnable launchWebServer(String container, String webAppFolder, String contextPath, int port, int sslPort)
330            throws Exception
331    {
332        final ServletContainerRunner runner = createWebServer(container, webAppFolder, contextPath, port, sslPort);
333
334        return new Runnable()
335        {
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    public void addCustomRequestHeader(String key, String value)
425    {
426        selenium.addCustomRequestHeader(key, value);
427    }
428
429    public void addLocationStrategy(String strategyName, String functionDefinition)
430    {
431        selenium.addLocationStrategy(strategyName, functionDefinition);
432    }
433
434    public void addScript(String scriptContent, String scriptTagId)
435    {
436        selenium.addScript(scriptContent, scriptTagId);
437    }
438
439    public void addSelection(String locator, String optionLocator)
440    {
441        selenium.addSelection(locator, optionLocator);
442    }
443
444    public void allowNativeXpath(String allow)
445    {
446        selenium.allowNativeXpath(allow);
447    }
448
449    public void altKeyDown()
450    {
451        selenium.altKeyDown();
452    }
453
454    public void altKeyUp()
455    {
456        selenium.altKeyUp();
457    }
458
459    public void answerOnNextPrompt(String answer)
460    {
461        selenium.answerOnNextPrompt(answer);
462    }
463
464    public void assignId(String locator, String identifier)
465    {
466        selenium.assignId(locator, identifier);
467    }
468
469    public void attachFile(String fieldLocator, String fileLocator)
470    {
471        selenium.attachFile(fieldLocator, fileLocator);
472    }
473
474    public void captureEntirePageScreenshot(String filename, String kwargs)
475    {
476        selenium.captureEntirePageScreenshot(filename, kwargs);
477    }
478
479    public String captureEntirePageScreenshotToString(String kwargs)
480    {
481        return selenium.captureEntirePageScreenshotToString(kwargs);
482    }
483
484    public String captureNetworkTraffic(String type)
485    {
486        return selenium.captureNetworkTraffic(type);
487    }
488
489    public void captureScreenshot(String filename)
490    {
491        selenium.captureScreenshot(filename);
492    }
493
494    public String captureScreenshotToString()
495    {
496        return selenium.captureScreenshotToString();
497    }
498
499    public void check(String locator)
500    {
501        selenium.check(locator);
502    }
503
504    public void chooseCancelOnNextConfirmation()
505    {
506        selenium.chooseCancelOnNextConfirmation();
507    }
508
509    public void chooseOkOnNextConfirmation()
510    {
511        selenium.chooseOkOnNextConfirmation();
512    }
513
514    public void click(String locator)
515    {
516        selenium.click(locator);
517    }
518
519    public void clickAt(String locator, String coordString)
520    {
521        selenium.clickAt(locator, coordString);
522    }
523
524    public void close()
525    {
526        selenium.close();
527    }
528
529    public void contextMenu(String locator)
530    {
531        selenium.contextMenu(locator);
532    }
533
534    public void contextMenuAt(String locator, String coordString)
535    {
536        selenium.contextMenuAt(locator, coordString);
537    }
538
539    public void controlKeyDown()
540    {
541        selenium.controlKeyDown();
542    }
543
544    public void controlKeyUp()
545    {
546        selenium.controlKeyUp();
547    }
548
549    public void createCookie(String nameValuePair, String optionsString)
550    {
551        selenium.createCookie(nameValuePair, optionsString);
552    }
553
554    public void deleteAllVisibleCookies()
555    {
556        selenium.deleteAllVisibleCookies();
557    }
558
559    public void deleteCookie(String name, String optionsString)
560    {
561        selenium.deleteCookie(name, optionsString);
562    }
563
564    public void deselectPopUp()
565    {
566        selenium.deselectPopUp();
567    }
568
569    public void doubleClick(String locator)
570    {
571        selenium.doubleClick(locator);
572    }
573
574    public void doubleClickAt(String locator, String coordString)
575    {
576        selenium.doubleClickAt(locator, coordString);
577    }
578
579    public void dragAndDrop(String locator, String movementsString)
580    {
581        selenium.dragAndDrop(locator, movementsString);
582    }
583
584    public void dragAndDropToObject(String locatorOfObjectToBeDragged, String locatorOfDragDestinationObject)
585    {
586        selenium.dragAndDropToObject(locatorOfObjectToBeDragged, locatorOfDragDestinationObject);
587    }
588
589    public void dragdrop(String locator, String movementsString)
590    {
591        selenium.dragdrop(locator, movementsString);
592    }
593
594    public void fireEvent(String locator, String eventName)
595    {
596        selenium.fireEvent(locator, eventName);
597    }
598
599    public void focus(String locator)
600    {
601        selenium.focus(locator);
602    }
603
604    public String getAlert()
605    {
606        return selenium.getAlert();
607    }
608
609    public String[] getAllButtons()
610    {
611        return selenium.getAllButtons();
612    }
613
614    public String[] getAllFields()
615    {
616        return selenium.getAllFields();
617    }
618
619    public String[] getAllLinks()
620    {
621        return selenium.getAllLinks();
622    }
623
624    public String[] getAllWindowIds()
625    {
626        return selenium.getAllWindowIds();
627    }
628
629    public String[] getAllWindowNames()
630    {
631        return selenium.getAllWindowNames();
632    }
633
634    public String[] getAllWindowTitles()
635    {
636        return selenium.getAllWindowTitles();
637    }
638
639    public String getAttribute(String attributeLocator)
640    {
641        return selenium.getAttribute(attributeLocator);
642    }
643
644    public String[] getAttributeFromAllWindows(String attributeName)
645    {
646        return selenium.getAttributeFromAllWindows(attributeName);
647    }
648
649    public String getBodyText()
650    {
651        return selenium.getBodyText();
652    }
653
654    public String getConfirmation()
655    {
656        return selenium.getConfirmation();
657    }
658
659    public String getCookie()
660    {
661        return selenium.getCookie();
662    }
663
664    public String getCookieByName(String name)
665    {
666        return selenium.getCookieByName(name);
667    }
668
669    public Number getCursorPosition(String locator)
670    {
671        return selenium.getCursorPosition(locator);
672    }
673
674    public Number getElementHeight(String locator)
675    {
676        return selenium.getElementHeight(locator);
677    }
678
679    public Number getElementIndex(String locator)
680    {
681        return selenium.getElementIndex(locator);
682    }
683
684    public Number getElementPositionLeft(String locator)
685    {
686        return selenium.getElementPositionLeft(locator);
687    }
688
689    public Number getElementPositionTop(String locator)
690    {
691        return selenium.getElementPositionTop(locator);
692    }
693
694    public Number getElementWidth(String locator)
695    {
696        return selenium.getElementWidth(locator);
697    }
698
699    public String getEval(String script)
700    {
701        return selenium.getEval(script);
702    }
703
704    public String getExpression(String expression)
705    {
706        return selenium.getExpression(expression);
707    }
708
709    public String getHtmlSource()
710    {
711        return selenium.getHtmlSource();
712    }
713
714    public String getLocation()
715    {
716        return selenium.getLocation();
717    }
718
719    public String getLog()
720    {
721        return selenium.getLog();
722    }
723
724    public Number getMouseSpeed()
725    {
726        return selenium.getMouseSpeed();
727    }
728
729    public String getPrompt()
730    {
731        return selenium.getPrompt();
732    }
733
734    public String getSelectedId(String selectLocator)
735    {
736        return selenium.getSelectedId(selectLocator);
737    }
738
739    public String[] getSelectedIds(String selectLocator)
740    {
741        return selenium.getSelectedIds(selectLocator);
742    }
743
744    public String getSelectedIndex(String selectLocator)
745    {
746        return selenium.getSelectedIndex(selectLocator);
747    }
748
749    public String[] getSelectedIndexes(String selectLocator)
750    {
751        return selenium.getSelectedIndexes(selectLocator);
752    }
753
754    public String getSelectedLabel(String selectLocator)
755    {
756        return selenium.getSelectedLabel(selectLocator);
757    }
758
759    public String[] getSelectedLabels(String selectLocator)
760    {
761        return selenium.getSelectedLabels(selectLocator);
762    }
763
764    public String getSelectedValue(String selectLocator)
765    {
766        return selenium.getSelectedValue(selectLocator);
767    }
768
769    public String[] getSelectedValues(String selectLocator)
770    {
771        return selenium.getSelectedValues(selectLocator);
772    }
773
774    public String[] getSelectOptions(String selectLocator)
775    {
776        return selenium.getSelectOptions(selectLocator);
777    }
778
779    public String getSpeed()
780    {
781        return selenium.getSpeed();
782    }
783
784    public String getTable(String tableCellAddress)
785    {
786        return selenium.getTable(tableCellAddress);
787    }
788
789    public String getText(String locator)
790    {
791        return selenium.getText(locator);
792    }
793
794    public String getTitle()
795    {
796        return selenium.getTitle();
797    }
798
799    public String getValue(String locator)
800    {
801        return selenium.getValue(locator);
802    }
803
804    public boolean getWhetherThisFrameMatchFrameExpression(String currentFrameString, String target)
805    {
806        return selenium.getWhetherThisFrameMatchFrameExpression(currentFrameString, target);
807    }
808
809    public boolean getWhetherThisWindowMatchWindowExpression(String currentWindowString, String target)
810    {
811        return selenium.getWhetherThisWindowMatchWindowExpression(currentWindowString, target);
812    }
813
814    public Number getXpathCount(String xpath)
815    {
816        return selenium.getXpathCount(xpath);
817    }
818
819    public void goBack()
820    {
821        selenium.goBack();
822    }
823
824    public void highlight(String locator)
825    {
826        selenium.highlight(locator);
827    }
828
829    public void ignoreAttributesWithoutValue(String ignore)
830    {
831        selenium.ignoreAttributesWithoutValue(ignore);
832    }
833
834    public boolean isAlertPresent()
835    {
836        return selenium.isAlertPresent();
837    }
838
839    public boolean isChecked(String locator)
840    {
841        return selenium.isChecked(locator);
842    }
843
844    public boolean isConfirmationPresent()
845    {
846        return selenium.isConfirmationPresent();
847    }
848
849    public boolean isCookiePresent(String name)
850    {
851        return selenium.isCookiePresent(name);
852    }
853
854    public boolean isEditable(String locator)
855    {
856        return selenium.isEditable(locator);
857    }
858
859    public boolean isElementPresent(String locator)
860    {
861        return selenium.isElementPresent(locator);
862    }
863
864    public boolean isOrdered(String locator1, String locator2)
865    {
866        return selenium.isOrdered(locator1, locator2);
867    }
868
869    public boolean isPromptPresent()
870    {
871        return selenium.isPromptPresent();
872    }
873
874    public boolean isSomethingSelected(String selectLocator)
875    {
876        return selenium.isSomethingSelected(selectLocator);
877    }
878
879    public boolean isTextPresent(String pattern)
880    {
881        return selenium.isTextPresent(pattern);
882    }
883
884    public boolean isVisible(String locator)
885    {
886        return selenium.isVisible(locator);
887    }
888
889    public void keyDown(String locator, String keySequence)
890    {
891        selenium.keyDown(locator, keySequence);
892    }
893
894    public void keyDownNative(String keycode)
895    {
896        selenium.keyDownNative(keycode);
897    }
898
899    public void keyPress(String locator, String keySequence)
900    {
901        selenium.keyPress(locator, keySequence);
902    }
903
904    public void keyPressNative(String keycode)
905    {
906        selenium.keyPressNative(keycode);
907    }
908
909    public void keyUp(String locator, String keySequence)
910    {
911        selenium.keyUp(locator, keySequence);
912    }
913
914    public void keyUpNative(String keycode)
915    {
916        selenium.keyUpNative(keycode);
917    }
918
919    public void metaKeyDown()
920    {
921        selenium.metaKeyDown();
922    }
923
924    public void metaKeyUp()
925    {
926        selenium.metaKeyUp();
927    }
928
929    public void mouseDown(String locator)
930    {
931        selenium.mouseDown(locator);
932    }
933
934    public void mouseDownAt(String locator, String coordString)
935    {
936        selenium.mouseDownAt(locator, coordString);
937    }
938
939    public void mouseDownRight(String locator)
940    {
941        selenium.mouseDownRight(locator);
942    }
943
944    public void mouseDownRightAt(String locator, String coordString)
945    {
946        selenium.mouseDownRightAt(locator, coordString);
947    }
948
949    public void mouseMove(String locator)
950    {
951        selenium.mouseMove(locator);
952    }
953
954    public void mouseMoveAt(String locator, String coordString)
955    {
956        selenium.mouseMoveAt(locator, coordString);
957    }
958
959    public void mouseOut(String locator)
960    {
961        selenium.mouseOut(locator);
962    }
963
964    public void mouseOver(String locator)
965    {
966        selenium.mouseOver(locator);
967    }
968
969    public void mouseUp(String locator)
970    {
971        selenium.mouseUp(locator);
972    }
973
974    public void mouseUpAt(String locator, String coordString)
975    {
976        selenium.mouseUpAt(locator, coordString);
977    }
978
979    public void mouseUpRight(String locator)
980    {
981        selenium.mouseUpRight(locator);
982    }
983
984    public void mouseUpRightAt(String locator, String coordString)
985    {
986        selenium.mouseUpRightAt(locator, coordString);
987    }
988
989    public void open(String url)
990    {
991        selenium.open(url);
992    }
993
994    public void open(String url, String ignoreResponseCode)
995    {
996        selenium.open(url, ignoreResponseCode);
997    }
998
999    public void openWindow(String url, String windowID)
1000    {
1001        selenium.openWindow(url, windowID);
1002    }
1003
1004    public void refresh()
1005    {
1006        selenium.refresh();
1007    }
1008
1009    public void removeAllSelections(String locator)
1010    {
1011        selenium.removeAllSelections(locator);
1012    }
1013
1014    public void removeScript(String scriptTagId)
1015    {
1016        selenium.removeScript(scriptTagId);
1017    }
1018
1019    public void removeSelection(String locator, String optionLocator)
1020    {
1021        selenium.removeSelection(locator, optionLocator);
1022    }
1023
1024    public String retrieveLastRemoteControlLogs()
1025    {
1026        return selenium.retrieveLastRemoteControlLogs();
1027    }
1028
1029    public void rollup(String rollupName, String kwargs)
1030    {
1031        selenium.rollup(rollupName, kwargs);
1032    }
1033
1034    public void runScript(String script)
1035    {
1036        selenium.runScript(script);
1037    }
1038
1039    public void select(String selectLocator, String optionLocator)
1040    {
1041        selenium.select(selectLocator, optionLocator);
1042    }
1043
1044    public void selectFrame(String locator)
1045    {
1046        selenium.selectFrame(locator);
1047    }
1048
1049    public void selectPopUp(String windowID)
1050    {
1051        selenium.selectPopUp(windowID);
1052    }
1053
1054    public void selectWindow(String windowID)
1055    {
1056        selenium.selectWindow(windowID);
1057    }
1058
1059    public void setBrowserLogLevel(String logLevel)
1060    {
1061        selenium.setBrowserLogLevel(logLevel);
1062    }
1063
1064    public void setContext(String context)
1065    {
1066        selenium.setContext(context);
1067    }
1068
1069    public void setCursorPosition(String locator, String position)
1070    {
1071        selenium.setCursorPosition(locator, position);
1072    }
1073
1074    public void setExtensionJs(String extensionJs)
1075    {
1076        selenium.setExtensionJs(extensionJs);
1077    }
1078
1079    public void setMouseSpeed(String pixels)
1080    {
1081        selenium.setMouseSpeed(pixels);
1082    }
1083
1084    public void setSpeed(String value)
1085    {
1086        selenium.setSpeed(value);
1087    }
1088
1089    public void setTimeout(String timeout)
1090    {
1091        selenium.setTimeout(timeout);
1092    }
1093
1094    public void shiftKeyDown()
1095    {
1096        selenium.shiftKeyDown();
1097    }
1098
1099    public void shiftKeyUp()
1100    {
1101        selenium.shiftKeyUp();
1102    }
1103
1104    public void showContextualBanner()
1105    {
1106        selenium.showContextualBanner();
1107    }
1108
1109    public void showContextualBanner(String className, String methodName)
1110    {
1111        selenium.showContextualBanner(className, methodName);
1112    }
1113
1114    public void shutDownSeleniumServer()
1115    {
1116        selenium.shutDownSeleniumServer();
1117    }
1118
1119    public void start()
1120    {
1121        selenium.start();
1122    }
1123
1124    public void start(Object optionsObject)
1125    {
1126        selenium.start(optionsObject);
1127    }
1128
1129    public void start(String optionsString)
1130    {
1131        selenium.start(optionsString);
1132    }
1133
1134    public void stop()
1135    {
1136        selenium.stop();
1137    }
1138
1139    public void submit(String formLocator)
1140    {
1141        selenium.submit(formLocator);
1142    }
1143
1144    public void type(String locator, String value)
1145    {
1146        selenium.type(locator, value);
1147    }
1148
1149    public void typeKeys(String locator, String value)
1150    {
1151        selenium.typeKeys(locator, value);
1152    }
1153
1154    public void uncheck(String locator)
1155    {
1156        selenium.uncheck(locator);
1157    }
1158
1159    public void useXpathLibrary(String libraryName)
1160    {
1161        selenium.useXpathLibrary(libraryName);
1162    }
1163
1164    public void waitForCondition(String script, String timeout)
1165    {
1166        selenium.waitForCondition(script, timeout);
1167    }
1168
1169    public void waitForFrameToLoad(String frameAddress, String timeout)
1170    {
1171        selenium.waitForFrameToLoad(frameAddress, timeout);
1172    }
1173
1174    /**
1175     * Waits for page  to load, then waits for initialization to finish, which is recognized by the {@code data-page-initialized} attribute
1176     * being set to true on the body element. Polls at increasing intervals, for up-to 30 seconds (that's extraordinarily long, but helps sometimes
1177     * when manually debugging a page that doesn't have the floating console enabled)..
1178     */
1179    public void waitForPageToLoad(String timeout)
1180    {
1181        selenium.waitForPageToLoad(timeout);
1182
1183        // In a limited number of cases, a "page" is an container error page or raw HTML content
1184        // that does not include the body element and data-page-initialized element. In those cases,
1185        // there will never be page initialization in the Tapestry sense and we return immediately.
1186
1187        if (!isElementPresent("css=body[data-page-initialized]"))
1188        {
1189            return;
1190        }
1191
1192        final long pollingStartTime = System.currentTimeMillis();
1193
1194        long sleepTime = 20;
1195
1196        while (true)
1197        {
1198            if (isElementPresent("css=body[data-page-initialized='true']"))
1199            {
1200                return;
1201            }
1202
1203            if ((System.currentTimeMillis() - pollingStartTime) > 30000)
1204            {
1205                reportAndThrowAssertionError("Page did not finish initializing after 30 seconds.");
1206            }
1207
1208            sleep(sleepTime);
1209
1210            sleepTime *= 2;
1211        }
1212    }
1213
1214    public void waitForPopUp(String windowID, String timeout)
1215    {
1216        selenium.waitForPopUp(windowID, timeout);
1217    }
1218
1219    public void windowFocus()
1220    {
1221        selenium.windowFocus();
1222    }
1223
1224    public void windowMaximize()
1225    {
1226        selenium.windowMaximize();
1227    }
1228
1229    // ---------------------------------------------------------------------
1230    // End of delegate methods
1231    // ---------------------------------------------------------------------
1232
1233    /**
1234     * Formats a message from the provided arguments, which is written to System.err. In addition,
1235     * captures the AUT's markup, screenshot, and a report to the output directory.
1236     *
1237     * @param message
1238     * @param arguments
1239     * @since 5.4
1240     */
1241    protected final void reportAndThrowAssertionError(String message, Object... arguments)
1242    {
1243        StringBuilder builder = new StringBuilder(5000);
1244
1245        String formatted = String.format(message, arguments);
1246
1247        builder.append(formatted);
1248
1249        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
1250
1251        StringBuilder buffer = new StringBuilder(5000);
1252
1253        boolean enabled = false;
1254
1255        for (StackTraceElement e : stackTrace)
1256        {
1257            if (enabled)
1258            {
1259                buffer.append("\n- ");
1260                buffer.append(e);
1261                continue;
1262            }
1263
1264            if (e.getMethodName().equals("reportAndThrowAssertionError"))
1265            {
1266                enabled = true;
1267            }
1268        }
1269
1270        writeErrorReport(builder.toString());
1271
1272        throw new AssertionError(formatted);
1273    }
1274
1275    protected final void unreachable()
1276    {
1277        reportAndThrowAssertionError("An unreachable statement was reached.");
1278    }
1279
1280    /**
1281     * Open the {@linkplain #getBaseURL()}, and waits for the page to load.
1282     */
1283    protected final void openBaseURL()
1284    {
1285        open(baseURL);
1286
1287        waitForPageToLoad();
1288    }
1289
1290    /**
1291     * Asserts the text of an element, identified by the locator.
1292     *
1293     * @param locator
1294     *         identifies the element whose text value is to be asserted
1295     * @param expected
1296     *         expected value for the element's text
1297     */
1298    protected final void assertText(String locator, String expected)
1299    {
1300        String actual = null;
1301
1302        try
1303        {
1304            actual = getText(locator);
1305        } catch (RuntimeException ex)
1306        {
1307            System.err.printf("Error accessing %s: %s, in:\n\n%s\n\n", locator, ex.getMessage(), getHtmlSource());
1308
1309            throw ex;
1310        }
1311
1312        if (actual.equals(expected))
1313        {
1314            return;
1315        }
1316
1317        reportAndThrowAssertionError("%s was '%s' not '%s'", locator, actual, expected);
1318    }
1319
1320    protected final void assertTextPresent(String... text)
1321    {
1322        for (String item : text)
1323        {
1324            if (isTextPresent(item))
1325            {
1326                continue;
1327            }
1328
1329            reportAndThrowAssertionError("Page did not contain '" + item + "'.");
1330        }
1331    }
1332
1333    /**
1334     * Assets that each string provided is present somewhere in the current document.
1335     *
1336     * @param expected
1337     *         string expected to be present
1338     */
1339    protected final void assertSourcePresent(String... expected)
1340    {
1341        String source = getHtmlSource();
1342
1343        for (String snippet : expected)
1344        {
1345            if (source.contains(snippet))
1346            {
1347                continue;
1348            }
1349
1350            reportAndThrowAssertionError("Page did not contain source '" + snippet + "'.");
1351        }
1352    }
1353
1354    /**
1355     * Click a link identified by a locator, then wait for the resulting page to load.
1356     * This is not useful for Ajax updates, just normal full-page refreshes.
1357     *
1358     * @param locator
1359     *         identifies the link to click
1360     */
1361    protected final void clickAndWait(String locator)
1362    {
1363        click(locator);
1364
1365        waitForPageToLoad();
1366    }
1367
1368    /**
1369     * Waits for the page to load (up to 15 seconds). This is invoked after clicking on an element
1370     * that forces a full page refresh.
1371     */
1372    protected final void waitForPageToLoad()
1373    {
1374        waitForPageToLoad(PAGE_LOAD_TIMEOUT);
1375    }
1376
1377    /**
1378     * Used when the locator identifies an attribute, not an element.
1379     *
1380     * @param locator
1381     *         identifies the attribute whose value is to be asserted
1382     * @param expected
1383     *         expected value for the attribute
1384     */
1385    protected final void assertAttribute(String locator, String expected)
1386    {
1387        String actual = null;
1388
1389        try
1390        {
1391            actual = getAttribute(locator);
1392        } catch (RuntimeException ex)
1393        {
1394
1395            reportAndThrowAssertionError("Error accessing %s: %s", locator, ex.getMessage());
1396        }
1397
1398        if (actual.equals(expected))
1399        {
1400            return;
1401        }
1402
1403        reportAndThrowAssertionError("%s was '%s' not '%s'", locator, actual, expected);
1404    }
1405
1406    /**
1407     * Assets that the value in the field matches the expectation
1408     *
1409     * @param locator
1410     *         identifies the field
1411     * @param expected
1412     *         expected value for the field
1413     * @since 5.3
1414     */
1415    protected final void assertFieldValue(String locator, String expected)
1416    {
1417        try
1418        {
1419            assertEquals(getValue(locator), expected);
1420        } catch (AssertionError ex)
1421        {
1422            reportAndThrowAssertionError("Failure accessing %s: %s", locator, ex);
1423        }
1424    }
1425
1426    /**
1427     * Opens the base URL, then clicks through a series of links to get to a desired application
1428     * state.
1429     *
1430     * @since 5.3
1431     */
1432    protected final void openLinks(String... linkText)
1433    {
1434        openBaseURL();
1435
1436        for (String text : linkText)
1437        {
1438            clickAndWait("link=" + text);
1439        }
1440    }
1441
1442    /**
1443     * Sleeps for the indicated number of seconds.
1444     *
1445     * @since 5.3
1446     */
1447    protected final void sleep(long millis)
1448    {
1449        try
1450        {
1451            Thread.sleep(millis);
1452        } catch (InterruptedException ex)
1453        {
1454            // Ignore.
1455        }
1456    }
1457
1458    /**
1459     * Waits, up to the page load limit for an element (identified by a CSS rule) to exist
1460     * (it is not assured that the element will be visible).
1461     * <p/>
1462     * This implementation only works if the application provides a function onto the
1463     * window object:  "testSupport.findCSSMatchCount()" which accepts a CSS rule and returns the number
1464     * of matching elements.
1465     *
1466     * @param cssSelector
1467     *         used to locate the element
1468     * @since 5.3
1469     * @deprecated Deprecated in 5.4 with no replacement
1470     */
1471    protected void waitForCSSSelectedElementToAppear(String cssSelector)
1472    {
1473        String condition = String.format("window.testSupport.findCSSMatchCount(\"%s\") > 0", cssSelector);
1474
1475        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1476    }
1477
1478    /**
1479     * Waits for the element with the given client-side id to be present in the DOM (
1480     * does not assure that the element is visible).
1481     *
1482     * @param elementId
1483     *         identifies the element
1484     * @since 5.3
1485     */
1486    protected final void waitForElementToAppear(String elementId)
1487    {
1488
1489        String condition = String.format("selenium.browserbot.getCurrentWindow().document.getElementById(\"%s\")", elementId);
1490
1491        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1492    }
1493
1494    /**
1495     * Waits for the element to be removed from the DOM.
1496     * <p/>
1497     * <p/>
1498     * This implementation depends on window being extended with testSupport.isNotVisible().
1499     *
1500     * @param elementId
1501     *         client-side id of element
1502     * @since 5.3
1503     * @deprecated Deprecated in 5.4 with no replacement
1504     */
1505    protected final void waitForElementToDisappear(String elementId)
1506    {
1507        String condition = String.format("selenium.browserbot.getCurrentWindow().testSupport.doesNotExist(\"%s\")", elementId);
1508
1509        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1510    }
1511
1512    /**
1513     * Waits for the element specified by the selector to become visible
1514     * Note that waitForElementToAppear waits for the element to be present in the dom, visible or not. waitForVisible
1515     * waits for an element that already exists in the dom to become visible.
1516     *
1517     * @param selector
1518     *         element selector
1519     * @since 5.3
1520     */
1521    protected final void waitForVisible(String selector)
1522    {
1523        String condition = String.format("selenium.isVisible(\"%s\")", selector);
1524
1525        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1526    }
1527
1528    /**
1529     * Waits for the element specified by the selector to become invisible
1530     * Note that waitForElementToDisappear waits for the element to be absent from the dom, visible or not. waitForInvisible
1531     * waits for an existing element to become invisible.
1532     *
1533     * @param selector
1534     *         element selector
1535     * @since 5.3
1536     */
1537    protected final void waitForInvisible(String selector)
1538    {
1539        String condition = String.format("!selenium.isVisible(\"%s\")", selector);
1540
1541        waitForCondition(condition, PAGE_LOAD_TIMEOUT);
1542    }
1543
1544    /**
1545     * Asserts that the current page's title matches the expected value.
1546     *
1547     * @param expected
1548     *         value for title
1549     * @since 5.3
1550     */
1551    protected final void assertTitle(String expected)
1552    {
1553        try
1554        {
1555            assertEquals(getTitle(), expected);
1556        } catch (AssertionError ex)
1557        {
1558            reportAndThrowAssertionError("Unexpected title: %s", ex);
1559
1560            throw ex;
1561        }
1562    }
1563
1564    /**
1565     * Waits until all active XHR requests are completed.
1566     *
1567     * @param timeout
1568     *         timeout to wait for (no longer used)
1569     * @since 5.3
1570     * @deprecated Deprecated in 5.4 in favor of the version without a timeout
1571     */
1572    protected final void waitForAjaxRequestsToComplete(String timeout)
1573    {
1574        waitForAjaxRequestsToComplete();
1575    }
1576
1577
1578    /**
1579     * Waits until all active XHR requests (as noted by the t5/core/dom module)
1580     * have completed.  Waits up to 500 ms.
1581     *
1582     * @since 5.4
1583     */
1584    protected final void waitForAjaxRequestsToComplete() {
1585        for (int i = 0; i < 6; i++)
1586        {
1587            if (i > 0)
1588            {
1589                sleep(100);
1590            }
1591
1592            // The t5/core/dom module tracks how many Ajax requests are active
1593            // and updates this property as appropriate.
1594            if (getAttribute("//body/@data-ajax-active").equals("false"))
1595            {
1596                return;
1597            }
1598        }
1599
1600        reportAndThrowAssertionError("Body 'data-ajax-active' attribute never reverted to 'false'.");
1601    }
1602
1603    public Number getCssCount(String str)
1604    {
1605        return selenium.getCssCount(str);
1606    }
1607
1608}