View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.web.test;
17  
18  import java.io.IOException;
19  import java.net.URL;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import org.apache.jasper.tagplugins.jstl.core.Url;
25  import org.apache.log4j.Logger;
26  import org.junit.After;
27  import org.junit.Before;
28  import org.kuali.rice.kns.UserSession;
29  import org.kuali.rice.kns.service.DocumentService;
30  import org.kuali.rice.kns.service.KNSServiceLocator;
31  import org.kuali.rice.kns.util.GlobalVariables;
32  import org.kuali.rice.test.web.HtmlUnitUtil;
33  
34  import com.gargoylesoftware.htmlunit.ElementNotFoundException;
35  import com.gargoylesoftware.htmlunit.Page;
36  import com.gargoylesoftware.htmlunit.WebClient;
37  import com.gargoylesoftware.htmlunit.html.BaseFrame;
38  import com.gargoylesoftware.htmlunit.html.ClickableElement;
39  import com.gargoylesoftware.htmlunit.html.DomNode;
40  import com.gargoylesoftware.htmlunit.html.FrameWindow;
41  import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
42  import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
43  import com.gargoylesoftware.htmlunit.html.HtmlElement;
44  import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
45  import com.gargoylesoftware.htmlunit.html.HtmlForm;
46  import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput;
47  import com.gargoylesoftware.htmlunit.html.HtmlImageInput;
48  import com.gargoylesoftware.htmlunit.html.HtmlInlineFrame;
49  import com.gargoylesoftware.htmlunit.html.HtmlListItem;
50  import com.gargoylesoftware.htmlunit.html.HtmlOption;
51  import com.gargoylesoftware.htmlunit.html.HtmlPage;
52  import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
53  import com.gargoylesoftware.htmlunit.html.HtmlSelect;
54  import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
55  import com.gargoylesoftware.htmlunit.html.HtmlTable;
56  import com.gargoylesoftware.htmlunit.html.HtmlTableBody;
57  import com.gargoylesoftware.htmlunit.html.HtmlTableCell;
58  import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
59  import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
60  import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
61  
62  /**
63   * This is base class for HtmlUnit tests based on work Lin-Long Shyu originally did for KRA and
64   * that Don Barre enhanced.
65   */
66  public abstract class WebTestBase extends ServerTestBase {
67      private static final Logger LOG = Logger.getLogger(WebTestBase.class);
68  
69      protected static String HELP_PAGE_TITLE = "Kuali :: Kuali Help";
70      protected static String USER_NETWORK_ID = "admin";
71  
72      protected WebClient webClient = null;
73      protected DocumentService documentService = null;
74  
75      private HtmlPage portalPage;
76  
77      /**
78       * Web test setup overloading. Sets up Portal page access.
79       *
80       * @see org.kuali.rice.web.test.WebTestBase#setUp()
81       */
82      @Before
83      public void setUp() throws Exception {
84          super.setUp();
85          GlobalVariables.setUserSession(new UserSession(USER_NETWORK_ID));
86          documentService = KNSServiceLocator.getDocumentService();
87          webClient = new WebClient();
88  
89          setPortalPage(buildPageFromUrl(HtmlUnitUtil.BASE_URL, HTML_PAGE_TITLE_TEXT));
90  
91      }
92  
93      /**
94       * Web test tear down overloading.
95       *
96       * @see org.kuali.rice.web.test.WebTestBase#tearDown()
97       */
98      @After
99      public void tearDown() throws Exception {
100         super.tearDown();
101         GlobalVariables.setUserSession(null);
102         documentService = null;
103         webClient = null;
104     }
105 
106     /**
107      * Set the KRA Portal Web Page. The portal page is the starting point
108      * for many (or all) HtmlUnit tests in order to simulate a user.
109      *
110      * @param portalPage <code>{@link HtmlPage}</code> instance for the portal page
111      */
112     protected final void setPortalPage(HtmlPage portalPage) {
113         this.portalPage = portalPage;
114     }
115 
116     /**
117      * Get the KRA Portal Web Page. The portal page is the starting point
118      * for many (or all) HtmlUnit tests in order to simulate a user.
119      * @return the KRA Portal Web Page.
120      * @throws IOException
121      */
122     protected final HtmlPage getPortalPage() throws IOException {
123         return this.portalPage;
124     }
125 
126     /**
127      * Take a given <code>{@link String}</code> url and get a <code>{@link HtmlPage}</code> instance
128      *  from it. This method actually overloads <code>{@link #buildPageFromUrl(URL, String)}</code>
129      *
130      * @param url <code>{@link String}</code> instance of url
131      * @param title to compare against and verify the page is valid
132      * @return HtmlPage
133      * @throws IOException
134      */
135     protected final HtmlPage buildPageFromUrl(String url, String title) throws IOException {
136         return buildPageFromUrl(new URL(url), title);
137     }
138 
139     /**
140      * Take a given <code>{@link URL}</code> url and get a <code>{@link HtmlPage}</code> instance
141      *  from it.
142      *
143      * @param url <code>{@link Url}</code> instance of url
144      * @param title to compare against and verify the page is valid
145      * @return HtmlPage
146      * @throws IOException
147      */
148     protected final HtmlPage buildPageFromUrl(URL url, String title) throws IOException {
149         HtmlPage retval = (HtmlPage) webClient.getPage(url);
150         assertEquals(title, retval.getTitleText());
151         return retval;
152     }
153 
154     /**
155      * Simulate clicking on an HTML element in the web page.  To find
156      * the HTML element, the following algorithm is used:
157      * <ol>
158      * <li>Search for a HTML element with an <b>id</b> attribute that matches the given id.</li>
159      * <li>If not found, search for the first HTML element with a <b>name</b> attribute that matches.</li>
160      * <li>If not found, search for the first HTML element with a <b>title</b> attribute that matches.</li>
161      * </ol>
162      * If an HTML element is not found or the element is not clickable,
163      * an assertion will cause the test case to fail.
164      *
165      * Using any of the <b>clickOn()</b> methods is the preferred way to click on an HTML element
166      * due to the Login process.  If the Login web page is encountered, the user will be
167      * automatically logged in and next web page is returned.
168      *
169      * @param page the HTML web page.
170      * @param id the <i>id</i> of the HTML element to click on.
171      * @return the next web page after clicking on the HTML element.
172      * @throws IOException
173      */
174     protected final HtmlPage clickOn(HtmlPage page, String id) throws IOException {
175         return clickOn(page, id, null);
176     }
177 
178     /**
179      * Simulate clicking on an HTML element in the web page.  To find
180      * the HTML element, the following algorithm is used:
181      * <ol>
182      * <li>Search for a HTML element with an <b>id</b> attribute that matches the given id.</li>
183      * <li>If not found, search for the first HTML element with a <b>name</b> attribute that matches.</li>
184      * <li>If not found, search for the first HTML element with a <b>title</b> attribute that matches.</li>
185      * </ol>
186      * If an HTML element is not found or the element is not clickable,
187      * an assertion will cause the test case to fail.  Also, if the <i>nextPageTitle</i>
188      * is not null, the test case will fail if the next web page doesn't have the
189      * expected title.
190      *
191      * Using any of the <b>clickOn()</b> methods is the preferred way to click on an HTML element
192      * due to the Login process.  If the Login web page is encountered, the user will be
193      * automatically logged in and next web page is returned.
194      *
195      * @param page the HTML web page.
196      * @param id the <i>id</i> of the HTML element to click on.
197      * @param nextPageTitle the expected title of the new web page (may be null).
198      * @return the next web page after clicking on the HTML element.
199      * @throws IOException
200      */
201     protected final HtmlPage clickOn(HtmlPage page, String id, String nextPageTitle) throws IOException {
202         HtmlElement element = getElement(page, id);
203         assertTrue(id +" not found",element != null);
204         assertTrue(element instanceof ClickableElement);
205 
206         return clickOn((ClickableElement) element, nextPageTitle);
207     }
208 
209     /**
210      * Simulate clicking on an HTML element in the web page.
211      *
212      * Using any of the <b>clickOn()</b> methods is the preferred way to click on an HTML element
213      * due to the Login process.  If the Login web page is encountered, the user will be
214      * automatically logged in and next web page is returned.
215      *
216      * @param element the HTML element to click on.
217      * @return the next web page after clicking on the HTML element.
218      * @throws IOException
219      */
220     protected final HtmlPage clickOn(HtmlElement element) throws IOException {
221         return clickOn(element, null);
222     }
223 
224     /**
225      * Simulate clicking on an HTML element in the web page.
226      *
227      * Using any of the <b>clickOn()</b> methods is the preferred way to click on an HTML element
228      * due to the Login process.  If the Login web page is encountered, the user will be
229      * automatically logged in and next web page is returned.
230      *
231      * If the <i>nextPageTitle</i> is not null, the test case will fail if the next web page
232      * doesn't have the expected title.
233      *
234      * @param element the HTML element to click on.
235      * @param nextPageTitle the expected title of the new web page (may be null).
236      * @return the next web page after clicking on the HTML element.
237      * @throws IOException
238      */
239     protected final HtmlPage clickOn(HtmlElement element, String nextPageTitle) throws IOException {
240         assertTrue(element instanceof ClickableElement);
241 
242         ClickableElement clickable = (ClickableElement) element;
243         Page nextPage = clickable.click();
244 
245         assertTrue(nextPage != null);
246         assertTrue(nextPage instanceof HtmlPage);
247 
248         HtmlPage htmlNextPage = (HtmlPage) nextPage;
249 
250         htmlNextPage = checkForLoginPage(htmlNextPage);
251 
252         if (nextPageTitle != null) {
253             assertEquals(nextPageTitle, htmlNextPage.getTitleText());
254         }
255 
256         return htmlNextPage;
257     }
258 
259     /**
260      * Simulate clicking on a Lookup icon.
261      * This method is similar to the <b>clickOn()</b> methods except that it
262      * is used for clicking on Lookup icon. This is because it is difficult to
263      * find a Lookup HTML element based upon its full name, i.e. the name is
264      * incredibly long and cryptic.  To find the Lookup HTML element, the name
265      * attribute of the HTML lookup elements are examined to see if they contain
266      * the given <i>id</i>.  As soon as a match is found, that HTML element
267      * is clicked on.  Users should be sure to pick a part of the Lookup's name
268      * that is unique for that Lookup.
269      *
270      * If the Lookup HTML element is not found, an assertion will cause
271      * the test to fail.
272      *
273      * @param page the HTML web page.
274      * @param tag identifies the Lookup HTML element.
275      * @return the Lookup web page.
276      * @throws IOException
277      */
278     protected final HtmlPage clickOnLookup(HtmlPage page, String tag) throws IOException {
279         HtmlImageInput element = getLookup(page, tag);
280         assertTrue(element != null);
281 
282         return clickOn(element);
283     }
284 
285     /**
286      * Asserts that the given web page contains the given text.
287      * @param page the HTML web page.
288      * @param text the string to look for in the web page.
289      */
290     protected final void assertContains(HtmlPage page, String text) {
291         assertTrue(page.asText().contains(text));
292     }
293 
294     /**
295      * Asserts that the given web page does <b>not</b> contain the given text.
296      * @param page the HTML web page.
297      * @param text the string to look for in the web page.
298      */
299     protected final void assertDoesNotContain(HtmlPage page, String text) {
300         assertTrue(!page.asText().contains(text));
301     }
302 
303     /**
304      * Asserts that the given HTML element contains the given text.
305      * @param element the HTML element.
306      * @param text the string to look for in the HTML element.
307      */
308     protected final void assertContains(HtmlElement element, String text) {
309         assertTrue(element.asText().contains(text));
310     }
311 
312     /**
313      * Asserts that the given HTML element does <b>not</b> contain the given text.
314      * @param element the HTML element.
315      * @param text the string to look for in the HTML element.
316      */
317     protected final void assertDoesNotContain(HtmlElement element, String text) {
318         assertTrue(!element.asText().contains(text));
319     }
320 
321     /**
322      * Asserts that a Select control has the given number of options.
323      * @param page the HTML web page.
324      * @param elementId the value of the HTML element's id attribute.
325      * @param size the number of options that must be in the list of options.
326      */
327     protected final void assertSelectOptionsSize(HtmlPage page, String elementId, int size) {
328         HtmlElement element = page.getHtmlElementById(elementId);
329         assertTrue(element != null);
330 
331         if (element instanceof HtmlSelect) {
332             HtmlSelect selectField = (HtmlSelect) element;
333             assertEquals(selectField.getOptionSize(), size);
334         }
335         else {
336             assertTrue("Not a Select Field", false);
337         }
338     }
339 
340     /**
341      * Performs a single value Lookup.  The following occurs on a lookup:
342      * <ol>
343      * <li>The Lookup icon is clicked on.</li>
344      * <li>In the Lookup web page, the search button is clicked on.</li>
345      * <li>The first item in the results is returned.</li>
346      * <li>The web page resulting from clicking on "return value" is returned.</li>
347      * </ol>
348      * To find the Lookup HTML element, the name attribute of the HTML lookup
349      * elements are examined to see if they contain the given <i>id</i>.  As soon as
350      * a match is found, that HTML element is clicked on.  Users should be sure to pick
351      * a part of the Lookup's name that is unique for that Lookup.
352      *
353      * The test will fail for any of the following reasons:
354      * <ul>
355      * <li>The HTML lookup element was not found.</li>
356      * <li>There was no data returned in the search.</li>
357      * </ul>
358      *
359      * @param page the original web page with the Lookup icon.
360      * @param tag identifies the Lookup icon to click on.
361      * @return the resulting web page.
362      * @throws IOException
363      */
364     protected final HtmlPage lookup(HtmlPage page, String tag) throws IOException {
365        return lookup(page, tag, null, null);
366     }
367 
368     /**
369      * Performs a single value Lookup.  The following occurs on a lookup:
370      * <ol>
371      * <li>The Lookup icon is clicked on.</li>
372      * <li>In the Lookup web page, the given field is filled in with the given value.</li>
373      * <li>In the Lookup web page, the search button is clicked on.</li>
374      * <li>The first item in the results is returned.</li>
375      * <li>The web page resulting from clicking on "return value" is returned.</li>
376      * </ol>
377      * To find the Lookup HTML element, the name attribute of the HTML lookup
378      * elements are examined to see if they contain the given <i>id</i>.  As soon as
379      * a match is found, that HTML element is clicked on.  Users should be sure to pick
380      * a part of the Lookup's name that is unique for that Lookup.
381      *
382      * The test will fail for any of the following reasons:
383      * <ul>
384      * <li>The HTML lookup element was not found.</li>
385      * <li>The search field HTML element was not found.</li>
386      * <li>There was no data returned in the search.</li>
387      * </ul>
388      *
389      * @param page the original web page with the Lookup icon.
390      * @param tag identifies the Lookup icon to click on.
391      * @param searchFieldId the id of the HTML field element (may be null).
392      * @param searchValue the value to insert into the search field (may be null if searchFieldId is null).
393      * @return the resulting web page.
394      * @throws IOException
395      */
396     protected final HtmlPage lookup(HtmlPage page, String tag, String searchFieldId, String searchValue) throws IOException {
397 
398         HtmlPage lookupPage = clickOnLookup(page, tag);
399 
400         if (searchFieldId != null) {
401             assertTrue(searchValue != null);
402             setFieldValue(lookupPage, searchFieldId, searchValue);
403         }
404 
405         // click on the search button
406         HtmlImageInput searchBtn = (HtmlImageInput) getElement(lookupPage, "methodToCall.search", "search", "search");
407         HtmlPage resultsPage = (HtmlPage) searchBtn.click();
408 
409         HtmlTable table = (HtmlTable) getElement(resultsPage, "row");
410         assertTrue("No data to return", table != null);
411 
412         HtmlTableBody body = (HtmlTableBody) table.getBodies().get(0);
413         List rows = body.getRows();
414 
415         HtmlTableRow row = (HtmlTableRow) rows.get(0);
416         List cells = row.getCells();
417         HtmlTableCell cell = (HtmlTableCell) cells.get(cells.size() - 1);
418         HtmlAnchor anchor = (HtmlAnchor) getFirstChild(cell);
419         page = (HtmlPage) anchor.click();
420 
421         return page;
422     }
423 
424     /**
425      * Performs a multi value Lookup.  The following occurs on a lookup:
426      * <ol>
427      * <li>The Lookup icon is clicked on.</li>
428      * <li>In the Lookup web page, the search button is clicked on.</li>
429      * <li>The "select all" button is clicked on.</li>
430      * <li>The web page resulting from clicking on "return selected" is returned.</li>
431      * </ol>
432      * To find the Lookup HTML element, the name attribute of the HTML lookup
433      * elements are examined to see if they contain the given <i>id</i>.  As soon as
434      * a match is found, that HTML element is clicked on.  Users should be sure to pick
435      * a part of the Lookup's name that is unique for that Lookup.
436      *
437      * The test will fail if the Lookup HTML element is not found.
438      *
439      * @param page the original web page with the Lookup icon.
440      * @param tag identifies the Lookup icon to click on.
441      * @return the resulting web page.
442      * @throws IOException
443      */
444     protected final HtmlPage multiLookup(HtmlPage page, String tag) throws IOException {
445         return multiLookup(page, tag, null, null);
446     }
447 
448     /**
449      * Performs a multi value Lookup.  The following occurs on a lookup:
450      * <ol>
451      * <li>The Lookup icon is clicked on.</li>
452      * <li>The search field is filled in with the given search value.</li>
453      * <li>In the Lookup web page, the search button is clicked on.</li>
454      * <li>The "select all" button is clicked on.</li>
455      * <li>The web page resulting from clicking on "return selected" is returned.</li>
456      * </ol>
457      * To find the Lookup HTML element, the name attribute of the HTML lookup
458      * elements are examined to see if they contain the given <i>id</i>.  As soon as
459      * a match is found, that HTML element is clicked on.  Users should be sure to pick
460      * a part of the Lookup's name that is unique for that Lookup.
461      *
462      * The test will fail for any of the following reasons:
463      * <ul>
464      * <li>The HTML lookup element was not found.</li>
465      * <li>The search field HTML element was not found.</li>
466      * </ul>
467      *
468      * @param page the original web page with the Lookup icon.
469      * @param tag identifies the Lookup icon to click on.
470      * @param searchFieldId the id of the HTML field element (may be null).
471      * @param searchValue the value to insert into the search field (may be null if searchFieldId is null).
472      * @return the resulting web page.
473      * @throws IOException
474      */
475     protected final HtmlPage multiLookup(HtmlPage page, String tag, String searchFieldId, String searchValue) throws IOException {
476         HtmlPage lookupPage = clickOnLookup(page, tag);
477 
478         if (searchFieldId != null) {
479             assertTrue(searchValue != null);
480             setFieldValue(lookupPage, searchFieldId, searchValue);
481         }
482 
483         // click on the search button
484         HtmlImageInput searchBtn = (HtmlImageInput) getElement(lookupPage, "methodToCall.search", "search", "search");
485         HtmlPage resultsPage = (HtmlPage) searchBtn.click();
486 
487         HtmlImageInput selectAllBtn = (HtmlImageInput) getElement(resultsPage, "methodToCall.selectAll.(::;false;::).x", null, null);
488         HtmlPage selectedPage = (HtmlPage) selectAllBtn.click();
489 
490         HtmlImageInput returnAllBtn = (HtmlImageInput) getElement(selectedPage, "methodToCall.prepareToReturnSelectedResults.x", null, null);
491         HtmlPage returnPage = (HtmlPage) returnAllBtn.click();
492 
493         return returnPage;
494     }
495 
496     /**
497      * Set the value of a control field.  The control field must be a
498      * text, text area, hidden, checkbox, radio button, or single-select field.
499      *
500      * For a checkbox field, the only legal values are "on" and "off".
501      *
502      * The test will fail for any of the following reasons:
503      * <ul>
504      * <li>The HTML control element was not found.</li>
505      * <li>The control is not a text, text area, hidden, checkbox, radio button, or select field.</li>
506      * </ul>
507      *
508      * @param page the HTML web page.
509      * @param fieldId the id of the HTML element.
510      * @param fieldValue the value to set the control to.
511      */
512     protected final void setFieldValue(HtmlPage page, String fieldId, String fieldValue) {
513         HtmlElement element = getElement(page, fieldId);
514         assertTrue(element != null);
515 
516         if (element instanceof HtmlTextInput) {
517             HtmlTextInput textField = (HtmlTextInput) element;
518             textField.setValueAttribute(fieldValue);
519         }
520         else if (element instanceof HtmlTextArea) {
521             HtmlTextArea textAreaField = (HtmlTextArea) element;
522             textAreaField.setText(fieldValue);
523         }
524         else if (element instanceof HtmlHiddenInput) {
525             HtmlHiddenInput hiddenField = (HtmlHiddenInput) element;
526             hiddenField.setValueAttribute(fieldValue);
527         }
528         else if (element instanceof HtmlSelect) {
529             HtmlSelect selectField = (HtmlSelect) element;
530             selectField.setSelectedAttribute(fieldValue, true);
531         }
532         else if (element instanceof HtmlCheckBoxInput) {
533             HtmlCheckBoxInput checkboxField = (HtmlCheckBoxInput) element;
534             if (fieldValue.equals("on")) {
535                 checkboxField.setChecked(true);
536             }
537             else if (fieldValue.equals("off")) {
538                 checkboxField.setChecked(false);
539             }
540             else {
541                 assertTrue("Invalid checkbox value", false);
542             }
543         }
544         else if (element instanceof HtmlFileInput) {
545             HtmlFileInput fileInputField = (HtmlFileInput) element;
546             fileInputField.setValueAttribute(fieldValue);
547         }
548         else if (element instanceof HtmlRadioButtonInput) {
549             List<HtmlElement> elements = getAllElementsByName(page, fieldId, false);
550             for (HtmlElement child : elements) {
551                 assertTrue(child instanceof HtmlRadioButtonInput);
552                 HtmlRadioButtonInput radioBtn = (HtmlRadioButtonInput) child;
553                 if (radioBtn.getValueAttribute().equals(fieldValue)) {
554                     radioBtn.setChecked(true);
555                     break;
556                 }
557             }
558         }
559         else {
560             assertTrue("Unknown control field", false);
561         }
562     }
563 
564     /**
565      * Gets the current value of a control field.
566      *
567      * For a checkbox field, the only legal values are "on" and "off".
568      *
569      * The test will fail for any of the following reasons:
570      * <ul>
571      * <li>The HTML control element was not found.</li>
572      * <li>The control is not a text, text area, hidden, checkbox, radio button, or select field.</li>
573      * </ul>
574      *
575      * @param page the HTML web page.
576      * @param fieldId the id of the HTML element.
577      * @return the current value.
578      */
579     protected final String getFieldValue(HtmlPage page, String fieldId) {
580         String fieldValue = null;
581 
582         HtmlElement element = getElement(page, fieldId);
583         assertTrue(fieldId + " not found", element != null);
584 
585         if (element instanceof HtmlTextInput) {
586             HtmlTextInput textField = (HtmlTextInput) element;
587             fieldValue = textField.getValueAttribute();
588         }
589         else if (element instanceof HtmlTextArea) {
590             HtmlTextArea textAreaField = (HtmlTextArea) element;
591             fieldValue = textAreaField.getText();
592         }
593         else if (element instanceof HtmlHiddenInput) {
594             HtmlHiddenInput hiddenField = (HtmlHiddenInput) element;
595             fieldValue = hiddenField.getValueAttribute();
596         }
597         else if (element instanceof HtmlSelect) {
598             HtmlSelect selectField = (HtmlSelect) element;
599             fieldValue = ((HtmlOption) selectField.getSelectedOptions().get(0)).getValueAttribute();
600         }
601         else if (element instanceof HtmlCheckBoxInput) {
602             HtmlCheckBoxInput checkboxField = (HtmlCheckBoxInput) element;
603             fieldValue = checkboxField.isChecked() ? "on" : "off";
604         }
605         else if (element instanceof HtmlRadioButtonInput) {
606             List<HtmlElement> elements = getAllElementsByName(page, fieldId, false);
607             for (HtmlElement child : elements) {
608                 assertTrue(child instanceof HtmlRadioButtonInput);
609                 HtmlRadioButtonInput radioBtn = (HtmlRadioButtonInput) child;
610                 if (radioBtn.isChecked()) {
611                     fieldValue = radioBtn.getValueAttribute();
612                     break;
613                 }
614             }
615         }
616         else {
617             assertTrue("Unknown control field", false);
618         }
619 
620         return fieldValue;
621     }
622 
623     /**
624      * Gets the default value of a control field.
625      *
626      * For a checkbox field, the only legal values are "on" and "off".
627      *
628      * The test will fail for any of the following reasons:
629      * <ul>
630      * <li>The HTML control element was not found.</li>
631      * <li>The control is not a text, text area, hidden, checkbox, radio button, or select field.</li>
632      * </ul>
633      *
634      * @param page the HTML web page.
635      * @param fieldId the id of the HTML element.
636      * @return the default value.
637      */
638     protected final String getDefaultFieldValue(HtmlPage page, String fieldId) {
639         String fieldValue = null;
640 
641         HtmlElement element = getElement(page, fieldId);
642         assertTrue(element != null);
643 
644         if (element instanceof HtmlTextInput) {
645             HtmlTextInput textField = (HtmlTextInput) element;
646             fieldValue = textField.getDefaultValue();
647         }
648         else if (element instanceof HtmlTextArea) {
649             HtmlTextArea textAreaField = (HtmlTextArea) element;
650             fieldValue = textAreaField.getDefaultValue();
651         }
652         else if (element instanceof HtmlHiddenInput) {
653             HtmlHiddenInput hiddenField = (HtmlHiddenInput) element;
654             fieldValue = hiddenField.getDefaultValue();
655         }
656         else if (element instanceof HtmlSelect) {
657             HtmlSelect selectField = (HtmlSelect) element;
658             fieldValue = selectField.getDefaultValue();
659         }
660         else if (element instanceof HtmlCheckBoxInput) {
661             HtmlCheckBoxInput checkboxField = (HtmlCheckBoxInput) element;
662             fieldValue = checkboxField.isDefaultChecked() ? "on" : "off";
663         }
664         else if (element instanceof HtmlRadioButtonInput) {
665             List<HtmlElement> elements = getAllElementsByName(page, fieldId, false);
666             for (HtmlElement child : elements) {
667                 assertTrue(child instanceof HtmlRadioButtonInput);
668                 HtmlRadioButtonInput radioBtn = (HtmlRadioButtonInput) child;
669                 if (radioBtn.isDefaultChecked()) {
670                     fieldValue = radioBtn.getValueAttribute();
671                     break;
672                 }
673             }
674         }
675         else {
676             assertTrue("Unknown control field", false);
677         }
678 
679         return fieldValue;
680     }
681 
682     /**
683      * Checks all of the Help hyperlinks on a web page.  Each hyperlink
684      * with a <b>helpWindow</b> target is clicked on to verify that the
685      * the Help web page is displayed.  The contents of the help page is
686      * not examined.  The test will only fail if a Help hyperlink does
687      * not bring up a Help web page.  This is useful to verify that there
688      * are no broken links.
689      *
690      * @param page the HTML page to check.
691      * @throws IOException
692      */
693     protected final void checkHelpLinks(HtmlPage page) throws IOException {
694         List<HtmlAnchor> anchors = findHelpLinks(page);
695         for (HtmlAnchor anchor : anchors) {
696             HtmlPage helpPage = (HtmlPage) anchor.click();
697             assertTrue(HELP_PAGE_TITLE.equals(helpPage.getTitleText()));
698         }
699     }
700 
701     /**
702      * Checks the Expanded Text Area.  Many text area controls have a corresponding
703      * expanded text area icon (pencil) for displaying another web page with a larger
704      * text area box.  This method does the following to verify that the expanded text
705      * area feature is working properly.
706      * <ol>
707      * <li>The text area is set to the <i>text1</i> value.</li>
708      * <li>The Expanded Text Area icon is clicked on.</li>
709      * <li>The text in the expanded text area web page is examined to verify
710      *     that it is equal to <i>text1</i>.</li>
711      * <li>The text area is changed to <i>text2</i>.</li>
712      * <li>The "save" button is clicked on.</li>
713      * <li>The resulting web page is examined to verify that the original
714      *     text area has changed to the value of <i>text2</i>.</li>
715      * </ol>
716      *
717      * The test will fail for any of the following reasons:
718      * <ul>
719      * <li>The HTML text area element was not found.</li>
720      * <li>The Expanded Text Area icon HTML element was not found.</li>
721      * <li>The setting of <i>text1</i> did not transfer to the Expanded Text Area web page.</li>
722      * <li>The saving of <i>text2</i> did not transfer to the original text area.</li>
723      * </ul>
724      *
725      * @param page the HTML web page with the text area control.
726      * @param id identifies the text area (not its corresponding icon).
727      * @param text1 the string to set the original text area to.
728      * @param text2 the string to set in the Expanded Text Area web page.
729      * @return the resulting web page from saving <i>text2</i>.
730      * @throws IOException
731      */
732     protected final HtmlPage checkExpandedTextArea(HtmlPage page, String id, String text1, String text2) throws IOException {
733         boolean javascriptEnabled = webClient.isJavaScriptEnabled();
734 
735         webClient.setJavaScriptEnabled(false);
736 
737         setFieldValue(page, id, text1);
738 
739         HtmlElement field = getElement(page, id);
740         assertTrue(field != null);
741 
742         ClickableElement btn = (ClickableElement) this.getNextSibling(field);
743         assertTrue(btn != null);
744 
745         HtmlPage textPage = clickOn(btn);
746 
747         assertEquals(getFieldValue(textPage, id), text1);
748 
749         setFieldValue(textPage, id, text2);
750         HtmlPage returnPage = clickOn(textPage, "save");
751 
752         assertEquals(getFieldValue(returnPage, id), text2);
753 
754         webClient.setJavaScriptEnabled(javascriptEnabled);
755 
756         return returnPage;
757     }
758 
759     /**
760      * Gets the error messages for a specific panel.  If an operation
761      * results in an error, those errors are displayed in specific panel,
762      * i.e. tab.  Each panel is contained within an HTML div tag that has
763      * a unique id, i.e. the HTML id attribute.
764      *
765      * @param page the HTML web page.
766      * @param panelId the unique id of the panel.
767      * @return the list of error strings (may be empty).
768      */
769     protected final List<String> getErrors(HtmlPage page, String panelId) {
770         List<String> errors = new ArrayList<String>();
771 
772         HtmlElement panelDiv = getElement(page, panelId);
773         assertTrue(panelDiv != null);
774 
775         HtmlElement errorDiv = getElementByClass(panelDiv, "error");
776         if (errorDiv != null) {
777             Iterator iterator = errorDiv.getAllHtmlChildElements();
778             while (iterator.hasNext()) {
779                 HtmlElement child = (HtmlElement) iterator.next();
780                 if (child instanceof HtmlListItem) {
781                     HtmlListItem li = (HtmlListItem) child;
782                     errors.add(li.asText());
783                 }
784             }
785         }
786 
787         return errors;
788     }
789 
790     /**
791      * Determines if any of the errors contains the given text string.
792      *
793      * @param errors the list of errors.
794      * @param text the string to compare against.
795      * @return true if any of errors contains the text string; otherwise false.
796      */
797     protected final boolean containsError(List<String> errors, String text) {
798         for (String error : errors) {
799             if (error.contains(text)) {
800                 return true;
801             }
802         }
803         return false;
804     }
805 
806     /**
807      * Checks for the Login web page.  The Login web page can be
808      * obtained due to a request for another web page.  This method
809      * logs the user into the system and returns the expected web page.
810      *
811      * @param page the HTML web page.
812      * @return the same HTML web page or the one resulting from the login.
813      * @throws IOException
814      */
815     private HtmlPage checkForLoginPage(HtmlPage page) throws IOException {
816         if (page.getTitleText().equals("Login")) {
817             HtmlForm form = (HtmlForm) page.getForms().get(0);
818             setFieldValue(page, "__login_user", USER_NETWORK_ID);
819             HtmlSubmitInput loginBtn = (HtmlSubmitInput) form.getInputByValue("Login");
820             page = (HtmlPage) loginBtn.click();
821             if (page.getTitleText().equals("Login")) {
822                 page = (HtmlPage) loginBtn.click();
823             }
824         }
825         return page;
826     }
827 
828     /**
829      * Gets an HTML element in the web page.
830      *
831      * Since some HTML elements don't use the id attribute, those HTML elements
832      * can found by searching for elements with the given values for the <b>name</b>,
833      * <b>value</b>, and <b>title</b> attributes.  The first HTML element that
834      * matches is returned.
835      *
836      * HTML web pages may contain Inline Frames (iframes) which are not expanded
837      * within HtmlUnit.  The inline frames contain inner web pages that must
838      * also be searched.
839      *
840      * @param page the HTML web page.
841      * @param name the value for the name attribute (may be null).
842      * @param value the value for the value attribute (may be null).
843      * @param title the value for the title attribute (may be null).
844      * @return the HTML element or null if not found.
845      */
846     protected final HtmlElement getElement(HtmlPage page, String name, String value, String title) {
847         HtmlElement element = getElement(page.getDocumentElement(), name, value, title);
848 
849         if (element == null) {
850             List<HtmlPage> innerPages = getInnerPages(page);
851             for (HtmlPage innerPage : innerPages) {
852                 element = getElement(innerPage, name, value, title);
853                 if (element != null) break;
854             }
855         }
856 
857         return element;
858     }
859 
860     /**
861      * Gets an HTML element in an HTML element.
862      *
863      * Since some HTML elements don't use the id attribute, those HTML elements
864      * can found by searching for elements with the given values for the <b>name</b>,
865      * <b>value</b>, and <b>title</b> attributes.  The first HTML element that
866      * matches is returned.
867      *
868      * @param element the HTML element.
869      * @param name the value for the name attribute (may be null).
870      * @param value the value for the value attribute (may be null).
871      * @param title the value for the title attribute (may be null).
872      * @return the HTML element or null if not found.
873      */
874     private HtmlElement getElement(HtmlElement element, String name, String value, String title) {
875         Iterator iterator = element.getAllHtmlChildElements();
876         while (iterator.hasNext()) {
877             HtmlElement child = (HtmlElement) iterator.next();
878             String nameValue = child.getAttributeValue("name");
879             String valueValue = child.getAttributeValue("value");
880             String titleValue = child.getAttributeValue("title");
881             if ((name == null || name.equals(nameValue)) &&
882                 (value == null || value.equals(valueValue)) &&
883                 (title == null || title.equals(titleValue))) {
884                 return child;
885             }
886         }
887         return null;
888     }
889 
890     /**
891      * Gets an HTML element in the web page.
892      *
893      * To find the HTML element, the following algorithm is used:
894      * <ol>
895      * <li>Search for a HTML element with an <b>id</b> attribute that matches the given id.</li>
896      * <li>If not found, search for the first HTML element with a <b>name</b> attribute that matches.</li>
897      * <li>If not found, search for the first HTML element with a <b>title</b> attribute that matches.</li>
898      * </ol>
899      *
900      * @param page the HTML web page to search.
901      * @param id the id of the HTML attribute.
902      * @return the HTML element or null if not found.
903      */
904     protected final HtmlElement getElement(HtmlPage page, String id) {
905         HtmlElement element = getElementById(page, id);
906         if (element == null) {
907             element = getElementByName(page, id);
908             if (element == null) {
909                 element = getElementByTitle(page, id);
910             }
911         }
912         return element;
913     }
914 
915     /**
916      * Gets an HTML element in the web page.  Searches the web page for
917      * an HTML element whose id attribute matches the given id.
918      *
919      * HTML web pages may contain Inline Frames (iframes) which are not expanded
920      * within HtmlUnit.  The inline frames contain inner web pages that must
921      * also be searched.
922      *
923      * @param page the HTML web page to search.
924      * @param id the id to search for.
925      * @return the HTML element or null if not found.
926      */
927     protected final HtmlElement getElementById(HtmlPage page, String id) {
928         HtmlElement element = null;
929         try {
930             element = page.getHtmlElementById(id);
931         } catch (ElementNotFoundException ex) {
932             List<HtmlPage> innerPages = getInnerPages(page);
933             for (HtmlPage innerPage : innerPages) {
934                 element = getElementById(innerPage, id);
935                 if (element != null) break;
936             }
937         }
938         return element;
939     }
940 
941     /**
942      * Gets an HTML element in the web page.  Searches the web page for
943      * the first HTML element whose name attribute matches the given name.
944      *
945      * HTML web pages may contain Inline Frames (iframes) which are not expanded
946      * within HtmlUnit.  The inline frames contain inner web pages that must
947      * also be searched.
948      *
949      * @param page the HTML web page to search.
950      * @param name the name to search for.
951      * @return the HTML element or null if not found.
952      */
953     protected final HtmlElement getElementByName(HtmlPage page, String name) {
954         return getElementByName(page, name, false);
955     }
956 
957     /**
958      * Gets an HTML element in a parent HTML element.  Searches the parent HTML
959      * element for the first HTML element whose name attribute matches the given name.
960      *
961      * @param element the parent HTML element to search.
962      * @param name the name to search for.
963      * @return the HTML element or null if not found.
964      */
965     protected final HtmlElement getElementByName(HtmlElement element, String name) {
966         return getElementByName(element, name, false);
967     }
968 
969     /**
970      * Gets an HTML element in the web page.  Searches the web page for
971      * the first HTML element whose name attribute matches the given name.
972      *
973      * HTML web pages may contain Inline Frames (iframes) which are not expanded
974      * within HtmlUnit.  The inline frames contain inner web pages that must
975      * also be searched.
976      *
977      * @param page the HTML web page to search.
978      * @param name the name to search for.
979      * @param startsWith if true, only match against the start of the name.
980      * @return the HTML element or null if not found.
981      */
982     protected final HtmlElement getElementByName(HtmlPage page, String name, boolean startsWith) {
983         HtmlElement element = getElementByName(page.getDocumentElement(), name, startsWith);
984 
985         if (element == null) {
986             List<HtmlPage> innerPages = getInnerPages(page);
987             for (HtmlPage innerPage : innerPages) {
988                 element = getElementByName(innerPage, name, startsWith);
989                 if (element != null) break;
990             }
991         }
992 
993         return element;
994     }
995 
996     /**
997      * Gets an HTML element in a parent HTML element.  Searches the parent HTML
998      * element for the first HTML element whose name attribute matches the given name.
999      *
1000      * @param element the parent HTML element to search.
1001      * @param name the name to search for.
1002      * @param startsWith if true, only match against the start of the name.
1003      * @return the HTML element or null if not found.
1004      */
1005     protected final HtmlElement getElementByName(HtmlElement element, String name, boolean startsWith) {
1006         Iterator iterator = element.getAllHtmlChildElements();
1007         while (iterator.hasNext()) {
1008             HtmlElement e = (HtmlElement) iterator.next();
1009             String value = e.getAttributeValue("name");
1010             if (!startsWith && name.equals(value)) {
1011                 return e;
1012             } else if (startsWith && value != null && value.startsWith(name)) {
1013                 return e;
1014             }
1015         }
1016         return null;
1017     }
1018 
1019     /**
1020      * Gets an HTML element in the web page.  Searches the web page for
1021      * the first HTML element whose title attribute matches the given title.
1022      *
1023      * HTML web pages may contain Inline Frames (iframes) which are not expanded
1024      * within HtmlUnit.  The inline frames contain inner web pages that must
1025      * also be searched.
1026      *
1027      * @param page the HTML web page to search.
1028      * @param title the title to search for.
1029      * @return the HTML element or null if not found.
1030      */
1031     protected final HtmlElement getElementByTitle(HtmlPage page, String title) {
1032         HtmlElement element = getElementByTitle(page.getDocumentElement(), title);
1033 
1034         if (element == null) {
1035             List<HtmlPage> innerPages = getInnerPages(page);
1036             for (HtmlPage innerPage : innerPages) {
1037                 element = getElementByTitle(innerPage, title);
1038                 if (element != null) break;
1039             }
1040         }
1041         return element;
1042     }
1043 
1044     /**
1045      * Gets an HTML element in a parent HTML element.  Searches the parent HTML
1046      * element for the first HTML element whose title attribute matches the given title.
1047      *
1048      * @param element the parent HTML element to search.
1049      * @param title the title to search for.
1050      * @return the HTML element or null if not found.
1051      */
1052     protected final HtmlElement getElementByTitle(HtmlElement element, String title) {
1053         Iterator iterator = element.getAllHtmlChildElements();
1054         while (iterator.hasNext()) {
1055             HtmlElement e = (HtmlElement) iterator.next();
1056             String value = e.getAttributeValue("title");
1057             if (title.equals(value)) {
1058                 return e;
1059             }
1060         }
1061         return null;
1062     }
1063 
1064     /**
1065      * Gets an HTML element in a parent HTML element.  Searches the parent HTML
1066      * element for the first HTML element whose class attribute matches the given classname.
1067      *
1068      * @param element the parent HTML element to search.
1069      * @param classname the classname to search for.
1070      * @return the HTML element or null if not found.
1071      */
1072     protected final HtmlElement getElementByClass(HtmlElement element, String classname) {
1073         Iterator iterator = element.getAllHtmlChildElements();
1074         while (iterator.hasNext()) {
1075             HtmlElement e = (HtmlElement) iterator.next();
1076             String value = e.getAttributeValue("class");
1077             if (classname.equals(value)) {
1078                 return e;
1079             }
1080         }
1081         return null;
1082     }
1083 
1084     /**
1085      * Gets a Lookup HTML element.  The searching for Lookup HTML elements
1086      * is a special case.  This is because it lacks an id attribute and because
1087      * the value of its name attribute is extremely long and cryptic. To find
1088      * a Lookup element, the value of the name attribute is examined to see if
1089      * it contains the given id.  Lookup HTML elements always contain some data
1090      * in the name attribute that is specific to the Lookup.
1091      *
1092      * HTML web pages may contain Inline Frames (iframes) which are not expanded
1093      * within HtmlUnit.  The inline frames contain inner web pages that must
1094      * also be searched.
1095      *
1096      * @param page the HTML web page to search.
1097      * @param tag the tag to compare against Lookup HTML name attributes.
1098      * @return the Lookup's HTML element or null if not found.
1099      */
1100     protected final HtmlImageInput getLookup(HtmlPage page, String tag) {
1101         HtmlImageInput element = getLookup(page.getDocumentElement(), tag);
1102 
1103         List<HtmlPage> innerPages = getInnerPages(page);
1104         for (Iterator<HtmlPage> page_it = innerPages.iterator(); page_it.hasNext() && element == null;) {
1105             HtmlPage innerPage = page_it.next();
1106             element = getLookup(innerPage, tag);
1107         }
1108 
1109         return element;
1110     }
1111 
1112     /**
1113      * Gets a Lookup HTML element.  The searching for Lookup HTML elements
1114      * is a special case.  This is because it lacks an id attribute and because
1115      * the value of its name attribute is extremely long and cryptic. To find
1116      * a Lookup element, the value of the name attribute is examined to see if
1117      * it contains the given id.  Lookup HTML elements always contain some data
1118      * in the name attribute that is specific to the Lookup.
1119      *
1120      * @param element the parent HTML element to search.
1121      * @param tag the tag to compare against Lookup HTML name attributes.
1122      * @return the Lookup's HTML element or null if not found.
1123      */
1124     private final HtmlImageInput getLookup(HtmlElement element, String tag) {
1125         for (Iterator<HtmlElement> iterator = element.getAllHtmlChildElements(); iterator.hasNext();) {
1126             HtmlElement e = iterator.next();
1127             if (e instanceof HtmlImageInput) {
1128                 String name = e.getAttributeValue("name");
1129                 LOG.info("Found name attribute " + name);
1130                 if (name != null && name.startsWith("methodToCall.performLookup") && name.contains(tag)) {
1131                     return (HtmlImageInput) e;
1132                 }
1133             }
1134         }
1135         return null;
1136     }
1137 
1138     /**
1139      * Gets a HTML Table element.  The search begins within the
1140      * first HTML element identified by <i>id</i>.  If there are
1141      * multiple tables in the HTML element, only one is returned
1142      * and it is undefined which one it will be.
1143      *
1144      * @param page the HTML web page.
1145      * @param id identifies the HTML element to begin searching in.
1146      * @return the HTML table or null if not found.
1147      */
1148     protected final HtmlTable getTable(HtmlPage page, String id) {
1149         HtmlTable table = null;
1150         HtmlElement element = getElement(page, id);
1151         assertTrue(element != null);
1152 
1153         if (element instanceof HtmlTable) {
1154             table = (HtmlTable) element;
1155         }
1156         else {
1157             Iterator iterator = element.getAllHtmlChildElements();
1158             while (iterator.hasNext()) {
1159                 HtmlElement e = (HtmlElement) iterator.next();
1160                 if (e instanceof HtmlTable) {
1161                     table = (HtmlTable) e;
1162                     break;
1163                 }
1164             }
1165         }
1166         return table;
1167     }
1168 
1169     /**
1170      * Gets an Anchor (hyperlink) element whose HREF attribute string contains
1171      * the given tag string.
1172      *
1173      * @param page the web page.
1174      * @param tag the string to look for in HREF.
1175      * @return the HTML Anchor element or null if not found.
1176      */
1177     protected final HtmlAnchor getAnchor(HtmlPage page, String tag) {
1178         HtmlAnchor element = getAnchor(page.getDocumentElement(), tag);
1179 
1180         if (element == null) {
1181             List<HtmlPage> innerPages = getInnerPages(page);
1182             for (HtmlPage innerPage : innerPages) {
1183                 element = getAnchor(innerPage, tag);
1184                 if (element != null) break;
1185             }
1186         }
1187 
1188         return element;
1189     }
1190 
1191     /**
1192      * Gets an Anchor (hyperlink) element whose HREF attribute string contains
1193      * the given tag string.
1194      *
1195      * @param element the HTML element to look in.
1196      * @param tag the string to look for in HREF.
1197      * @return the HTML Anchor element or null if not found.
1198      */
1199     private HtmlAnchor getAnchor(HtmlElement element, String tag) {
1200         Iterator iterator = element.getAllHtmlChildElements();
1201         while (iterator.hasNext()) {
1202             HtmlElement e = (HtmlElement) iterator.next();
1203             if (e instanceof HtmlAnchor) {
1204                 String href = e.getAttributeValue("href");
1205                 if (href != null && href.contains(tag)) {
1206                     return (HtmlAnchor) e;
1207                 }
1208             }
1209         }
1210         return null;
1211     }
1212 
1213     /**
1214      * Finds all of the Help HTML elements (hyperlinks) in a web page.
1215      *
1216      * HTML web pages may contain Inline Frames (iframes) which are not expanded
1217      * within HtmlUnit.  The inline frames contain inner web pages that must
1218      * also be searched.
1219      *
1220      * @param page the HTML web page.
1221      * @return the list of Help HTML elements.
1222      */
1223     protected final List<HtmlAnchor> findHelpLinks(HtmlPage page) {
1224         List <HtmlAnchor> helpLinks = findHelpLinks(page.getDocumentElement());
1225 
1226         List<HtmlPage> innerPages = getInnerPages(page);
1227         for (HtmlPage innerPage : innerPages) {
1228             helpLinks.addAll(findHelpLinks(innerPage));
1229         }
1230 
1231         return helpLinks;
1232     }
1233 
1234     /**
1235      * Finds all of the Help HTML elements (hyperlinks) in a parent HTML element.
1236      * In Kuali, all Help hyperlinks have a target window with value of "helpWindow".
1237      *
1238      * @param element the parent HTML element.
1239      * @return the list of Help HTML elements.
1240      */
1241     private List<HtmlAnchor> findHelpLinks(HtmlElement element) {
1242         List<HtmlAnchor> helpLinks = new ArrayList<HtmlAnchor>();
1243         Iterator iterator = element.getAllHtmlChildElements();
1244         while (iterator.hasNext()) {
1245             HtmlElement e = (HtmlElement) iterator.next();
1246             if (e instanceof HtmlAnchor) {
1247                 String target = e.getAttributeValue("target");
1248                 if (target != null && target.equals("helpWindow")) {
1249                     helpLinks.add((HtmlAnchor) e);
1250                 }
1251             }
1252         }
1253         return helpLinks;
1254     }
1255 
1256     /**
1257      * Gets an HTML element in the web page.  Searches the web page for
1258      * the first HTML element whose name attribute matches the given name.
1259      *
1260      * HTML web pages may contain Inline Frames (iframes) which are not expanded
1261      * within HtmlUnit.  The inline frames contain inner web pages that must
1262      * also be searched.
1263      *
1264      * @param page the HTML web page to search.
1265      * @param name the name to search for.
1266      * @param startsWith if true, only match against the start of the name.
1267      * @return the HTML element or null if not found.
1268      */
1269     protected final List<HtmlElement> getAllElementsByName(HtmlPage page, String name, boolean startsWith) {
1270         List<HtmlElement> elements = getAllElementsByName(page.getDocumentElement(), name, startsWith);
1271 
1272         List<HtmlPage> innerPages = getInnerPages(page);
1273         for (HtmlPage innerPage : innerPages) {
1274             elements.addAll(getAllElementsByName(innerPage, name, startsWith));
1275         }
1276 
1277         return elements;
1278     }
1279 
1280     /**
1281      * Gets an HTML element in a parent HTML element.  Searches the parent HTML
1282      * element for the first HTML element whose name attribute matches the given name.
1283      *
1284      * @param element the parent HTML element to search.
1285      * @param name the name to search for.
1286      * @param startsWith if true, only match against the start of the name.
1287      * @return the HTML element or null if not found.
1288      */
1289     protected final List<HtmlElement> getAllElementsByName(HtmlElement element, String name, boolean startsWith) {
1290         List<HtmlElement> elements = new ArrayList<HtmlElement>();
1291         Iterator iterator = element.getAllHtmlChildElements();
1292         while (iterator.hasNext()) {
1293             HtmlElement e = (HtmlElement) iterator.next();
1294             String value = e.getAttributeValue("name");
1295             if (!startsWith && name.equals(value)) {
1296                 elements.add(e);
1297             } else if (startsWith && value != null && value.startsWith(name)) {
1298                 elements.add(e);
1299             }
1300         }
1301         return elements;
1302     }
1303 
1304     /**
1305      * Gets the list of inner web pages.  Inner web pages are contained
1306      * within Inline Frames (iframes).
1307      *
1308      * @param page the HTML web page to search for inner web pages.
1309      * @return the list of inner HTML web pages.
1310      */
1311     protected final List<HtmlPage> getInnerPages(HtmlPage page) {
1312         List<HtmlPage> innerPages = new ArrayList<HtmlPage>();
1313 
1314         List frames = page.getFrames();
1315         for (int i = 0; i < frames.size(); i++) {
1316             FrameWindow frame = (FrameWindow) frames.get(i);
1317             BaseFrame baseFrame = frame.getFrameElement();
1318             if (baseFrame instanceof HtmlInlineFrame) {
1319                 HtmlInlineFrame iframe = (HtmlInlineFrame) baseFrame;
1320                 Page epage = iframe.getEnclosedPage();
1321                 if (epage instanceof HtmlPage) {
1322                     innerPages.add((HtmlPage) epage);
1323                 }
1324             }
1325         }
1326         return innerPages;
1327     }
1328 
1329     /**
1330      * Get the first HTML child element.
1331      *
1332      * @param element the parent HTML element.
1333      * @return the first child HTML element or null if there are no children.
1334      */
1335     protected final HtmlElement getFirstChild(HtmlElement element) {
1336         HtmlElement firstChild = null;
1337         Iterator iterator = element.getChildElementsIterator();
1338         if (iterator.hasNext()) {
1339             firstChild = (HtmlElement) iterator.next();
1340         }
1341         return firstChild;
1342     }
1343 
1344     /**
1345      * Get the next sibling HTML element.
1346      *
1347      * @param element an HTML element.
1348      * @return the next sibling HTML element or null if there is none.
1349      */
1350     protected final HtmlElement getNextSibling(HtmlElement element) {
1351         HtmlElement sibling = null;
1352         DomNode node = element.getParentNode();
1353         if (node instanceof HtmlElement) {
1354             HtmlElement parent = (HtmlElement) node;
1355             Iterator iterator = parent.getChildElementsIterator();
1356             while (iterator.hasNext()) {
1357                 HtmlElement e = (HtmlElement) iterator.next();
1358                 if (e == element) {
1359                     if (iterator.hasNext()) {
1360                         sibling = (HtmlElement) iterator.next();
1361                     }
1362                     break;
1363                 }
1364             }
1365         }
1366         return sibling;
1367     }
1368 
1369     /**
1370      * Do a document search looking for the a specific document based upon its docNbr.
1371      * The search begins at the Portal Page and then navigates to the Doc Search Page.
1372      * The given docNbr is added to the search field and search is performed.  If the
1373      * doc is found, it's hyperlink is clicked and the resulting page is returned.
1374      *
1375      * @param docNbr the docNbr to search for
1376      * @return the document's page or null if not found.
1377      * @throws IOException
1378      */
1379     protected final HtmlPage docSearch(String docNbr) throws IOException {
1380         HtmlPage docPage = null;
1381         HtmlPage portalPage = getPortalPage();
1382         HtmlPage docSearchPage = clickOn(portalPage, "Document Search");
1383 
1384         setFieldValue(docSearchPage, "criteria.routeHeaderId", docNbr);
1385         docSearchPage = clickOn(docSearchPage, "methodToCall.doDocSearch");
1386         if (docSearchPage.asText().contains("Nothing found to display.")) {
1387             docPage = null;
1388         } else {
1389             HtmlAnchor hyperlink = getAnchor(docSearchPage, docNbr);
1390             assertNotNull(hyperlink);
1391             docPage = clickOn(hyperlink);
1392         }
1393         return docPage;
1394     }
1395 
1396     /**
1397      * Gets the docNbr from a document's web page.
1398      * The docNbr is expected to be in an HTML table
1399      * is the division labeled "headerarea".
1400      *
1401      * @param page the documen's web page
1402      * @return the document's docNbr
1403      */
1404     protected final String getDocNbr(HtmlPage page) {
1405         HtmlTable table = getTable(page, "headerarea");
1406         HtmlTableRow row = table.getRow(0);
1407         HtmlTableCell cell = row.getCell(1);
1408         return cell.asText().trim();
1409     }
1410 
1411     /**
1412      * Save a document, i.e. click on Save button.
1413      *
1414      * @param page the document web page
1415      * @return the next page
1416      * @throws IOException
1417      */
1418     protected final HtmlPage saveDoc(HtmlPage page) throws IOException {
1419         return clickOn(page, "save");
1420     }
1421 
1422     /**
1423      * Closes a document.  If queried to save the document, the
1424      * "No" button is clicked on.
1425      *
1426      * @param page the document web page
1427      * @return the next page
1428      * @throws IOException
1429      */
1430     protected final HtmlPage closeDoc(HtmlPage page) throws IOException {
1431         HtmlPage nextPage = clickOn(page, "close");
1432         if (nextPage.asText().contains("Would you like to save this document before you close it")) {
1433             nextPage = clickOn(nextPage, "methodToCall.processAnswer.button1");
1434         }
1435         return nextPage;
1436     }
1437 
1438     /**
1439      * Saves and closes a document.  After saving and closing the document,
1440      * the document's number is returned.  It would have been logical to
1441      * return the next web page, but in this testing environment, it is
1442      * anticipated that the document number will be used to perform a document
1443      * search.
1444      *
1445      * @param page the document web page
1446      * @return the document's number
1447      * @throws IOException
1448      */
1449     protected final String saveAndCloseDoc(HtmlPage page) throws IOException {
1450         String docNbr = getDocNbr(page);
1451         HtmlPage savedPage = saveDoc(page);
1452         closeDoc(savedPage);
1453         return docNbr;
1454     }
1455 
1456     /**
1457      * Saves a document and then performs a document search to retrieve
1458      * the document.  This is useful for verifying that a document can be
1459      * saved correctly.  After retrieving a saved document, its values can
1460      * be inspected to verify their correctness.
1461      *
1462      * @param docPage the web page containing the document.
1463      * @return the retrieved document web page.
1464      * @throws Exception
1465      */
1466     protected final HtmlPage saveAndSearchDoc(HtmlPage docPage) throws Exception {
1467         String docNbr = saveAndCloseDoc(docPage);
1468         docPage = docSearch(docNbr);
1469         assertNotNull(docPage);
1470         return docPage;
1471     }
1472 
1473     /**
1474      * Gets the list of options in a select field.  This list only
1475      * contains the text that is displayed to the user, not the
1476      * actual values sent to the web server in a POST.
1477      *
1478      * @param page the web page.
1479      * @param id the id of the select field.
1480      * @return the list of displayed options.
1481      */
1482     protected final List<String> getSelectOptions(HtmlPage page, String id) {
1483         List<String> options = new ArrayList<String>();
1484 
1485         HtmlElement element = getElement(page, id);
1486         assertNotNull(element);
1487         assertTrue(element instanceof HtmlSelect);
1488 
1489         HtmlSelect select = (HtmlSelect) element;
1490         for (int i = 0; i < select.getOptionSize(); i++) {
1491             HtmlOption option = select.getOption(i);
1492             options.add(option.asText().trim());
1493         }
1494 
1495         return options;
1496     }
1497 
1498     /**
1499      * Verify that all the Help links on the web page go to the Kuali Help Web Page.
1500      * This will also test the help links on other panels on the page, but no big deal. This is not enabled in
1501      *  tests by default. If you want your test to do this, you need to add the <code>@Test</code> annotation
1502      *  and override it in your test class.
1503      * @throws Exception
1504      */
1505     public void testHelpLinks() throws Exception {
1506         checkHelpLinks(getDefaultDocumentPage());
1507     }
1508 
1509     /**
1510      * Used by generic methods like <code>{@link #testHelpLinks()}</code> for general behavior that is particular to a
1511      * page, but we don't know what that page is. This should be <code>abstract</code>, but that would break stuff.
1512      *
1513      * @return <code>{@link HtmlPage}</code> instance of a page that is used frequently in this test class
1514      */
1515     protected HtmlPage getDefaultDocumentPage() throws Exception {
1516         return null;
1517     }
1518 
1519     /**
1520      * Sets a select field to any of options except for "select:".
1521      * This is useful if you don't know the possible options in a
1522      * select field and you don't care which one is selected.
1523      *
1524      * @param page the HTML web page.
1525      * @param id the id of the HTML select tag.
1526      */
1527     protected void selectAnyOption(HtmlPage page, String id) {
1528         HtmlElement element = getElement(page, id);
1529         assertTrue(element instanceof HtmlSelect);
1530 
1531         HtmlSelect selectField = (HtmlSelect) element;
1532         List<HtmlOption> options = selectField.getOptions();
1533         for (HtmlOption option : options) {
1534             String value = option.getValueAttribute();
1535             if (!value.equals("")) {
1536                 selectField.setSelectedAttribute(value, true);
1537                 break;
1538             }
1539         }
1540     }
1541 }