View Javadoc

1   /**
2    * Copyright (c) 2002-2015, JWebUnit team.
3    *
4    * This file is part of JWebUnit.
5    *
6    * JWebUnit is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * JWebUnit is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with JWebUnit.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package net.sourceforge.jwebunit.htmlunit;
20  
21  import com.gargoylesoftware.htmlunit.AlertHandler;
22  import com.gargoylesoftware.htmlunit.BrowserVersion;
23  import com.gargoylesoftware.htmlunit.ConfirmHandler;
24  import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider;
25  import com.gargoylesoftware.htmlunit.ElementNotFoundException;
26  import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
27  import com.gargoylesoftware.htmlunit.ImmediateRefreshHandler;
28  import com.gargoylesoftware.htmlunit.JavaScriptPage;
29  import com.gargoylesoftware.htmlunit.Page;
30  import com.gargoylesoftware.htmlunit.PromptHandler;
31  import com.gargoylesoftware.htmlunit.RefreshHandler;
32  import com.gargoylesoftware.htmlunit.TextPage;
33  import com.gargoylesoftware.htmlunit.TopLevelWindow;
34  import com.gargoylesoftware.htmlunit.UnexpectedPage;
35  import com.gargoylesoftware.htmlunit.WebClient;
36  import com.gargoylesoftware.htmlunit.WebResponse;
37  import com.gargoylesoftware.htmlunit.WebWindow;
38  import com.gargoylesoftware.htmlunit.WebWindowEvent;
39  import com.gargoylesoftware.htmlunit.WebWindowListener;
40  import com.gargoylesoftware.htmlunit.WebWindowNotFoundException;
41  import com.gargoylesoftware.htmlunit.html.DomComment;
42  import com.gargoylesoftware.htmlunit.html.DomNode;
43  import com.gargoylesoftware.htmlunit.html.FrameWindow;
44  import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
45  import com.gargoylesoftware.htmlunit.html.HtmlButton;
46  import com.gargoylesoftware.htmlunit.html.HtmlButtonInput;
47  import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
48  import com.gargoylesoftware.htmlunit.html.HtmlElement;
49  import com.gargoylesoftware.htmlunit.html.HtmlForm;
50  import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput;
51  import com.gargoylesoftware.htmlunit.html.HtmlImageInput;
52  import com.gargoylesoftware.htmlunit.html.HtmlInput;
53  import com.gargoylesoftware.htmlunit.html.HtmlOption;
54  import com.gargoylesoftware.htmlunit.html.HtmlPage;
55  import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
56  import com.gargoylesoftware.htmlunit.html.HtmlResetInput;
57  import com.gargoylesoftware.htmlunit.html.HtmlSelect;
58  import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
59  import com.gargoylesoftware.htmlunit.html.HtmlTable;
60  import com.gargoylesoftware.htmlunit.html.HtmlTableCell;
61  import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
62  import com.gargoylesoftware.htmlunit.html.HtmlTableRow.CellIterator;
63  import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
64  import com.gargoylesoftware.htmlunit.util.Cookie;
65  import com.gargoylesoftware.htmlunit.util.NameValuePair;
66  import com.gargoylesoftware.htmlunit.xml.XmlPage;
67  import net.sourceforge.jwebunit.api.HttpHeader;
68  import net.sourceforge.jwebunit.api.IElement;
69  import net.sourceforge.jwebunit.api.ITestingEngine;
70  import net.sourceforge.jwebunit.exception.ExpectedJavascriptAlertException;
71  import net.sourceforge.jwebunit.exception.ExpectedJavascriptConfirmException;
72  import net.sourceforge.jwebunit.exception.ExpectedJavascriptPromptException;
73  import net.sourceforge.jwebunit.exception.TestingEngineResponseException;
74  import net.sourceforge.jwebunit.exception.UnableToSetFormException;
75  import net.sourceforge.jwebunit.exception.UnexpectedJavascriptAlertException;
76  import net.sourceforge.jwebunit.exception.UnexpectedJavascriptConfirmException;
77  import net.sourceforge.jwebunit.exception.UnexpectedJavascriptPromptException;
78  import net.sourceforge.jwebunit.html.Cell;
79  import net.sourceforge.jwebunit.html.Row;
80  import net.sourceforge.jwebunit.html.Table;
81  import net.sourceforge.jwebunit.javascript.JavascriptAlert;
82  import net.sourceforge.jwebunit.javascript.JavascriptConfirm;
83  import net.sourceforge.jwebunit.javascript.JavascriptPrompt;
84  import net.sourceforge.jwebunit.util.TestContext;
85  import org.apache.http.auth.AuthScope;
86  import org.apache.regexp.RE;
87  import org.apache.regexp.RESyntaxException;
88  import org.slf4j.Logger;
89  import org.slf4j.LoggerFactory;
90  import org.w3c.dom.Node;
91  import org.w3c.dom.NodeList;
92  
93  import java.io.IOException;
94  import java.io.InputStream;
95  import java.net.InetAddress;
96  import java.net.URL;
97  import java.net.UnknownHostException;
98  import java.util.ArrayList;
99  import java.util.Arrays;
100 import java.util.Calendar;
101 import java.util.Date;
102 import java.util.LinkedList;
103 import java.util.List;
104 import java.util.Map;
105 import java.util.Set;
106 
107 /**
108  * Acts as the wrapper for HtmlUnit access. A testing engine is initialized with a given URL, and maintains conversational state
109  * as the dialog progresses through link navigation, form submission, etc.
110  *
111  * @author Julien Henry
112  *
113  */
114 public class HtmlUnitTestingEngineImpl implements ITestingEngine {
115   /**
116    * Logger for this class.
117    */
118   private final Logger logger = LoggerFactory.getLogger(HtmlUnitTestingEngineImpl.class);
119 
120   /**
121    * holder for alternative refresh handler.
122    */
123   private RefreshHandler refreshHandler;
124   /**
125    * Encapsulate browser abilities.
126    */
127   private WebClient wc;
128 
129   /**
130    * The currently selected window.
131    */
132   private WebWindow win;
133 
134   /**
135    * A ref to the test context.
136    */
137   private TestContext testContext;
138 
139   /**
140    * The currently selected form.
141    */
142   private HtmlForm form;
143 
144   /**
145    * Is Javascript enabled.
146    */
147   private boolean jsEnabled = true;
148 
149   /**
150    * Should throw exception on Javascript error.
151    */
152   private boolean throwExceptionOnScriptError = true;
153 
154   /**
155    * Javascript alerts.
156    */
157   private List<JavascriptAlert> expectedJavascriptAlerts = new LinkedList<JavascriptAlert>();
158 
159   /**
160    * Javascript confirms.
161    */
162   private List<JavascriptConfirm> expectedJavascriptConfirms = new LinkedList<JavascriptConfirm>();
163 
164   /**
165    * Javascript prompts.
166    */
167   private List<JavascriptPrompt> expectedJavascriptPrompts = new LinkedList<JavascriptPrompt>();
168 
169   /**
170    * The default browser version.
171    */
172   private BrowserVersion defaultBrowserVersion = BrowserVersion.FIREFOX_38;
173 
174   /**
175      * Should we ignore failing status codes?
176      */
177   private boolean ignoreFailingStatusCodes = false;
178 
179   /**
180   * Do we provide a timeout limit (in seconds)? Default 0 = unlimited timeout.
181   */
182   private int timeout = 0;
183 
184   // Implementation of IJWebUnitDialog
185 
186   /**
187    * Initializes default HtmlUnit testing engine implementation.
188    */
189   public HtmlUnitTestingEngineImpl() {
190   }
191 
192   /**
193    * Initializes HtmlUnit testing engine implementation with web client.
194    *
195    * @param client web client
196    */
197   HtmlUnitTestingEngineImpl(WebClient client) {
198     this.wc = client;
199   }
200 
201   /**
202    * Begin a dialog with an initial URL and test client context.
203    *
204    * @param initialURL absolute url at which to begin dialog.
205    * @param context contains context information for the test client.
206    * @throws TestingEngineResponseException
207    */
208   @Override
209   public void beginAt(URL initialURL, TestContext context)
210       throws TestingEngineResponseException {
211     this.setTestContext(context);
212     initWebClient();
213     gotoPage(initialURL);
214   }
215 
216   /**
217    * Close the browser and check that all expected Javascript alerts, confirms and
218    * prompts have been taken care of.
219    */
220   @Override
221   public void closeBrowser() throws ExpectedJavascriptAlertException,
222       ExpectedJavascriptConfirmException,
223       ExpectedJavascriptPromptException {
224     if (wc != null) {
225       wc.close();
226       wc = null;
227     }
228     form = null; // reset current form
229     if (this.expectedJavascriptAlerts.size() > 0) {
230       throw new ExpectedJavascriptAlertException(
231           (expectedJavascriptAlerts.get(0))
232               .getMessage());
233     }
234     if (this.expectedJavascriptConfirms.size() > 0) {
235       throw new ExpectedJavascriptConfirmException(
236           (expectedJavascriptConfirms.get(0))
237               .getMessage());
238     }
239     if (this.expectedJavascriptPrompts.size() > 0) {
240       throw new ExpectedJavascriptPromptException(
241           (expectedJavascriptPrompts.get(0))
242               .getMessage());
243     }
244 
245   }
246 
247   /**
248    * Go to a particular page.
249    *
250    * @throws TestingEngineResponseException if an error response code is encountered
251    * 	and ignoreFailingStatusCodes is not enabled.
252    */
253   @Override
254   public void gotoPage(URL initialURL) throws TestingEngineResponseException {
255     try {
256       wc.getPage(initialURL);
257       win = wc.getCurrentWindow();
258       form = null;
259     } catch (FailingHttpStatusCodeException ex) {
260       throw new TestingEngineResponseException(ex.getStatusCode(),
261           "unexpected status code [" + ex.getStatusCode() + "] at URL: [" + initialURL + "]", ex);
262     } catch (IOException ex) {
263       throw new RuntimeException(ex);
264     }
265   }
266 
267   /**
268    * @see net.sourceforge.jwebunit.api.IJWebUnitDialog#setScriptingEnabled(boolean)
269    */
270   @Override
271   public void setScriptingEnabled(boolean value) {
272     // This variable is used to set Javascript before wc is instancied
273     jsEnabled = value;
274     if (wc != null) {
275       wc.getOptions().setJavaScriptEnabled(value);
276     }
277   }
278 
279   @Override
280   public void setThrowExceptionOnScriptError(boolean value) {
281     throwExceptionOnScriptError = value;
282     if (wc != null) {
283       wc.getOptions().setThrowExceptionOnScriptError(value);
284     }
285   }
286 
287   @Override
288   public List<javax.servlet.http.Cookie> getCookies() {
289     List<javax.servlet.http.Cookie> result = new LinkedList<javax.servlet.http.Cookie>();
290     Set<Cookie> cookies = wc.getCookieManager().getCookies();
291     for (Cookie cookie : cookies) {
292       javax.servlet.http.Cookie c = new javax.servlet.http.Cookie(
293           cookie.getName(), cookie.getValue());
294       c.setComment(cookie.toHttpClient().getComment());
295       c.setDomain(cookie.getDomain());
296       Date expire = cookie.toHttpClient().getExpiryDate();
297       if (expire == null) {
298         c.setMaxAge(-1);
299       } else {
300         Date now = Calendar.getInstance().getTime();
301         // Convert milli-second to second
302         Long second = Long.valueOf((expire.getTime() - now.getTime()) / 1000);
303         c.setMaxAge(second.intValue());
304       }
305       c.setPath(cookie.getPath());
306       c.setSecure(cookie.toHttpClient().isSecure());
307       c.setVersion(cookie.toHttpClient().getVersion());
308       result.add(c);
309     }
310     return result;
311   }
312 
313   @Override
314   public boolean hasWindow(String windowName) {
315     try {
316       getWindow(windowName);
317     } catch (WebWindowNotFoundException e) {
318       return false;
319     }
320     return true;
321   }
322 
323   @Override
324   public boolean hasWindowByTitle(String title) {
325     return getWindowByTitle(title) != null;
326   }
327 
328   /**
329    * Make the window with the given name in the current conversation active.
330    *
331    * @param windowName
332    */
333   @Override
334   public void gotoWindow(String windowName) {
335     setMainWindow(getWindow(windowName));
336   }
337 
338   @Override
339   public void gotoWindow(int windowID) {
340     setMainWindow(wc.getWebWindows().get(windowID));
341   }
342 
343   @Override
344   public int getWindowCount() {
345     return wc.getWebWindows().size();
346   }
347 
348   /**
349    * Goto first window with the given title.
350    *
351    * @param title
352    */
353   @Override
354   public void gotoWindowByTitle(String title) {
355     WebWindow window = getWindowByTitle(title);
356     if (window != null) {
357       setMainWindow(window);
358     }
359     else {
360       throw new RuntimeException("No window found with title [" + title + "]");
361     }
362   }
363 
364   /**
365    * Close the current window.
366    */
367   @Override
368   public void closeWindow() {
369     if (win != null) {
370       ((TopLevelWindow) win.getTopWindow()).close();
371       win = wc.getCurrentWindow();
372       form = null;
373     }
374 
375   }
376 
377   /**
378    * {@inheritDoc}
379    */
380   @Override
381   public boolean hasFrame(String frameNameOrId) {
382     return getFrame(frameNameOrId) != null;
383   }
384 
385   /**
386    * {@inheritDoc}
387    */
388   @Override
389   public void gotoFrame(String frameNameOrId) {
390     WebWindow frame = getFrame(frameNameOrId);
391     if (frame == null) {
392       throw new RuntimeException("No frame found in current page with name or id [" + frameNameOrId + "]");
393     }
394     win = frame;
395   }
396 
397   /**
398    * {@inheritDoc}
399    */
400   @Override
401   public void setWorkingForm(int index) {
402     HtmlForm newForm = getForm(index);
403     if (newForm == null) {
404       throw new UnableToSetFormException("No form found in current page with index " + index);
405     }
406     setWorkingForm(newForm);
407   }
408 
409   /**
410    * {@inheritDoc}
411    */
412   @Override
413   public void setWorkingForm(String nameOrId, int index) {
414     HtmlForm newForm = getForm(nameOrId, index);
415     if (newForm == null) {
416       throw new UnableToSetFormException("No form found in current page with name or id [" + nameOrId + "] and index " + index);
417     }
418     setWorkingForm(newForm);
419   }
420 
421   /**
422    * Return true if the current response contains a form.
423    */
424   @Override
425   public boolean hasForm() {
426     return ((HtmlPage) win.getEnclosedPage()).getForms().size() > 0;
427   }
428 
429   /**
430    * Return true if the current response contains a specific form.
431    *
432    * @param nameOrID name of id of the form to check for.
433    */
434   @Override
435   public boolean hasForm(String nameOrID) {
436     return getForm(nameOrID) != null;
437   }
438 
439   @Override
440   public boolean hasForm(String nameOrID, int index) {
441     return getForm(nameOrID, index) != null;
442   }
443 
444   @Override
445   public boolean hasFormParameterNamed(String paramName) {
446     for (HtmlElement e : getCurrentPage().getHtmlElementDescendants()) {
447       if (e.getAttribute("name").equals(paramName)) {
448         // set the working form if none has been set
449         if (e.getEnclosingForm() != null && getWorkingForm() == null) {
450           setWorkingForm(e.getEnclosingForm());
451         }
452         return true;
453       }
454     }
455     return false;
456   }
457 
458   /**
459    * Return the current value of a text input element with name <code>paramName</code>.
460    *
461    * @param paramName name of the input element. TODO: Find a way to handle multiple text input element with same
462    *            name.
463    */
464   @Override
465   public String getTextFieldValue(String paramName) {
466     // first try the current form
467     if (form != null) {
468       for (HtmlElement e : form.getHtmlElementDescendants()) {
469         if (e instanceof HtmlInput && e.getAttribute("name").equals(paramName)) {
470           // we found it
471           return ((HtmlInput) e).getValueAttribute();
472         }
473         if (e instanceof HtmlTextArea && e.getAttribute("name").equals(paramName)) {
474           // we found it
475           return ((HtmlTextArea) e).getText();
476         }
477       }
478     }
479 
480     // not in the current form: try *all* elements
481     HtmlElement outside_element = getHtmlElementWithAttribute("name", paramName);
482     if (outside_element != null) {
483       if (outside_element instanceof HtmlInput) {
484         // set current form if not null
485         if (outside_element.getEnclosingForm() != null) {
486           form = outside_element.getEnclosingForm();
487         }
488         return ((HtmlInput) outside_element).getValueAttribute();
489       }
490       if (outside_element instanceof HtmlTextArea) {
491         // set current form if not null
492         if (outside_element.getEnclosingForm() != null) {
493           form = outside_element.getEnclosingForm();
494         }
495         return ((HtmlTextArea) outside_element).getText();
496       }
497     }
498 
499     // we can't find it anywhere
500     throw new RuntimeException(
501         "getTextFieldParameterValue failed, text field with name ["
502           + paramName + "] does not exist.");
503   }
504 
505   /**
506    * Return the current value of a hidden input element with name <code>paramName</code>.
507    *
508    * @param paramName name of the input element. TODO: Find a way to handle multiple hidden input element with same
509    *            name.
510    */
511   @Override
512   public String getHiddenFieldValue(String paramName) {
513     // first try the current form
514     if (form != null) {
515       for (HtmlElement e : form.getHtmlElementDescendants()) {
516         if (e instanceof HtmlHiddenInput && e.getAttribute("name").equals(paramName)) {
517           // we found it
518           return ((HtmlInput) e).getValueAttribute();
519         }
520       }
521     }
522 
523     // not in the current form: try *all* elements
524     HtmlElement outside_element = getHtmlElementWithAttribute("name", paramName);
525     if (outside_element != null) {
526       if (outside_element instanceof HtmlHiddenInput) {
527         // set current form if not null
528         if (outside_element.getEnclosingForm() != null) {
529           form = outside_element.getEnclosingForm();
530         }
531         return ((HtmlHiddenInput) outside_element).getValueAttribute();
532       }
533     }
534 
535     // we can't find it anywhere
536     throw new RuntimeException("No hidden field with name [" + paramName
537       + "] was found.");
538   }
539 
540   /**
541    * Set a form text, password input element or textarea to the provided value.
542    *
543    * @param fieldName name of the input element or textarea
544    * @param text parameter value to submit for the element.
545    */
546   @Override
547   public void setTextField(String paramName, String text) {
548     // first try the current form
549     if (form != null) {
550       for (HtmlElement e : form.getHtmlElementDescendants()) {
551         if (e instanceof HtmlInput && e.getAttribute("name").equals(paramName)) {
552           // we found it
553           ((HtmlInput) e).setValueAttribute(text);
554           return;
555         }
556         if (e instanceof HtmlTextArea && e.getAttribute("name").equals(paramName)) {
557           // we found it
558           ((HtmlTextArea) e).setText(text);
559           return;
560         }
561       }
562     }
563 
564     // not in the current form: try *all* elements
565     HtmlElement outside_element = getHtmlElementWithAttribute("name", paramName);
566     if (outside_element != null) {
567       if (outside_element instanceof HtmlInput) {
568         ((HtmlInput) outside_element).setValueAttribute(text);
569         // set current form if not null
570         if (outside_element.getEnclosingForm() != null) {
571           form = outside_element.getEnclosingForm();
572         }
573         return;
574       }
575       if (outside_element instanceof HtmlTextArea) {
576         ((HtmlTextArea) outside_element).setText(text);
577         // set current form if not null
578         if (outside_element.getEnclosingForm() != null) {
579           form = outside_element.getEnclosingForm();
580         }
581         return;
582       }
583     }
584 
585     // we can't find it anywhere
586     throw new RuntimeException("No text field with name [" + paramName
587       + "] was found.");
588   }
589 
590   /**
591    * Set a form hidden element to the provided value.
592    *
593    * @param fieldName name of the hidden input element
594    * @param paramValue parameter value to submit for the element.
595    */
596   @Override
597   public void setHiddenField(String fieldName, String text) {
598     // first try the current form
599     if (form != null) {
600       for (HtmlElement e : form.getHtmlElementDescendants()) {
601         if (e instanceof HtmlHiddenInput && e.getAttribute("name").equals(fieldName)) {
602           // we found it
603           ((HtmlHiddenInput) e).setValueAttribute(text);
604           return;
605         }
606       }
607     }
608 
609     // not in the current form: try *all* elements
610     HtmlElement outside_element = getHtmlElementWithAttribute("name", fieldName);
611     if (outside_element != null) {
612       if (outside_element instanceof HtmlHiddenInput) {
613         ((HtmlHiddenInput) outside_element).setValueAttribute(text);
614         // set current form if not null
615         if (outside_element.getEnclosingForm() != null) {
616           form = outside_element.getEnclosingForm();
617         }
618         return;
619       }
620     }
621 
622     // we can't find it anywhere
623     throw new RuntimeException("No hidden field with name [" + fieldName
624       + "] was found.");
625   }
626 
627   /**
628    * Return a string array of select box option values.
629    *
630    * @param selectName name of the select box.
631    */
632   @Override
633   public String[] getSelectOptionValues(String selectName) {
634     HtmlSelect sel = getForm().getSelectByName(selectName);
635     ArrayList<String> result = new ArrayList<String>();
636     for (HtmlOption opt : sel.getOptions()) {
637       result.add(opt.getValueAttribute());
638     }
639     return result.toArray(new String[result.size()]);
640   }
641 
642   /**
643    * Return a string array of the Nth select box option values.
644    *
645    * @param selectName name of the select box.
646    * @param index the 0-based index when more than one
647    * select with the same name is expected.
648    */
649   @Override
650   public String[] getSelectOptionValues(String selectName, int index) {
651     List<HtmlSelect> sels = getForm().getSelectsByName(selectName);
652     if (sels == null || sels.size() < index + 1) {
653       throw new RuntimeException("Did not find select with name [" + selectName
654         + "] at index " + index);
655     }
656     HtmlSelect sel = sels.get(index);
657     ArrayList<String> result = new ArrayList<String>();
658     for (HtmlOption opt : sel.getOptions()) {
659       result.add(opt.getValueAttribute());
660     }
661     return result.toArray(new String[result.size()]);
662   }
663 
664   private String[] getSelectedOptions(HtmlSelect sel) {
665     String[] result = new String[sel.getSelectedOptions().size()];
666     int i = 0;
667     for (HtmlOption opt : sel.getSelectedOptions()) {
668       result[i++] = opt.getValueAttribute();
669     }
670     return result;
671   }
672 
673   @Override
674   public String[] getSelectedOptions(String selectName) {
675     HtmlSelect sel = getForm().getSelectByName(selectName);
676     return getSelectedOptions(sel);
677   }
678 
679   @Override
680   public String[] getSelectedOptions(String selectName, int index) {
681     List<HtmlSelect> sels = getForm().getSelectsByName(selectName);
682     if (sels == null || sels.size() < index + 1) {
683       throw new RuntimeException("Did not find select with name [" + selectName
684         + "] at index " + index);
685     }
686     HtmlSelect sel = sels.get(index);
687     return getSelectedOptions(sel);
688   }
689 
690   private String getSelectOptionValueForLabel(HtmlSelect sel, String label) {
691     for (HtmlOption opt : sel.getOptions()) {
692       if (opt.asText().equals(label)) {
693         return opt.getValueAttribute();
694       }
695     }
696     throw new RuntimeException("Unable to find option " + label + " for "
697       + sel.getNameAttribute());
698   }
699 
700   @Override
701   public String getSelectOptionValueForLabel(String selectName, String label) {
702     HtmlSelect sel = getForm().getSelectByName(selectName);
703     return getSelectOptionValueForLabel(sel, label);
704   }
705 
706   @Override
707   public String getSelectOptionValueForLabel(String selectName, int index, String label) {
708     List<HtmlSelect> sels = getForm().getSelectsByName(selectName);
709     if (sels == null || sels.size() < index + 1) {
710       throw new RuntimeException("Did not find select with name [" + selectName
711         + "] at index " + index);
712     }
713     HtmlSelect sel = sels.get(index);
714     return getSelectOptionValueForLabel(sel, label);
715   }
716 
717   private String getSelectOptionLabelForValue(HtmlSelect sel, String value) {
718     for (HtmlOption opt : sel.getOptions()) {
719       if (opt.getValueAttribute().equals(value)) {
720         return opt.asText();
721       }
722     }
723     throw new RuntimeException("Unable to find option " + value + " for "
724       + sel.getNameAttribute());
725   }
726 
727   @Override
728   public String getSelectOptionLabelForValue(String selectName, String value) {
729     HtmlSelect sel = getForm().getSelectByName(selectName);
730     return getSelectOptionLabelForValue(sel, value);
731   }
732 
733   @Override
734   public String getSelectOptionLabelForValue(String selectName, int index, String value) {
735     List<HtmlSelect> sels = getForm().getSelectsByName(selectName);
736     if (sels == null || sels.size() < index + 1) {
737       throw new RuntimeException("Did not find select with name [" + selectName
738         + "] at index " + index);
739     }
740     HtmlSelect sel = sels.get(index);
741     return getSelectOptionLabelForValue(sel, value);
742   }
743 
744   @Override
745   public URL getPageURL() {
746     return win.getEnclosedPage().getWebResponse().getWebRequest().getUrl();
747   }
748 
749   @Override
750   public String getPageSource() {
751     return win.getEnclosedPage().getWebResponse()
752         .getContentAsString();
753   }
754 
755   @Override
756   public String getPageTitle() {
757     return getCurrentPageTitle();
758   }
759 
760   @Override
761   public String getPageText() {
762     Page page = win.getEnclosedPage();
763     if (page instanceof HtmlPage) {
764       return ((HtmlPage) page).getBody().asText();
765     }
766     if (page instanceof TextPage) {
767       return ((TextPage) page).getContent();
768     }
769     if (page instanceof JavaScriptPage) {
770       return ((JavaScriptPage) page).getContent();
771     }
772     if (page instanceof XmlPage) {
773       return ((XmlPage) page).getContent();
774     }
775     if (page instanceof UnexpectedPage) {
776       return ((UnexpectedPage) page).getWebResponse()
777           .getContentAsString();
778     }
779     throw new RuntimeException(
780         "Unexpected error in getPageText(). This method need to be updated.");
781   }
782 
783   @Override
784   public String getServerResponse() {
785     StringBuffer result = new StringBuffer();
786     WebResponse wr = wc.getCurrentWindow().getEnclosedPage()
787         .getWebResponse();
788     result.append(wr.getStatusCode()).append(" ").append(
789         wr.getStatusMessage()).append("\n");
790     result.append("Location: ").append(wr.getWebRequest().getUrl()).append("\n");
791     for (NameValuePair h : wr.getResponseHeaders()) {
792       result.append(h.getName()).append(": ").append(h.getValue())
793           .append("\n");
794     }
795     result.append("\n");
796     result.append(wr.getContentAsString());
797     return result.toString();
798   }
799 
800   @Override
801   public InputStream getInputStream() {
802     try {
803       return wc.getCurrentWindow().getEnclosedPage().getWebResponse()
804           .getContentAsStream();
805     } catch (IOException e) {
806       throw new RuntimeException(e);
807     }
808   }
809 
810   @Override
811   public InputStream getInputStream(URL resourceUrl)
812       throws TestingEngineResponseException {
813     WebWindow imageWindow = null;
814     try {
815       // as far as I can tell, there is no such thing as an iframe/object kind of "window" in htmlunit, so I'm
816       // opening a fake new window here
817       imageWindow = wc.openWindow(resourceUrl, "for_stream");
818       Page page = imageWindow.getEnclosedPage();
819       return page.getWebResponse().getContentAsStream();
820     } catch (FailingHttpStatusCodeException aException) {
821       throw new TestingEngineResponseException(
822           aException.getStatusCode(), aException);
823     } catch (IOException e) {
824       throw new RuntimeException(e);
825     } finally {
826       if (imageWindow != null) {
827         wc.deregisterWebWindow(imageWindow);
828       }
829     }
830   }
831 
832   /**
833    * Create the {@link WebClient} that will be used for this test.
834    * Subclasses should only override this method if they need to override
835    * the default {@link WebClient}.
836    *
837    * <p>Also see issue 2697234.
838    *
839    * @author Jevon
840    * @return A newly created {@link WebClient}
841    */
842   protected WebClient createWebClient() {
843     /*
844      * The user agent string is now provided by default to new test cases.
845      * It can still be overridden if testContext.getUserAgent() is not
846      * null (i.e. has been set manually.)
847      *
848      * @author Jevon
849      */
850     BrowserVersion bv;
851     if (testContext.getUserAgent() != null) {
852       bv = BrowserVersion.FIREFOX_38.clone();
853       bv.setUserAgent(testContext.getUserAgent());
854     } else {
855       bv = defaultBrowserVersion; // use default (which includes a full UserAgent string)
856     }
857 
858     if (getTestContext().getProxyHost() != null && getTestContext().getProxyPort() > 0) {
859       // Proxy configuration
860       return new WebClient(bv, getTestContext().getProxyHost(), getTestContext().getProxyPort());
861     } else {
862       return new WebClient(bv);
863     }
864   }
865 
866   /**
867    * Initialise the web client before accessing a page.
868    */
869   private void initWebClient() {
870 
871     wc = createWebClient();
872 
873     wc.getOptions().setJavaScriptEnabled(jsEnabled);
874     wc.getOptions().setThrowExceptionOnFailingStatusCode(!ignoreFailingStatusCodes);
875     wc.getOptions().setThrowExceptionOnScriptError(throwExceptionOnScriptError);
876     wc.getOptions().setRedirectEnabled(true);
877     wc.getOptions().setUseInsecureSSL(true);
878     if (refreshHandler == null) {
879       wc.setRefreshHandler(new ImmediateRefreshHandler());
880     } else {
881       wc.setRefreshHandler(refreshHandler);
882     }
883     wc.getOptions().setTimeout(timeout);
884     DefaultCredentialsProvider creds = new DefaultCredentialsProvider();
885     if (getTestContext().hasAuthorization()) {
886       creds.addCredentials(getTestContext().getUser(), getTestContext()
887           .getPassword());
888     }
889     if (getTestContext().hasNTLMAuthorization()) {
890       InetAddress netAddress;
891       String address;
892       try {
893         netAddress = InetAddress.getLocalHost();
894         address = netAddress.getHostName();
895       } catch (UnknownHostException e) {
896         address = "";
897       }
898       creds.addNTLMCredentials(getTestContext().getUser(),
899           getTestContext().getPassword(), "", -1, address,
900           getTestContext().getDomain());
901     }
902     if (getTestContext().hasProxyAuthorization()) {
903       creds.addCredentials(getTestContext().getProxyUser(),
904           getTestContext().getProxyPasswd(), getTestContext()
905               .getProxyHost(), getTestContext().getProxyPort(), AuthScope.ANY_REALM);
906     }
907     wc.setCredentialsProvider(creds);
908     wc.addWebWindowListener(new WebWindowListener() {
909       @Override
910       public void webWindowClosed(WebWindowEvent event) {
911         if (win == null || event.getOldPage().equals(win.getEnclosedPage())) {
912           win = wc.getCurrentWindow();
913           form = null;
914         }
915         String win = event.getWebWindow().getName();
916         Page oldPage = event.getOldPage();
917         String oldPageTitle = "no_html";
918         if (oldPage instanceof HtmlPage) {
919           oldPageTitle = ((HtmlPage) oldPage).getTitleText();
920         }
921         logger.debug("Window {} closed : {}", win, oldPageTitle);
922       }
923 
924       @Override
925       public void webWindowContentChanged(WebWindowEvent event) {
926         form = null;
927         String winName = event.getWebWindow().getName();
928         Page oldPage = event.getOldPage();
929         Page newPage = event.getNewPage();
930         String oldPageTitle = "no_html";
931         if (oldPage instanceof HtmlPage) {
932           oldPageTitle = ((HtmlPage) oldPage).getTitleText();
933         }
934         String newPageTitle = "no_html";
935         if (newPage instanceof HtmlPage) {
936           newPageTitle = ((HtmlPage) newPage).getTitleText();
937         }
938         logger.debug("Window \"{}\" changed : \"{}\" became \"{}", new Object[] {winName, oldPageTitle, newPageTitle});
939       }
940 
941       @Override
942       public void webWindowOpened(WebWindowEvent event) {
943         String win = event.getWebWindow().getName();
944         Page newPage = event.getNewPage();
945         if (newPage instanceof HtmlPage) {
946           logger.debug("Window {} opened : {}", win, ((HtmlPage) newPage).getTitleText());
947         } else {
948           logger.info("Window {} opened", win);
949         }
950       }
951     });
952     // Add Javascript Alert Handler
953     wc.setAlertHandler(new AlertHandler() {
954       @Override
955       public void handleAlert(Page page, String msg) {
956         if (expectedJavascriptAlerts.size() < 1) {
957           throw new UnexpectedJavascriptAlertException(msg);
958         } else {
959           JavascriptAlert expected = expectedJavascriptAlerts
960               .remove(0);
961           if (!msg.equals(expected.getMessage())) {
962             throw new UnexpectedJavascriptAlertException(msg);
963           }
964         }
965       }
966     });
967     // Add Javascript Confirm Handler
968     wc.setConfirmHandler(new ConfirmHandler() {
969       @Override
970       public boolean handleConfirm(Page page, String msg) {
971         if (expectedJavascriptConfirms.size() < 1) {
972           throw new UnexpectedJavascriptConfirmException(msg);
973         } else {
974           JavascriptConfirm expected = expectedJavascriptConfirms
975               .remove(0);
976           if (!msg.equals(expected.getMessage())) {
977             throw new UnexpectedJavascriptConfirmException(msg);
978           } else {
979             return expected.getAction();
980           }
981         }
982       }
983     });
984     // Add Javascript Prompt Handler
985     wc.setPromptHandler(new PromptHandler() {
986       @Override
987       public String handlePrompt(Page page, String msg) {
988         if (expectedJavascriptPrompts.size() < 1) {
989           throw new UnexpectedJavascriptPromptException(msg);
990         } else {
991           JavascriptPrompt expected = expectedJavascriptPrompts
992               .remove(0);
993           if (!msg.equals(expected.getMessage())) {
994             throw new UnexpectedJavascriptPromptException(msg);
995           } else {
996             return expected.getInput();
997           }
998         }
999       }
1000     });
1001     // Deal with cookies
1002     for (javax.servlet.http.Cookie c : getTestContext().getCookies()) {
1003       // If Path==null, cookie is not send to the server.
1004       wc.getCookieManager().addCookie(
1005           new Cookie(c.getDomain() != null ? c.getDomain() : "", c
1006               .getName(), c.getValue(), c.getPath() != null ? c
1007               .getPath() : "", c.getMaxAge(), c.getSecure()));
1008     }
1009     // Deal with custom request header
1010     Map<String, String> requestHeaders = getTestContext().getRequestHeaders();
1011 
1012     for (Map.Entry<String, String> requestHeader : requestHeaders.entrySet()) {
1013       wc.addRequestHeader(requestHeader.getKey(), requestHeader.getValue());
1014     }
1015   }
1016 
1017   /**
1018    * Return the window with the given name in the current conversation.
1019    *
1020    * @param windowName
1021    * @throws WebWindowNotFoundException if the window could not be found
1022    */
1023   public WebWindow getWindow(String windowName) {
1024     return wc.getWebWindowByName(windowName);
1025   }
1026 
1027   /**
1028    * Return the currently opened window (issue 2697234).
1029    *
1030    * @return the currently opened window
1031    */
1032   public WebWindow getCurrentWindow() {
1033     return win;
1034   }
1035 
1036   /**
1037    * Return the current web client (issue 2697234).
1038    *
1039    * @return the current web client
1040    */
1041   public WebClient getWebClient() {
1042     return wc;
1043   }
1044 
1045   private HtmlElement getHtmlElement(String anID) {
1046     try {
1047       return ((HtmlPage) win.getEnclosedPage()).getHtmlElementById(anID);
1048     } catch (ElementNotFoundException e) {
1049       return null;
1050     }
1051   }
1052 
1053   private HtmlElement getHtmlElementByXPath(String xpath) {
1054     return getHtmlElementByXPath(getCurrentPage(), xpath);
1055   }
1056 
1057   private HtmlElement getHtmlElementByXPath(DomNode parent, String xpath) {
1058     return (HtmlElement) parent.getFirstByXPath(xpath);
1059   }
1060 
1061   /**
1062    * Get all the comments in a document, as a list of strings.
1063    */
1064   @Override
1065   public List<String> getComments() {
1066     List<String> comments = new ArrayList<String>();
1067     getComments(comments, ((HtmlPage) win.getEnclosedPage()));
1068 
1069     return comments;
1070   }
1071 
1072   /**
1073    * Recursively find comments for all child nodes.
1074    *
1075    * @param comments
1076    * @param node
1077    */
1078   private void getComments(List<String> comments, Node node) {
1079     NodeList nodes = node.getChildNodes();
1080     for (int i = 0; i < nodes.getLength(); i++) {
1081       Node n = nodes.item(i);
1082       if (n instanceof DomComment) {
1083         comments.add(((DomComment) n).getData().trim());
1084       }
1085       // add all child nodes
1086       getComments(comments, n);
1087     }
1088   }
1089 
1090   /**
1091    * Return the first open window with the given title.
1092    */
1093   private WebWindow getWindowByTitle(String title) {
1094     for (WebWindow window : wc.getWebWindows()) {
1095       if (window.getEnclosedPage() instanceof HtmlPage
1096         && ((HtmlPage) window.getEnclosedPage()).getTitleText()
1097             .equals(title)) {
1098         return window;
1099       }
1100     }
1101     return null;
1102   }
1103 
1104   /**
1105    * Return the page title of the current response page, encoded as specified by the current
1106    * {@link net.sourceforge.jwebunit.util.TestContext}.
1107    */
1108   public String getCurrentPageTitle() {
1109     if (win.getEnclosedPage() instanceof HtmlPage) {
1110       return ((HtmlPage) win.getEnclosedPage()).getTitleText();
1111     }
1112     return "";
1113   }
1114 
1115   /**
1116    * <p>
1117    * Return the current form active for the dialog.
1118    * </p>
1119    * <p>
1120    * The active form can also be explicitly set by {@link #setWorkingForm}.
1121    * </p>
1122    * <p>
1123    * If this method is called without the form having been implicitly or explicitly set, it will attempt to return the
1124    * default first form on the page.
1125    * </p>
1126    *
1127    * @exception UnableToSetFormException This runtime assertion failure will be raised if there is no form on the
1128    *                response.
1129    * @return HtmlForm object representing the current active form from the response.
1130    */
1131   protected HtmlForm getForm() {
1132     if (form == null) {
1133       if (hasForm()) {
1134         setWorkingForm(getForm(0));
1135         return getForm(0);
1136       } else {
1137         throw new RuntimeException("No form in current page");
1138       }
1139     } else {
1140       return form;
1141     }
1142   }
1143 
1144   private HtmlForm getForm(int formIndex) {
1145     return ((HtmlPage) win.getEnclosedPage()).getForms().get(
1146         formIndex);
1147   }
1148 
1149   private HtmlForm getForm(String nameOrID) {
1150     try {
1151       return (HtmlForm) ((HtmlPage) win.getEnclosedPage())
1152           .getHtmlElementById(nameOrID);
1153     } catch (ElementNotFoundException e) {
1154 
1155     }
1156     try {
1157       return ((HtmlPage) win.getEnclosedPage()).getFormByName(nameOrID);
1158     } catch (ElementNotFoundException e) {
1159 
1160     }
1161     return null;
1162   }
1163 
1164   private HtmlForm getForm(String nameOrID, int index) {
1165     List<HtmlForm> forms = new ArrayList<HtmlForm>();
1166     for (HtmlForm form : getCurrentPage().getForms()) {
1167       if (nameOrID.equals(form.getId())
1168         || nameOrID.equals(form.getNameAttribute())) {
1169         forms.add(form);
1170       }
1171     }
1172     if (forms.size() > index) {
1173       return forms.get(index);
1174     }
1175     else {
1176       return null;
1177     }
1178   }
1179 
1180   private List<HtmlForm> getForms() {
1181     HtmlPage page = (HtmlPage) win.getEnclosedPage();
1182     return page.getForms();
1183   }
1184 
1185   protected HtmlPage getCurrentPage() {
1186     Page page = win.getEnclosedPage();
1187     if (page instanceof HtmlPage) {
1188       return (HtmlPage) page;
1189     }
1190     throw new RuntimeException("Non HTML content");
1191   }
1192 
1193   private void setWorkingForm(HtmlForm newForm) {
1194     form = newForm;
1195   }
1196 
1197   private HtmlForm getWorkingForm() {
1198     return form;
1199   }
1200 
1201   /**
1202    * Does an element with a certain attribute value exist in this page?
1203    *
1204   * @param attributeName attribute name to search for
1205   * @param value value to search for
1206   * @return true if the element is found
1207   */
1208   private boolean hasHtmlElementWithAttribute(String attributeName, String value) {
1209     return getHtmlElementWithAttribute(attributeName, value) != null;
1210   }
1211 
1212   /**
1213    * Get an element with a certain attribute value.
1214    *
1215    * @param attributeName attribute name to search for
1216    * @param value value to search for
1217    * @return the element found, or null
1218    */
1219   private HtmlElement getHtmlElementWithAttribute(String attributeName, String value) {
1220     for (HtmlElement e : getCurrentPage().getHtmlElementDescendants()) {
1221       if (e.getAttribute(attributeName).equals(value)) {
1222         return e;
1223       }
1224     }
1225     return null;
1226   }
1227 
1228   /**
1229      * Return true if a form parameter (input element) is present on the current response.
1230      *
1231      * @param selectName name of the input element to check for
1232      */
1233   public boolean hasFormSelectNamed(String selectName) {
1234     return hasHtmlElementWithAttribute("name", selectName);
1235   }
1236 
1237   /**
1238    * Return the HtmlUnit submit button with a given name.
1239    *
1240    * @param buttonName name of button.
1241    * @return the button
1242    */
1243   public HtmlElement getSubmitButton(String buttonName) {
1244     List<HtmlElement> btns = new LinkedList<HtmlElement>();
1245     if (form != null) {
1246       btns.addAll(getForm().getInputsByName(buttonName));
1247       btns.addAll(getForm().getButtonsByName(buttonName));
1248     } else {
1249       for (HtmlForm f : getCurrentPage().getForms()) {
1250         btns.addAll(f.getInputsByName(buttonName));
1251         btns.addAll(f.getButtonsByName(buttonName));
1252       }
1253     }
1254     for (HtmlElement o : btns) {
1255       if (o instanceof HtmlSubmitInput) {
1256         HtmlSubmitInput btn = (HtmlSubmitInput) o;
1257         if (form == null) {
1258           form = btn.getEnclosingFormOrDie();
1259         }
1260         return btn;
1261       }
1262       if (o instanceof HtmlImageInput) {
1263         HtmlImageInput btn = (HtmlImageInput) o;
1264         if (form == null) {
1265           form = btn.getEnclosingFormOrDie();
1266         }
1267         return btn;
1268       }
1269       if (o instanceof HtmlButton) {
1270         HtmlButton btn = (HtmlButton) o;
1271         if (btn.getTypeAttribute().equals("submit")) {
1272           if (form == null) {
1273             form = btn.getEnclosingFormOrDie();
1274           }
1275           return btn;
1276         }
1277       }
1278     }
1279     return null;
1280   }
1281 
1282   public HtmlElement getResetButton(String buttonName) {
1283     List<HtmlElement> btns = new LinkedList<HtmlElement>();
1284     if (form != null) {
1285       btns.addAll(getForm().getInputsByName(buttonName));
1286       btns.addAll(getForm().getButtonsByName(buttonName));
1287     } else {
1288       for (HtmlForm f : getCurrentPage().getForms()) {
1289         btns.addAll(f.getInputsByName(buttonName));
1290         btns.addAll(f.getButtonsByName(buttonName));
1291       }
1292     }
1293     for (HtmlElement o : btns) {
1294       if (o instanceof HtmlResetInput) {
1295         HtmlResetInput btn = (HtmlResetInput) o;
1296         if (form == null) {
1297           form = btn.getEnclosingFormOrDie();
1298         }
1299         return btn;
1300       }
1301       if (o instanceof HtmlButton) {
1302         HtmlButton btn = (HtmlButton) o;
1303         if (btn.getTypeAttribute().equals("reset")) {
1304           if (form == null) {
1305             form = btn.getEnclosingFormOrDie();
1306           }
1307           return btn;
1308         }
1309       }
1310     }
1311     return null;
1312   }
1313 
1314   /**
1315    * Return the HtmlUnit submit button with a given name and value.
1316    *
1317    * @param buttonName button name.
1318    * @param buttonValue button value.
1319    * @return HtmlSubmitInput, HtmlImageInput or HtmlButton
1320    */
1321   public HtmlElement getSubmitButton(String buttonName,
1322       String buttonValue) {
1323     List<HtmlElement> btns = new LinkedList<HtmlElement>();
1324     if (form != null) {
1325       btns.addAll(getForm().getInputsByName(buttonName));
1326       btns.addAll(getForm().getButtonsByName(buttonName));
1327     } else {
1328       for (HtmlForm f : getCurrentPage().getForms()) {
1329         btns.addAll(f.getInputsByName(buttonName));
1330         btns.addAll(f.getButtonsByName(buttonName));
1331       }
1332     }
1333     for (HtmlElement o : btns) {
1334       if (o instanceof HtmlSubmitInput) {
1335         HtmlSubmitInput btn = (HtmlSubmitInput) o;
1336         if (btn.getValueAttribute().equals(buttonValue)) {
1337           if (form == null) {
1338             form = btn.getEnclosingFormOrDie();
1339           }
1340           return btn;
1341         }
1342       }
1343       if (o instanceof HtmlImageInput) {
1344         HtmlImageInput btn = (HtmlImageInput) o;
1345         if (btn.getValueAttribute().equals(buttonValue)) {
1346           if (form == null) {
1347             form = btn.getEnclosingFormOrDie();
1348           }
1349           return btn;
1350         }
1351       }
1352       if (o instanceof HtmlButton) {
1353         HtmlButton btn = (HtmlButton) o;
1354         if (btn.getValueAttribute().equals(buttonValue)
1355           && btn.getTypeAttribute().equals("submit")) {
1356           if (form == null) {
1357             form = btn.getEnclosingFormOrDie();
1358           }
1359           return btn;
1360         }
1361       }
1362     }
1363     return null;
1364   }
1365   
1366   private HtmlElement getSubmitButton() {
1367     List<HtmlElement> btns = new LinkedList<HtmlElement>();
1368     if (form != null) {
1369       btns.addAll(getForm().getElementsByAttribute("input", "type", "submit"));
1370       btns.addAll(getForm().getElementsByAttribute("input", "type", "image"));
1371       btns.addAll(getForm().getElementsByAttribute("button", "type", "submit"));
1372     } else {
1373       for (HtmlForm f : getCurrentPage().getForms()) {
1374         btns.addAll(f.getElementsByAttribute("input", "type", "submit"));
1375         btns.addAll(f.getElementsByAttribute("input", "type", "image"));
1376         btns.addAll(f.getElementsByAttribute("button", "type", "submit"));
1377       }
1378     }
1379     for (HtmlElement o : btns) {
1380       if (o instanceof HtmlSubmitInput) {
1381         HtmlSubmitInput btn = (HtmlSubmitInput) o;
1382         if (form == null) {
1383           form = btn.getEnclosingFormOrDie();
1384         }
1385         return btn;
1386       }
1387       if (o instanceof HtmlImageInput) {
1388         HtmlImageInput btn = (HtmlImageInput) o;
1389         if (form == null) {
1390           form = btn.getEnclosingFormOrDie();
1391         }
1392         return btn;
1393       }
1394       if (o instanceof HtmlButton) {
1395         HtmlButton btn = (HtmlButton) o;
1396         if (btn.getTypeAttribute().equals("submit")) {
1397           if (form == null) {
1398             form = btn.getEnclosingFormOrDie();
1399           }
1400           return btn;
1401         }
1402       }
1403     }
1404     return null;
1405   }
1406 
1407   /**
1408    * {@inheritDoc}
1409    */
1410   @Override
1411   public boolean hasSubmitButton() {
1412     return getSubmitButton() != null;
1413   }
1414 
1415   /**
1416    * {@inheritDoc}
1417    */
1418   @Override
1419   public boolean hasSubmitButton(String buttonName) {
1420     return getSubmitButton(buttonName) != null;
1421   }
1422 
1423   /**
1424    * {@inheritDoc}
1425    */
1426   @Override
1427   public boolean hasSubmitButton(String buttonName, String buttonValue) {
1428     try {
1429       return getSubmitButton(buttonName, buttonValue) != null;
1430     } catch (UnableToSetFormException e) {
1431       return false;
1432     }
1433 
1434   }
1435 
1436   @Override
1437   public boolean hasResetButton() {
1438     HtmlForm form = getForm();
1439     List<?> l = form.getByXPath("//input[@type='reset']");
1440     List<?> l2 = form.getByXPath("//button[@type='reset']");
1441     return (l.size() > 0 || l2.size() > 0);
1442   }
1443 
1444   @Override
1445   public boolean hasResetButton(String buttonName) {
1446     return getResetButton(buttonName) != null;
1447   }
1448 
1449   /**
1450    * Return the HtmlUnit Button with a given id.
1451    *
1452    * @param buttonId
1453    */
1454   private HtmlElement getButton(String buttonId) {
1455     HtmlElement btn = null;
1456     try {
1457       btn = getCurrentPage().getHtmlElementById(buttonId);
1458       if (btn instanceof HtmlButton || btn instanceof HtmlButtonInput
1459         || btn instanceof HtmlSubmitInput
1460         || btn instanceof HtmlResetInput) {
1461         return btn;
1462       }
1463     } catch (ElementNotFoundException e) {
1464       return null;
1465     }
1466     return null;
1467   }
1468 
1469   /**
1470    * Checks whether a button containing the specified text as its label exists.
1471    * For HTML input tags of type submit, reset, or button, this checks the
1472    * value attribute.  For HTML button tags, this checks the element's
1473    * content by retrieving the text content.
1474    *
1475    * <p>This method does not check whether the button is currently visible to the
1476    * client.
1477    *
1478    * @param text the text of the button (between &lt;button&gt;&lt;/button&gt;)
1479    * or the value of the "value" attribute.
1480    * @return <code>true</code> when the button with text could be found.
1481    */
1482   @Override
1483   public boolean hasButtonWithText(String text) {
1484     return getButtonWithText(text) != null ? true : false;
1485   }
1486 
1487   /**
1488    * Returns the first button that contains the specified text as its label.
1489    * For HTML input tags of type submit, reset, or button, this checks the
1490    * value attribute.  For HTML button tags, this checks the element's
1491    * content by retrieving the text content.
1492    *
1493    * <p>This method does not check whether the button is currently visible to the
1494    * client.
1495    *
1496    * @param buttonValueText the text of the button (between &lt;button&gt;&lt;/button&gt;)
1497    * or the value of the "value" attribute.
1498    * @return the ClickableElement with the specified text or null if
1499    * no such button is found.
1500    */
1501   public HtmlElement getButtonWithText(String buttonValueText) {
1502     if (buttonValueText == null) {
1503       throw new NullPointerException("Cannot search for button with null text");
1504     }
1505 
1506     List<? extends HtmlElement> l = ((HtmlPage) win.getEnclosedPage()).getDocumentElement()
1507         .getHtmlElementsByTagNames(
1508             Arrays.asList(new String[] {"button", "input"}));
1509     for (HtmlElement e : l) {
1510       if (e instanceof HtmlButton) {
1511         // we cannot use asText(), as this returns an empty string if the
1512         // button is not currently displayed, resulting in different
1513         // behaviour as the <input> Buttons
1514         if (buttonValueText.equals(((HtmlButton) e).getTextContent())) {
1515           return e;
1516         }
1517       }
1518       else if (e instanceof HtmlButtonInput ||
1519         e instanceof HtmlSubmitInput ||
1520         e instanceof HtmlResetInput) {
1521         if (buttonValueText.equals(e.getAttribute("value"))) {
1522           return e;
1523         }
1524       }
1525     }
1526     return null;
1527   }
1528 
1529   /**
1530    * Returns if the button identified by <code>buttonId</code> is present.
1531    *
1532    * @param buttonId the id of the button
1533    * @return <code>true</code> when the button was found.
1534    */
1535   @Override
1536   public boolean hasButton(String buttonId) {
1537     try {
1538       return getButton(buttonId) != null;
1539     } catch (UnableToSetFormException e) {
1540       return false;
1541     }
1542   }
1543 
1544   @Override
1545   public boolean isCheckboxSelected(String checkBoxName) {
1546     HtmlCheckBoxInput cb = getCheckbox(checkBoxName);
1547     return cb.isChecked();
1548   }
1549 
1550   @Override
1551   public boolean isCheckboxSelected(String checkBoxName, String checkBoxValue) {
1552     HtmlCheckBoxInput cb = getCheckbox(checkBoxName, checkBoxValue);
1553     return cb.isChecked();
1554   }
1555 
1556   /**
1557    * Return true if given text is present in a specified table of the response.
1558    *
1559    * @param tableSummaryOrId table summary or id to inspect for expected text.
1560    * @param text expected text to check for.
1561    */
1562   public boolean isTextInTable(String tableSummaryOrId, String text) {
1563     HtmlTable table = getHtmlTable(tableSummaryOrId);
1564     if (table == null) {
1565       throw new RuntimeException("No table with summary or id ["
1566         + tableSummaryOrId + "] found in response.");
1567     }
1568     for (int row = 0; row < table.getRowCount(); row++) {
1569       for (int col = 0; table.getCellAt(row, col) != null; col++) {
1570         HtmlTableCell cell = table.getCellAt(row, col);
1571         if (cell != null) {
1572           String cellHtml = cell.asText();
1573           if (cellHtml.indexOf(text) != -1) {
1574             return true;
1575           }
1576         }
1577       }
1578     }
1579     return false;
1580   }
1581 
1582   @Override
1583   public Table getTable(String tableSummaryNameOrId) {
1584     HtmlTable table = getHtmlTable(tableSummaryNameOrId);
1585     Table result = new Table();
1586     for (int i = 0; i < table.getRowCount(); i++) {
1587       Row newRow = new Row();
1588       HtmlTableRow htmlRow = table.getRow(i);
1589       CellIterator cellIt = htmlRow.getCellIterator();
1590       while (cellIt.hasNext()) {
1591         HtmlTableCell htmlCell = cellIt.nextCell();
1592         newRow.appendCell(new Cell(htmlCell.asText(), htmlCell
1593             .getColumnSpan(), htmlCell.getRowSpan()));
1594       }
1595       result.appendRow(newRow);
1596     }
1597     return result;
1598   }
1599 
1600   /**
1601    * Return the HtmlUnit WebTable object representing a specified table in the current response. Null is returned if a
1602    * parsing exception occurs looking for the table or no table with the id or summary could be found.
1603    *
1604    * @param tableSummaryNameOrId summary or id of the table to return.
1605    */
1606   private HtmlTable getHtmlTable(String tableSummaryNameOrId) {
1607     try {
1608       return (HtmlTable) ((HtmlPage) win.getEnclosedPage())
1609           .getHtmlElementById(tableSummaryNameOrId);
1610     } catch (ElementNotFoundException e) {
1611       // Not found
1612     }
1613     try {
1614       return (HtmlTable) ((HtmlPage) win.getEnclosedPage())
1615           .getDocumentElement().getOneHtmlElementByAttribute("table",
1616               "summary", tableSummaryNameOrId);
1617     } catch (ElementNotFoundException e) {
1618       // Not found
1619     }
1620     try {
1621       return (HtmlTable) ((HtmlPage) win.getEnclosedPage())
1622           .getDocumentElement().getOneHtmlElementByAttribute("table",
1623               "name", tableSummaryNameOrId);
1624     } catch (ElementNotFoundException e) {
1625       // Not found
1626     }
1627     return null;
1628   }
1629 
1630   @Override
1631   public boolean hasTable(String tableSummaryNameOrId) {
1632     return getHtmlTable(tableSummaryNameOrId) != null;
1633   }
1634 
1635   /**
1636    * Submit the current form with the default submit button. See {@link #getForm}for an explanation of how the
1637    * current form is established.
1638    */
1639   @Override
1640   public void submit() {
1641     HtmlElement btn = getSubmitButton();
1642     if (btn == null) {
1643       throw new RuntimeException("No submit button found in current form.");
1644     }
1645     try {
1646       btn.click();
1647     } catch (FailingHttpStatusCodeException e) {
1648       throw new TestingEngineResponseException(
1649         e.getStatusCode(), e);
1650     } catch (IOException e) {
1651       throw new RuntimeException(
1652         "HtmlUnit Error submitting form using default submit button, "
1653           + "check that form has single submit button, otherwise use submit(name): \n", e);
1654     }
1655   }
1656 
1657   /**
1658    * Submit the current form with the specified submit button. See {@link #getForm}for an explanation of how the
1659    * current form is established.
1660    *
1661    * @param buttonName name of the button to use for submission.
1662    */
1663   @Override
1664   public void submit(String buttonName) {
1665     HtmlElement btn = getSubmitButton(buttonName);
1666     if (btn == null) {
1667       throw new RuntimeException("No submit button found in current form.");
1668     }
1669     try {
1670       btn.click();
1671     } catch (FailingHttpStatusCodeException e) {
1672       throw new TestingEngineResponseException(
1673         e.getStatusCode(), e);
1674     } catch (IOException e) {
1675       throw new RuntimeException(
1676         "HtmlUnit Error submitting form using default submit button", e);
1677     }
1678   }
1679 
1680   /**
1681    * Submit the current form with the specifed submit button (by name and value). See {@link #getForm}for an
1682    * explanation of how the current form is established.
1683    *
1684    * @param buttonName name of the button to use for submission.
1685    * @param buttonValue value/label of the button to use for submission
1686    */
1687   @Override
1688   public void submit(String buttonName, String buttonValue) {
1689     List<HtmlElement> l = new LinkedList<HtmlElement>();
1690     l.addAll(getForm().getInputsByName(buttonName));
1691     l.addAll(getForm().getButtonsByName(buttonName));
1692     try {
1693       for (int i = 0; i < l.size(); i++) {
1694         Object o = l.get(i);
1695         if (o instanceof HtmlSubmitInput) {
1696           HtmlSubmitInput inpt = (HtmlSubmitInput) o;
1697           if (inpt.getValueAttribute().equals(buttonValue)) {
1698             inpt.click();
1699             return;
1700           }
1701         }
1702         if (o instanceof HtmlImageInput) {
1703           HtmlImageInput inpt = (HtmlImageInput) o;
1704           if (inpt.getValueAttribute().equals(buttonValue)) {
1705             inpt.click();
1706             return;
1707           }
1708         }
1709         if (o instanceof HtmlButton) {
1710           HtmlButton inpt = (HtmlButton) o;
1711           if (inpt.getTypeAttribute().equals("submit")
1712             && inpt.getValueAttribute().equals(buttonValue)) {
1713             inpt.click();
1714             return;
1715           }
1716         }
1717       }
1718     } catch (FailingHttpStatusCodeException e) {
1719       // entirely possible that it can fail here
1720       throw new TestingEngineResponseException(
1721           e.getStatusCode(), e);
1722     } catch (IOException e) {
1723       throw new RuntimeException(
1724           "HtmlUnit Error submitting form using submit button with name ["
1725             + buttonName + "] and value [" + buttonValue
1726             + "]", e);
1727     }
1728     throw new RuntimeException(
1729         "No submit button found in current form with name ["
1730           + buttonName + "] and value [" + buttonValue + "].");
1731   }
1732 
1733   /**
1734    * Reset the current form. See {@link #getForm}for an explanation of how the current form is established.
1735    */
1736   @Override
1737   public void reset() {
1738     getForm().reset();
1739   }
1740 
1741   /**
1742    * Return true if a link is present in the current response containing the specified text (note that HttpUnit uses
1743    * contains rather than an exact match - if this is a problem consider using ids on the links to uniquely identify
1744    * them).
1745    *
1746    * @param linkText text to check for in links on the response.
1747    * @param index The 0-based index, when more than one link with the same text is expected.
1748    */
1749   @Override
1750   public boolean hasLinkWithText(String linkText, int index) {
1751     return getLinkWithText(linkText, index) != null;
1752   }
1753 
1754   @Override
1755   public boolean hasLinkWithExactText(String linkText, int index) {
1756     return getLinkWithExactText(linkText, index) != null;
1757   }
1758 
1759   @Override
1760   public boolean hasLinkWithImage(String imageFileName, int index) {
1761     return getLinkWithImage(imageFileName, index) != null;
1762   }
1763 
1764   /**
1765    * Return true if a link is present in the current response with the specified id.
1766    *
1767    * @param anId link id to check for.
1768    */
1769   @Override
1770   public boolean hasLink(String anId) {
1771     try {
1772       ((HtmlPage) win.getEnclosedPage()).getHtmlElementById(anId);
1773     } catch (ElementNotFoundException e) {
1774       return false;
1775     }
1776     return true;
1777   }
1778 
1779   @Override
1780   public void clickLinkWithText(String linkText, int index) {
1781     HtmlAnchor link = getLinkWithText(linkText, index);
1782     if (link == null) {
1783       throw new RuntimeException("No Link found for \"" + linkText
1784         + "\" with index " + index);
1785     }
1786     try {
1787       link.click();
1788     } catch (IOException e) {
1789       throw new RuntimeException("Click failed", e);
1790     }
1791   }
1792 
1793   @Override
1794   public void clickLinkWithExactText(String linkText, int index) {
1795     HtmlAnchor link = getLinkWithExactText(linkText, index);
1796     if (link == null) {
1797       throw new RuntimeException("No Link found for \"" + linkText
1798         + "\" with index " + index);
1799     }
1800     try {
1801       link.click();
1802     } catch (IOException e) {
1803       throw new RuntimeException("Click failed", e);
1804     }
1805   }
1806 
1807   private HtmlCheckBoxInput getCheckbox(String checkBoxName) {
1808     Object[] l = getForm().getInputsByName(checkBoxName).toArray();
1809     for (int i = 0; i < l.length; i++) {
1810       if (l[i] instanceof HtmlCheckBoxInput) {
1811         return (HtmlCheckBoxInput) l[i];
1812       }
1813     }
1814     throw new RuntimeException("No checkbox with name [" + checkBoxName
1815       + "] was found in current form.");
1816   }
1817 
1818   private HtmlCheckBoxInput getCheckbox(String checkBoxName, String value) {
1819     Object[] l = getForm().getInputsByName(checkBoxName).toArray();
1820     for (int i = 0; i < l.length; i++) {
1821       if ((l[i] instanceof HtmlCheckBoxInput) &&
1822         ((HtmlCheckBoxInput) l[i]).getValueAttribute()
1823             .equals(value)) {
1824         return (HtmlCheckBoxInput) l[i];
1825       }
1826     }
1827     throw new RuntimeException("No checkbox with name [" + checkBoxName
1828       + "] and value [" + value + "] was found in current form.");
1829   }
1830 
1831   /**
1832    * Select a specified checkbox. If the checkbox is already checked then the checkbox will stay checked.
1833    *
1834    * @param checkBoxName name of checkbox to be deselected.
1835    */
1836   @Override
1837   public void checkCheckbox(String checkBoxName) {
1838     HtmlCheckBoxInput cb = getCheckbox(checkBoxName);
1839     if (!cb.isChecked()) {
1840       try {
1841         cb.click();
1842       } catch (IOException e) {
1843         throw new RuntimeException("checkCheckbox failed", e);
1844       }
1845     }
1846   }
1847 
1848   @Override
1849   public void checkCheckbox(String checkBoxName, String value) {
1850     HtmlCheckBoxInput cb = getCheckbox(checkBoxName, value);
1851     if (!cb.isChecked()) {
1852       try {
1853         cb.click();
1854       } catch (IOException e) {
1855         e.printStackTrace();
1856         throw new RuntimeException("checkCheckbox failed", e);
1857       }
1858     }
1859   }
1860 
1861   /**
1862    * Deselect a specified checkbox. If the checkbox is already unchecked then the checkbox will stay unchecked.
1863    *
1864    * @param checkBoxName name of checkbox to be deselected.
1865    */
1866   @Override
1867   public void uncheckCheckbox(String checkBoxName) {
1868     HtmlCheckBoxInput cb = getCheckbox(checkBoxName);
1869     if (cb.isChecked()) {
1870       try {
1871         cb.click();
1872       } catch (IOException e) {
1873         e.printStackTrace();
1874         throw new RuntimeException("checkCheckbox failed", e);
1875       }
1876     }
1877   }
1878 
1879   @Override
1880   public void uncheckCheckbox(String checkBoxName, String value) {
1881     HtmlCheckBoxInput cb = getCheckbox(checkBoxName, value);
1882     if (cb.isChecked()) {
1883       try {
1884         cb.click();
1885       } catch (IOException e) {
1886         e.printStackTrace();
1887         throw new RuntimeException("uncheckCheckbox failed", e);
1888       }
1889     }
1890   }
1891 
1892   private HtmlRadioButtonInput getRadioOption(String radioGroup, String radioOption) {
1893     for (HtmlForm form : getForms()) {
1894       List<HtmlRadioButtonInput> buttons = form.getRadioButtonsByName(radioGroup);
1895       for (HtmlRadioButtonInput button : buttons) {
1896         if (button.getValueAttribute().equals(radioOption)) {
1897           return button;
1898         }
1899       }
1900     }
1901     return null;
1902   }
1903 
1904   /**
1905    * Clicks a radio option. Asserts that the radio option exists first. *
1906    *
1907    * @param radioGroup name of the radio group.
1908    * @param radioOption value of the option to check for.
1909    */
1910   @Override
1911   public void clickRadioOption(String radioGroup, String radioOption) {
1912     HtmlRadioButtonInput rb = getRadioOption(radioGroup, radioOption);
1913     if (!rb.isChecked()) {
1914       try {
1915         rb.click();
1916       } catch (IOException e) {
1917         e.printStackTrace();
1918         throw new RuntimeException("checkCheckbox failed", e);
1919       }
1920     }
1921   }
1922 
1923   /**
1924    * Navigate by submitting a request based on a link with a given ID. A RuntimeException is thrown if no such link
1925    * can be found.
1926    *
1927    * @param anID id of link to be navigated.
1928    */
1929   @Override
1930   public void clickLink(String anID) {
1931     clickElementByXPath("//a[@id=\"" + anID + "\"]");
1932   }
1933 
1934   private HtmlAnchor getLinkWithImage(String filename, int index) {
1935     return (HtmlAnchor) getHtmlElementByXPath("(//a[img[contains(@src,\""
1936       + filename + "\")]])[" + (index + 1) + "]");
1937   }
1938 
1939   private HtmlAnchor getLinkWithText(String linkText, int index) {
1940     List<HtmlAnchor> lnks = ((HtmlPage) win.getEnclosedPage()).getAnchors();
1941     int count = 0;
1942     for (HtmlAnchor lnk : lnks) {
1943       if ((lnk.asText().indexOf(linkText) >= 0) && (count++ == index)) {
1944         return lnk;
1945       }
1946     }
1947     return null;
1948   }
1949 
1950   private HtmlAnchor getLinkWithExactText(String linkText, int index) {
1951     List<HtmlAnchor> lnks = ((HtmlPage) win.getEnclosedPage()).getAnchors();
1952     int count = 0;
1953     for (HtmlAnchor lnk : lnks) {
1954       if ((lnk.asText().equals(linkText)) && (count++ == index)) {
1955         return lnk;
1956       }
1957     }
1958     return null;
1959   }
1960 
1961   /**
1962    * Navigate by submitting a request based on a link with a given image file name. A RuntimeException is thrown if no
1963    * such link can be found.
1964    *
1965    * @param imageFileName A suffix of the image's filename; for example, to match
1966    *            <tt>"images/my_icon.png"<tt>, you could just pass in
1967    *                      <tt>"my_icon.png"<tt>.
1968    */
1969   @Override
1970   public void clickLinkWithImage(String imageFileName, int index) {
1971     HtmlAnchor link = getLinkWithImage(imageFileName, index);
1972     if (link == null) {
1973       throw new RuntimeException("No Link found with filename \""
1974         + imageFileName + "\" and index " + index);
1975     }
1976     try {
1977       link.click();
1978     } catch (IOException e) {
1979       throw new RuntimeException("Click failed", e);
1980     }
1981   }
1982 
1983   @Override
1984   public boolean hasElement(String anID) {
1985     return getHtmlElement(anID) != null;
1986   }
1987 
1988   @Override
1989   public boolean hasElementByXPath(String xpath) {
1990     return getHtmlElementByXPath(xpath) != null;
1991   }
1992 
1993   @Override
1994   public void clickElementByXPath(String xpath) {
1995     HtmlElement e = getHtmlElementByXPath(xpath);
1996     if (e == null) {
1997       throw new RuntimeException("No element found with xpath \"" + xpath
1998         + "\"");
1999     }
2000     try {
2001       e.click();
2002     } catch (IOException exp) {
2003       throw new RuntimeException("Click failed", exp);
2004     }
2005   }
2006 
2007   @Override
2008   public String getElementAttributByXPath(String xpath, String attribut) {
2009     HtmlElement e = getHtmlElementByXPath(xpath);
2010     if (e == null) {
2011       return null;
2012     }
2013     return e.getAttribute(attribut);
2014   }
2015 
2016   @Override
2017   public String getElementTextByXPath(String xpath) {
2018     HtmlElement e = getHtmlElementByXPath(xpath);
2019     if (e == null) {
2020       return null;
2021     }
2022     return e.asText();
2023   }
2024 
2025   /**
2026    * Click the indicated button (input type=button).
2027    *
2028    * @param buttonId
2029    */
2030   @Override
2031   public void clickButton(String buttonId) {
2032     HtmlElement btn = getButton(buttonId);
2033     try {
2034       btn.click();
2035     } catch (Exception e) {
2036       throw new RuntimeException(e);
2037     }
2038   }
2039 
2040   /**
2041     * Clicks the first button that contains the specified text as its label.
2042     * For HTML input tags of type submit, reset, or button, this checks the
2043     * value attribute.  For HTML button tags, this checks the element's
2044     * content by converting it to text.  or an HTML &lt;button&gt; tag.
2045     */
2046   @Override
2047   public void clickButtonWithText(String buttonValueText) {
2048     HtmlElement b = getButtonWithText(buttonValueText);
2049     if (b != null) {
2050       try {
2051         b.click();
2052       } catch (Exception e) {
2053         throw new RuntimeException(e);
2054       }
2055     }
2056     else {
2057       throw new RuntimeException("No button found with text: " + buttonValueText);
2058     }
2059   }
2060 
2061   /**
2062    * Return true if a radio group contains the indicated option.
2063    *
2064    * @param radioGroup name of the radio group.
2065    * @param radioOption value of the option to check for.
2066    */
2067   @Override
2068   public boolean hasRadioOption(String radioGroup, String radioOption) {
2069     return getRadioOption(radioGroup, radioOption) != null;
2070   }
2071 
2072   @Override
2073   public String getSelectedRadio(String radioGroup) {
2074     List<HtmlRadioButtonInput> radios = getForm().getRadioButtonsByName(radioGroup);
2075     for (HtmlRadioButtonInput radio : radios) {
2076       if (radio.isChecked()) {
2077         return radio.getValueAttribute();
2078       }
2079     }
2080     throw new RuntimeException("Unexpected state: no radio button was selected in radio group [" + radioGroup + "]. Is it possible in a real browser?");
2081   }
2082 
2083   /**
2084    * Return true if a select box contains the indicated option.
2085    *
2086    * @param selectName name of the select box.
2087    * @param optionLabel label of the option.
2088    */
2089   @Override
2090   public boolean hasSelectOption(String selectName, String optionLabel) {
2091     String[] opts = getSelectOptionValues(selectName);
2092     for (int i = 0; i < opts.length; i++) {
2093       String label = getSelectOptionLabelForValue(selectName, opts[i]);
2094       if (label.equals(optionLabel)) {
2095         return true;
2096       }
2097     }
2098     return false;
2099   }
2100 
2101   /**
2102    * Return true if a select box contains the indicated option.
2103    *
2104    * @param selectName name of the select box.
2105    * @param optionValue value of the option.
2106    */
2107   @Override
2108   public boolean hasSelectOptionValue(String selectName, String optionValue) {
2109     String[] opts = getSelectOptionValues(selectName);
2110     for (int i = 0; i < opts.length; i++) {
2111       if (opts[i].equals(optionValue)) {
2112         return true;
2113       }
2114     }
2115     return false;
2116   }
2117 
2118   /**
2119    * Select the specified set of options in the select element
2120    * with the provided name.
2121    * @param selectName name of the select box
2122    * @param options set of options to select.
2123    */
2124   @Override
2125   public void selectOptions(String selectName, String[] options) {
2126     HtmlSelect sel = getForm().getSelectByName(selectName);
2127     if (!sel.isMultipleSelectEnabled() && options.length > 1) {
2128       throw new RuntimeException("Multiselect not enabled");
2129     }
2130     for (String option : options) {
2131       boolean found = false;
2132       for (HtmlOption opt : sel.getOptions()) {
2133         if (opt.getValueAttribute().equals(option)) {
2134           sel.setSelectedAttribute(opt, true);
2135           found = true;
2136           break;
2137         }
2138       }
2139       if (!found) {
2140         throw new RuntimeException("Option " + option
2141           + " not found");
2142       }
2143     }
2144   }
2145 
2146   /**
2147    * Return true if the Nth select box contains the indicated option.
2148    *
2149    * @param selectName name of the select box.
2150    * @param index the 0-based index of the select element when multiple
2151    * select elements are expected.
2152    * @param optionLabel label of the option.
2153    */
2154   @Override
2155   public boolean hasSelectOption(String selectName, int index, String optionLabel) {
2156     String[] opts = getSelectOptionValues(selectName, index);
2157     for (int i = 0; i < opts.length; i++) {
2158       String label = getSelectOptionLabelForValue(selectName, index, opts[i]);
2159       if (label.equals(optionLabel)) {
2160         return true;
2161       }
2162     }
2163     return false;
2164   }
2165 
2166   /**
2167    * Return true if the Nth select box contains the indicated option.
2168    *
2169    * @param selectName name of the select box.
2170    * @param index the 0-based index of the select element when multiple
2171    * select elements are expected.
2172    * @param optionValue value of the option.
2173    */
2174   @Override
2175   public boolean hasSelectOptionValue(String selectName, int index, String optionValue) {
2176     String[] opts = getSelectOptionValues(selectName, index);
2177     for (int i = 0; i < opts.length; i++) {
2178       if (opts[i].equals(optionValue)) {
2179         return true;
2180       }
2181     }
2182     return false;
2183   }
2184 
2185   /**
2186    * Select the specified set of options in the select element
2187    * with the provided name.
2188    * @param selectName name of the select box
2189    * @param index the 0-based index of the select element when multiple
2190    * select elements are expected.
2191    * @param options set of options to select.
2192    */
2193   @Override
2194   public void selectOptions(String selectName, int index, String[] options) {
2195     List<HtmlSelect> sels = getForm().getSelectsByName(selectName);
2196     if (sels == null || sels.size() < index + 1) {
2197       throw new RuntimeException("Did not find select with name [" + selectName
2198         + "] at index " + index);
2199     }
2200     HtmlSelect sel = sels.get(index);
2201     if (!sel.isMultipleSelectEnabled() && options.length > 1) {
2202       throw new RuntimeException("Multiselect not enabled");
2203     }
2204     for (String option : options) {
2205       boolean found = false;
2206       for (HtmlOption opt : sel.getOptions()) {
2207         if (opt.getValueAttribute().equals(option)) {
2208           sel.setSelectedAttribute(opt, true);
2209           found = true;
2210           break;
2211         }
2212       }
2213       if (!found) {
2214         throw new RuntimeException("Option " + option
2215           + " not found");
2216       }
2217     }
2218   }
2219 
2220   @Override
2221   public void unselectOptions(String selectName, String[] options) {
2222     HtmlSelect sel = getForm().getSelectByName(selectName);
2223     if (!sel.isMultipleSelectEnabled() && options.length > 1) {
2224       throw new RuntimeException("Multiselect not enabled");
2225     }
2226     for (String option : options) {
2227       boolean found = false;
2228       for (HtmlOption opt : sel.getOptions()) {
2229         if (opt.asText().equals(option)) {
2230           sel.setSelectedAttribute(opt, false);
2231           found = true;
2232           break;
2233         }
2234       }
2235       if (!found) {
2236         throw new RuntimeException("Option " + option
2237           + " not found");
2238       }
2239     }
2240   }
2241 
2242   @Override
2243   public void unselectOptions(String selectName, int index, String[] options) {
2244     List<HtmlSelect> sels = getForm().getSelectsByName(selectName);
2245     if (sels == null || sels.size() < index + 1) {
2246       throw new RuntimeException("Did not find select with name [" + selectName
2247         + "] at index " + index);
2248     }
2249     HtmlSelect sel = sels.get(index);
2250     if (!sel.isMultipleSelectEnabled() && options.length > 1) {
2251       throw new RuntimeException("Multiselect not enabled");
2252     }
2253     for (String option : options) {
2254       boolean found = false;
2255       for (HtmlOption opt : sel.getOptions()) {
2256         if (opt.asText().equals(option)) {
2257           sel.setSelectedAttribute(opt, false);
2258           found = true;
2259           break;
2260         }
2261       }
2262       if (!found) {
2263         throw new RuntimeException("Option " + option
2264           + " not found");
2265       }
2266     }
2267   }
2268 
2269   @Override
2270   public boolean isTextInElement(String elementID, String text) {
2271     return isTextInElement(getHtmlElement(elementID), text);
2272   }
2273 
2274   /**
2275    * Return true if a given string is contained within the specified element.
2276    *
2277    * @param element org.w3c.com.Element to inspect.
2278    * @param text text to check for.
2279    */
2280   private boolean isTextInElement(HtmlElement element, String text) {
2281     return element.asText().indexOf(text) >= 0;
2282   }
2283 
2284   @Override
2285   public boolean isMatchInElement(String elementID, String regexp) {
2286     return isMatchInElement(getHtmlElement(elementID), regexp);
2287   }
2288 
2289   /**
2290    * Return true if a given regexp is contained within the specified element.
2291    *
2292    * @param element org.w3c.com.Element to inspect.
2293    * @param regexp regexp to match.
2294    */
2295   private boolean isMatchInElement(HtmlElement element, String regexp) {
2296     RE re = getRE(regexp);
2297     return re.match(element.asText());
2298   }
2299 
2300   private RE getRE(String regexp) {
2301     try {
2302       return new RE(regexp, RE.MATCH_SINGLELINE);
2303     } catch (RESyntaxException e) {
2304       throw new RuntimeException(e);
2305     }
2306   }
2307 
2308   /**
2309    * {@inheritDoc}
2310    */
2311   @Override
2312   public void gotoRootWindow() {
2313     win = win.getTopWindow();
2314   }
2315 
2316   private void setMainWindow(WebWindow win) {
2317     wc.setCurrentWindow(win);
2318     this.win = win;
2319   }
2320 
2321   /**
2322    * Return the given frame in the current conversation.
2323    *
2324    * @param frameNameOrId Frame name or ID.
2325    * @return The frame found or null.
2326    */
2327   private WebWindow getFrame(String frameNameOrId) {
2328     // First try ID
2329     for (FrameWindow frame : getCurrentPage().getFrames()) {
2330       if (frameNameOrId.equals(frame.getFrameElement().getId())) {
2331         return frame;
2332       }
2333     }
2334     // Now try with Name
2335     for (FrameWindow frame : getCurrentPage().getFrames()) {
2336       if (frameNameOrId.equals(frame.getName())) {
2337         return frame;
2338       }
2339     }
2340     // Nothing was found.
2341     return null;
2342   }
2343 
2344   /**
2345    * @param testContext The testContext to set.
2346    */
2347   private void setTestContext(TestContext testContext) {
2348     this.testContext = testContext;
2349   }
2350 
2351   /**
2352    * @return Returns the testContext.
2353    */
2354   protected TestContext getTestContext() {
2355     return testContext;
2356   }
2357 
2358   @Override
2359   public void setExpectedJavaScriptAlert(JavascriptAlert[] alerts)
2360       throws ExpectedJavascriptAlertException {
2361     if (this.expectedJavascriptAlerts.size() > 0) {
2362       throw new ExpectedJavascriptAlertException(
2363           (expectedJavascriptAlerts.get(0))
2364               .getMessage());
2365     }
2366     for (int i = 0; i < alerts.length; i++) {
2367       expectedJavascriptAlerts.add(alerts[i]);
2368     }
2369   }
2370 
2371   @Override
2372   public void setExpectedJavaScriptConfirm(JavascriptConfirm[] confirms)
2373       throws ExpectedJavascriptConfirmException {
2374     if (this.expectedJavascriptConfirms.size() > 0) {
2375       throw new ExpectedJavascriptConfirmException(
2376           (expectedJavascriptConfirms.get(0))
2377               .getMessage());
2378     }
2379     for (int i = confirms.length - 1; i >= 0; i--) {
2380       expectedJavascriptConfirms.add(confirms[i]);
2381     }
2382   }
2383 
2384   @Override
2385   public void setExpectedJavaScriptPrompt(JavascriptPrompt[] prompts)
2386       throws ExpectedJavascriptPromptException {
2387     if (this.expectedJavascriptPrompts.size() > 0) {
2388       throw new ExpectedJavascriptPromptException(
2389           (expectedJavascriptPrompts.get(0))
2390               .getMessage());
2391     }
2392     for (int i = prompts.length - 1; i >= 0; i--) {
2393       expectedJavascriptPrompts.add(prompts[i]);
2394     }
2395   }
2396 
2397   /*
2398    * (non-Javadoc)
2399    *
2400    * @see net.sourceforge.jwebunit.api.ITestingEngine#getElementByXPath(java.lang.String)
2401    */
2402   @Override
2403   public IElement getElementByXPath(String xpath) {
2404     HtmlElement element = this.getHtmlElementByXPath(xpath);
2405     if (element != null) {
2406       return new HtmlUnitElementImpl(element);
2407     }
2408     return null;
2409   }
2410 
2411   /*
2412    * (non-Javadoc)
2413    *
2414    * @see net.sourceforge.jwebunit.api.ITestingEngine#getElementByID(java.lang.String)
2415    */
2416   @Override
2417   public IElement getElementByID(String id) {
2418     HtmlElement element = this.getHtmlElement(id);
2419     if (element != null) {
2420       return new HtmlUnitElementImpl(element);
2421     }
2422     return null;
2423   }
2424 
2425   /*
2426    * (non-Javadoc)
2427    *
2428    * @see net.sourceforge.jwebunit.api.ITestingEngine#getElementsByXPath(java.lang.String)
2429    */
2430   @Override
2431   public List<IElement> getElementsByXPath(String xpath) {
2432     List<IElement> children = new ArrayList<IElement>();
2433     for (Object child : getCurrentPage().getByXPath(xpath)) {
2434       if (child instanceof HtmlElement) {
2435         children.add(new HtmlUnitElementImpl((HtmlElement) child));
2436       }
2437     }
2438     return children;
2439   }
2440 
2441   /*
2442    * (non-Javadoc)
2443    *
2444    * @see net.sourceforge.jwebunit.api.ITestingEngine#getServerResponseCode()
2445    */
2446   @Override
2447   public int getServerResponseCode() {
2448     return getWebResponse().getStatusCode();
2449   }
2450 
2451   /**
2452    * Get the last WebResponse from HtmlUnit.
2453    */
2454   public WebResponse getWebResponse() {
2455     return wc.getCurrentWindow().getEnclosedPage().getWebResponse();
2456   }
2457 
2458   /*
2459    * @param ignoreFailingStatusCodes the ignoreFailingStatusCodes to set
2460    */
2461   @Override
2462   public void setIgnoreFailingStatusCodes(boolean ignore) {
2463     ignoreFailingStatusCodes = ignore;
2464     if (wc != null) {
2465       wc.getOptions().setThrowExceptionOnFailingStatusCode(!ignore);
2466     }
2467   }
2468 
2469   /*
2470    * (non-Javadoc)
2471    *
2472    * @see net.sourceforge.jwebunit.api.ITestingEngine#getHeader(java.lang.String)
2473    */
2474   @Override
2475   public String getHeader(String name) {
2476     return getWebResponse().getResponseHeaderValue(name);
2477   }
2478 
2479   /*
2480    * (non-Javadoc)
2481    *
2482    * @see net.sourceforge.jwebunit.api.ITestingEngine#getAllHeaders()
2483    */
2484   @Override
2485   @Deprecated
2486   public Map<String, String> getAllHeaders() {
2487     Map<String, String> map = new java.util.HashMap<String, String>();
2488     for (NameValuePair header : getWebResponse().getResponseHeaders()) {
2489       map.put(header.getName(), header.getValue());
2490     }
2491     return map;
2492   }
2493 
2494   @Override
2495   public List<HttpHeader> getResponseHeaders() {
2496     List<HttpHeader> result = new LinkedList<HttpHeader>();
2497     for (NameValuePair header : getWebResponse().getResponseHeaders()) {
2498       result.add(new HttpHeader(header.getName(), header.getValue()));
2499     }
2500     return result;
2501   }
2502 
2503   /**
2504    * An alternative to setting the {@link TestContext#setUserAgent(String) user agent string manually}
2505    * is to provide it with all the information for a complete browser version.
2506    *
2507    * @see com.gargoylesoftware.htmlunit.BrowserVersion
2508    * @return The default browser version
2509    */
2510   public BrowserVersion getDefaultBrowserVersion() {
2511     return defaultBrowserVersion;
2512   }
2513 
2514   /**
2515    * An alternative to setting the {@link TestContext#setUserAgent(String) user agent string manually}
2516    * is to provide it with all the information for a complete browser version.
2517    *
2518    * @see com.gargoylesoftware.htmlunit.BrowserVersion
2519    * @param the browser version to set as default for this engine instance
2520    */
2521   public void setDefaultBrowserVersion(BrowserVersion defaultBrowserVersion) {
2522     this.defaultBrowserVersion = defaultBrowserVersion;
2523   }
2524 
2525   @Override
2526   public void setTimeout(int milliseconds) {
2527     if (wc != null && wc.getWebConnection() != null) {
2528       throw new IllegalArgumentException("Cannot set the timeout when the WebConnection has already been created.");
2529     }
2530     timeout = milliseconds;
2531   }
2532 
2533   public void setRefreshHandler(RefreshHandler handler) {
2534     this.refreshHandler = handler;
2535 
2536     if (wc != null) {
2537       wc.setRefreshHandler(refreshHandler);
2538     }
2539   }
2540 
2541 }