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