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    
015    package org.apache.tapestry5.test;
016    
017    import java.io.File;
018    import java.lang.reflect.Method;
019    
020    import org.openqa.selenium.server.RemoteControlConfiguration;
021    import org.openqa.selenium.server.SeleniumServer;
022    import org.testng.Assert;
023    import org.testng.ITestContext;
024    import org.testng.annotations.AfterClass;
025    import org.testng.annotations.AfterMethod;
026    import org.testng.annotations.AfterTest;
027    import org.testng.annotations.BeforeClass;
028    import org.testng.annotations.BeforeMethod;
029    import org.testng.annotations.BeforeTest;
030    import org.testng.xml.XmlTest;
031    
032    import com.thoughtworks.selenium.CommandProcessor;
033    import com.thoughtworks.selenium.DefaultSelenium;
034    import com.thoughtworks.selenium.HttpCommandProcessor;
035    import 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     */
044    public 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    }