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