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.webdriver;
20  
21  import com.gargoylesoftware.htmlunit.WebClient;
22  import com.gargoylesoftware.htmlunit.WebResponse;
23  import net.sourceforge.jwebunit.api.HttpHeader;
24  import net.sourceforge.jwebunit.api.IElement;
25  import net.sourceforge.jwebunit.api.ITestingEngine;
26  import net.sourceforge.jwebunit.exception.ExpectedJavascriptAlertException;
27  import net.sourceforge.jwebunit.exception.ExpectedJavascriptConfirmException;
28  import net.sourceforge.jwebunit.exception.ExpectedJavascriptPromptException;
29  import net.sourceforge.jwebunit.exception.TestingEngineResponseException;
30  import net.sourceforge.jwebunit.html.Cell;
31  import net.sourceforge.jwebunit.html.Row;
32  import net.sourceforge.jwebunit.html.Table;
33  import net.sourceforge.jwebunit.javascript.JavascriptAlert;
34  import net.sourceforge.jwebunit.javascript.JavascriptConfirm;
35  import net.sourceforge.jwebunit.javascript.JavascriptPrompt;
36  import net.sourceforge.jwebunit.util.TestContext;
37  import org.apache.commons.lang.StringUtils;
38  import org.apache.http.Header;
39  import org.apache.http.HttpStatus;
40  import org.apache.regexp.RE;
41  import org.apache.regexp.RESyntaxException;
42  import org.browsermob.proxy.ProxyServer;
43  import org.browsermob.proxy.http.BrowserMobHttpRequest;
44  import org.browsermob.proxy.http.BrowserMobHttpResponse;
45  import org.browsermob.proxy.http.RequestInterceptor;
46  import org.browsermob.proxy.http.ResponseInterceptor;
47  import org.browsermob.proxy.jetty.util.MultiException;
48  import org.openqa.selenium.By;
49  import org.openqa.selenium.Cookie;
50  import org.openqa.selenium.JavascriptExecutor;
51  import org.openqa.selenium.NoSuchElementException;
52  import org.openqa.selenium.NoSuchWindowException;
53  import org.openqa.selenium.Proxy;
54  import org.openqa.selenium.WebDriver;
55  import org.openqa.selenium.WebElement;
56  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
57  import org.openqa.selenium.remote.CapabilityType;
58  import org.openqa.selenium.remote.DesiredCapabilities;
59  import org.openqa.selenium.support.ui.Select;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  import java.io.IOException;
64  import java.io.InputStream;
65  import java.net.MalformedURLException;
66  import java.net.URL;
67  import java.util.ArrayList;
68  import java.util.Calendar;
69  import java.util.Date;
70  import java.util.LinkedList;
71  import java.util.List;
72  import java.util.Map;
73  import java.util.Random;
74  import java.util.Set;
75  
76  /**
77   * Acts as the wrapper for Webdriver access. A testing engine is initialized with a given URL, and maintains
78   * conversational state as the dialog progresses through link navigation, form submission, etc.
79   *
80   * @author Julien Henry
81   */
82  public class WebDriverTestingEngineImpl implements ITestingEngine {
83  
84    /**
85     * Logger for this class.
86     */
87    private final Logger logger = LoggerFactory.getLogger(WebDriverTestingEngineImpl.class);
88    private ProxyServer proxyServer;
89    private WebDriver driver;
90    private TestContext testContext;
91    private static final int TRY_COUNT = 50;
92    private static final int DEFAULT_PORT = 8183;
93    private static final Random RANDOM = new Random();
94    private BrowserMobHttpResponse response;
95    // The xpath string that identifie the current form
96    // ie : @name='myForm'
97    private String formIdent;
98    private boolean throwExceptionOnScriptError = true;// TODO
99    private boolean ignoreFailingStatusCodes = false;
100   /**
101    * Is Javascript enabled.
102    */
103   private boolean jsEnabled = true;
104 
105   public WebDriverTestingEngineImpl() {
106   }
107 
108   public void beginAt(URL aInitialURL, TestContext aTestContext) throws TestingEngineResponseException {
109     this.setTestContext(aTestContext);
110     // start the proxy
111     Proxy proxy = startBrowserMobProxy();
112 
113     DesiredCapabilities capabilities = new DesiredCapabilities();
114     capabilities.setCapability(CapabilityType.PROXY, proxy);
115     capabilities.setCapability(CapabilityType.SUPPORTS_JAVASCRIPT, jsEnabled);
116     capabilities.setBrowserName("htmlunit");
117     capabilities.setVersion("firefox");
118 
119     driver = new HtmlUnitDriver(capabilities);
120 
121     // Reset form
122     formIdent = null;
123 
124     // Deal with cookies
125     for (javax.servlet.http.Cookie c : aTestContext.getCookies()) {
126       // FIXME Hack for BrowserMob
127       String domain = c.getDomain();
128       if ("localhost".equals(domain)) {
129         domain = null;
130       }
131       if ("".equals(domain)) {
132         domain = null;
133       }
134       Date expiry = null;
135       if (c.getMaxAge() != -1) {
136         Calendar cal = Calendar.getInstance();
137         cal.add(Calendar.SECOND, c.getMaxAge());
138         expiry = cal.getTime();
139       }
140       driver.manage().addCookie(
141         new org.openqa.selenium.Cookie(c
142           .getName(), c.getValue(), domain, c.getPath() != null ? c
143           .getPath() : "", expiry, c.getSecure()));
144     }
145     gotoPage(aInitialURL);
146   }
147 
148   private Proxy startBrowserMobProxy() {
149     for (int i = 1; i <= TRY_COUNT; i++) {
150       int port = getRandomPort();
151       proxyServer = new ProxyServer(port);
152       try {
153         proxyServer.start();
154         proxyServer.addResponseInterceptor(new ResponseInterceptor() {
155 
156           @Override
157           public void process(BrowserMobHttpResponse response) {
158             WebDriverTestingEngineImpl.this.response = response;
159           }
160         });
161         if (testContext.getRequestHeaders() != null && !testContext.getRequestHeaders().isEmpty()) {
162           proxyServer.addRequestInterceptor(new RequestInterceptor() {
163 
164             @Override
165             public void process(BrowserMobHttpRequest request) {
166               for (Map.Entry<String, String> requestHeader : testContext.getRequestHeaders().entrySet()) {
167                 request.addRequestHeader(requestHeader.getKey(), requestHeader.getValue());
168               }
169             }
170           });
171         }
172         if (StringUtils.isNotBlank(testContext.getUserAgent())) {
173           proxyServer.addRequestInterceptor(new RequestInterceptor() {
174             @Override
175             public void process(BrowserMobHttpRequest request) {
176               request.getMethod().removeHeaders("User-Agent");
177               request.addRequestHeader("User-Agent", testContext.getUserAgent());
178             }
179           });
180         }
181         return proxyServer.seleniumProxy();
182       } catch (Exception e) {
183         if (i < TRY_COUNT) {
184           logger.error("Error while starting BrowserMob proxy on port " + port + ". Retry...: " + e.getMessage(), e);
185           if (e instanceof MultiException) {
186             Exception e1 = ((MultiException) e).getException(0);
187             logger.error("First exception: " + e1.getMessage(), e1);
188           }
189           continue;
190         }
191       }
192     }
193     throw new RuntimeException("Unable to start BrowserMob proxy after " + TRY_COUNT + " retries");
194   }
195 
196   private static int getRandomPort() {
197     synchronized (RANDOM) {
198       return DEFAULT_PORT + RANDOM.nextInt(1000);
199     }
200   }
201 
202   public void setTestContext(TestContext testContext) {
203     this.testContext = testContext;
204   }
205 
206   public void closeBrowser() throws ExpectedJavascriptAlertException, ExpectedJavascriptConfirmException, ExpectedJavascriptPromptException {
207     formIdent = null;
208     if (driver != null) {
209       driver.quit();
210       driver = null;
211     }
212     if (proxyServer != null) {
213       try {
214         proxyServer.stop();
215         proxyServer = null;
216       } catch (Exception e) {
217         logger.error("Error while stopping proxy", e);
218         throw new RuntimeException("Error while stopping proxy", e);
219       }
220     }
221   }
222 
223   public void gotoPage(URL url) throws TestingEngineResponseException {
224     formIdent = null;
225     // Big hack for browsermob
226     String urlStr = url.toString().replace("http://localhost", "http://127.0.0.1");
227     driver.get(urlStr);
228     throwFailingHttpStatusCodeExceptionIfNecessary(
229       getServerResponseCode(), urlStr);
230   }
231 
232   /**
233    * Copied from {@link WebClient#throwFailingHttpStatusCodeExceptionIfNecessary(WebResponse)}
234    *
235    * @param webResponse
236    */
237   private void throwFailingHttpStatusCodeExceptionIfNecessary(int statusCode, String url) {
238     final boolean successful = (statusCode >= HttpStatus.SC_OK && statusCode < HttpStatus.SC_MULTIPLE_CHOICES)
239       || statusCode == HttpStatus.SC_USE_PROXY
240       || statusCode == HttpStatus.SC_NOT_MODIFIED;
241     if (!this.ignoreFailingStatusCodes && !successful) {
242       throw new TestingEngineResponseException(statusCode,
243         "unexpected status code [" + statusCode + "] at URL: [" + url + "]");
244     }
245   }
246 
247   public void setScriptingEnabled(boolean value) {
248     // This variable is used to set Javascript before wc is instancied
249     jsEnabled = value;
250     if (driver != null && driver instanceof HtmlUnitDriver) {
251       ((HtmlUnitDriver) driver).setJavascriptEnabled(value);
252     }
253   }
254 
255   public void setThrowExceptionOnScriptError(boolean value) {
256     this.throwExceptionOnScriptError = true;
257   }
258 
259   public List<javax.servlet.http.Cookie> getCookies() {
260     List<javax.servlet.http.Cookie> result = new LinkedList<javax.servlet.http.Cookie>();
261     Set<Cookie> cookies = driver.manage().getCookies();
262     for (Cookie cookie : cookies) {
263       javax.servlet.http.Cookie c = new javax.servlet.http.Cookie(
264         cookie.getName(), cookie.getValue());
265       c.setDomain(cookie.getDomain());
266       Date expire = cookie.getExpiry();
267       if (expire == null) {
268         c.setMaxAge(-1);
269       } else {
270         Date now = Calendar.getInstance().getTime();
271         // Convert milli-second to second
272         Long second = Long.valueOf((expire.getTime() - now.getTime()) / 1000);
273         c.setMaxAge(second.intValue());
274       }
275       c.setPath(cookie.getPath());
276       c.setSecure(cookie.isSecure());
277       result.add(c);
278     }
279     return result;
280   }
281 
282   public boolean hasWindow(String windowName) {
283     // Save current handle
284     String current = driver.getWindowHandle();
285     try {
286       driver.switchTo().window(windowName);
287       driver.switchTo().window(current);
288       return true;
289     } catch (NoSuchWindowException e) {
290       return false;
291     }
292   }
293 
294   public boolean hasWindowByTitle(String windowTitle) {
295     // Save current handle
296     String current = driver.getWindowHandle();
297     for (String handle : driver.getWindowHandles()) {
298       driver.switchTo().window(handle);
299       if (driver.getTitle().equals(windowTitle)) {
300         driver.switchTo().window(current);
301         return true;
302       }
303     }
304     driver.switchTo().window(current);
305     return false;
306   }
307 
308   public void gotoWindow(String windowName) {
309     driver.switchTo().window(windowName);
310   }
311 
312   public void gotoWindowByTitle(String title) {
313     // Save current handle
314     String current = driver.getWindowHandle();
315     for (String handle : driver.getWindowHandles()) {
316       driver.switchTo().window(handle);
317       if (title.equals(driver.getTitle())) {
318         return;
319       }
320     }
321     driver.switchTo().window(current);
322     throw new RuntimeException("No window found with title [" + title + "]");
323   }
324 
325   public void gotoWindow(int windowID) {
326     Set<String> handles = driver.getWindowHandles();
327     driver.switchTo().window(handles.toArray(new String[handles.size()])[windowID]);
328   }
329 
330   public void gotoRootWindow() {
331     driver.switchTo().defaultContent();
332   }
333 
334   public int getWindowCount() {
335     return driver.getWindowHandles().size();
336   }
337 
338   public void closeWindow() {
339     formIdent = null;
340     driver.close();
341     // FIXME Issue 1466 & 2834
342     if (getWindowCount() > 0) {
343       driver.switchTo().window(driver.getWindowHandles().iterator().next());
344     }
345   }
346 
347   public boolean hasFrame(String frameNameOrId) {
348     List<WebElement> frameset = driver.findElements(By.tagName("frame"));
349     for (WebElement frame : frameset) {
350       if (frameNameOrId.equals(frame.getAttribute("name"))
351         || frameNameOrId.equals(frame.getAttribute("id"))) {
352         return true;
353       }
354     }
355     return false;
356   }
357 
358   public void gotoFrame(String frameNameOrId) {
359     driver.switchTo().frame(frameNameOrId);
360   }
361 
362   public void setWorkingForm(int index) {
363     formIdent = "position()=" + (index + 1);
364   }
365 
366   public void setWorkingForm(String nameOrId, int index) {
367     if (nameOrId != null) {
368       formIdent = "(@name=" + escapeQuotes(nameOrId) + " or @id=" + escapeQuotes(nameOrId) + ")][position()="
369         + (index + 1);
370     } else {
371       formIdent = null;
372     }
373   }
374 
375   protected String formSelector() {
376     if (formIdent == null) {
377       return "//form";
378     }
379     return "//form[" + formIdent + "]";
380   }
381 
382   public boolean hasForm() {
383     return hasElementByXPath("//form");
384   }
385 
386   public boolean hasForm(String nameOrID) {
387     return hasElementByXPath("//form[@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + "]");
388   }
389 
390   public boolean hasForm(String nameOrID, int index) {
391     return hasElementByXPath("//form[@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + "][position()="
392       + (index + 1) + "]");
393   }
394 
395   public boolean hasFormParameterNamed(String paramName) {
396     return getWebElementByXPath("//*[@name=" + escapeQuotes(paramName) + "]", false, true) != null;
397   }
398 
399   private WebElement getWebElementByXPath(String xpathAfterForm, boolean searchOnlyInCurrentForm, boolean overrideWorkingForm) {
400     // First try the current form
401     if (formIdent != null) {
402       try {
403         return driver.findElement(By.xpath("//form[" + formIdent + "]" + xpathAfterForm));
404       } catch (NoSuchElementException ex) {
405         if (searchOnlyInCurrentForm) {
406           return null;
407         }
408       }
409     }
410     // not in the current form: try other forms
411     List<WebElement> forms = driver.findElements(By.tagName("form"));
412     int index = 0;
413     for (WebElement f : forms) {
414       try {
415         WebElement e = driver.findElement(By.xpath("//form[position()=" + (index + 1) + "]" + xpathAfterForm));
416         if (overrideWorkingForm) {
417           setWorkingForm(index);
418         }
419         return e;
420       } catch (NoSuchElementException ex) {
421         index++;
422       }
423     }
424     // now look everywhere (maybe outside of form)
425     try {
426       return driver.findElement(By.xpath("//body" + xpathAfterForm));
427     } catch (NoSuchElementException ex) {
428       return null;
429     }
430   }
431 
432   private List<WebElement> getWebElementsByXPath(String xpathAfterForm) {
433     try {
434       return driver.findElements(By.xpath(formSelector() + xpathAfterForm));
435     } catch (NoSuchElementException e) {
436       return null;
437     }
438   }
439 
440   public String getTextFieldValue(String paramName) {
441     WebElement e = getTextField(paramName);
442     return e.getAttribute("value");
443   }
444 
445   public String getHiddenFieldValue(String paramName) {
446     WebElement e = getWebElementByXPath("//input[@type='hidden' and @name=" + escapeQuotes(paramName) + "]", false, true);
447     return e.getAttribute("value");
448   }
449 
450   public void setTextField(String inputName, String text) {
451     WebElement e = getTextField(inputName);
452     e.clear();
453     e.sendKeys(text);
454   }
455 
456   /**
457    * Look for any text field (input text, input password, textarea, file input).
458    */
459   private WebElement getTextField(String paramName) {
460     WebElement e = getWebElementByXPath("//input[@type='text' and @name=" + escapeQuotes(paramName) + "]", false, true);
461     if (e == null) {
462       e = getWebElementByXPath("//textarea[@name=" + escapeQuotes(paramName) + "]", false, true);
463     }
464     if (e == null) {
465       e = getWebElementByXPath("//input[@type='file' and @name=" + escapeQuotes(paramName) + "]", false, true);
466     }
467     if (e == null) {
468       e = getWebElementByXPath("//input[@type='password' and @name=" + escapeQuotes(paramName) + "]", false, true);
469     }
470     return e;
471   }
472 
473   public void setHiddenField(String inputName, String text) {
474     WebElement e = getWebElementByXPath("//input[@type='hidden' and @name=" + escapeQuotes(inputName) + "]", false, true);
475     ((JavascriptExecutor) driver).executeScript("arguments[0].value=" + escapeQuotes(text), e);
476   }
477 
478   public String[] getSelectOptionValues(String selectName) {
479     return getSelectOptionValues(selectName, 0);
480   }
481 
482   public String[] getSelectOptionValues(String selectName, int index) {
483     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
484     ArrayList<String> result = new ArrayList<String>();
485     for (WebElement opt : select.getOptions()) {
486       result.add(opt.getAttribute("value"));
487     }
488     return result.toArray(new String[result.size()]);
489   }
490 
491   public String[] getSelectedOptions(String selectName) {
492     return getSelectedOptions(selectName, 0);
493   }
494 
495   private String[] getSelectedOptions(Select select) {
496     // FIXME http://code.google.com/p/selenium/issues/detail?id=2295
497     String[] result = new String[select.getAllSelectedOptions().size()];
498     int i = 0;
499     for (WebElement opt : select.getAllSelectedOptions()) {
500       result[i++] = opt.getAttribute("value");
501     }
502     return result;
503   }
504 
505   public String[] getSelectedOptions(String selectName, int index) {
506     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
507     return getSelectedOptions(select);
508   }
509 
510   private String getSelectOptionValueForLabel(Select select, String label) {
511     for (WebElement opt : select.getOptions()) {
512       if (opt.getText().equals(label)) {
513         return opt.getAttribute("value");
514       }
515     }
516     throw new RuntimeException("Unable to find option " + label);
517   }
518 
519   private String getSelectOptionLabelForValue(Select select, String value) {
520     for (WebElement opt : select.getOptions()) {
521       if (opt.getAttribute("value").equals(value)) {
522         return opt.getText();
523       }
524     }
525     throw new RuntimeException("Unable to find option " + value);
526   }
527 
528   public String getSelectOptionLabelForValue(String selectName, String optionValue) {
529     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "]", true, true));
530     return getSelectOptionLabelForValue(select, optionValue);
531   }
532 
533   public String getSelectOptionLabelForValue(String selectName, int index, String optionValue) {
534     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
535     return getSelectOptionLabelForValue(select, optionValue);
536   }
537 
538   public String getSelectOptionValueForLabel(String selectName, String optionLabel) {
539     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "]", true, true));
540     return getSelectOptionValueForLabel(select, optionLabel);
541   }
542 
543   public String getSelectOptionValueForLabel(String selectName, int index, String optionLabel) {
544     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
545     return getSelectOptionValueForLabel(select, optionLabel);
546   }
547 
548   public void selectOptions(String selectName, String[] optionValues) {
549     selectOptions(selectName, 0, optionValues);
550   }
551 
552   public void selectOptions(String selectName, int index, String[] optionValues) {
553     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
554     if (!select.isMultiple() && optionValues.length > 1)
555       throw new RuntimeException("Multiselect not enabled");
556     for (String option : optionValues) {
557       boolean found = false;
558       for (WebElement opt : select.getOptions()) {
559         if (opt.getAttribute("value").equals(option)) {
560           select.selectByValue(option);
561           found = true;
562           break;
563         }
564       }
565       if (!found) {
566         throw new RuntimeException("Option " + option
567           + " not found");
568       }
569     }
570   }
571 
572   public void unselectOptions(String selectName, String[] optionValues) {
573     unselectOptions(selectName, 0, optionValues);
574   }
575 
576   public void unselectOptions(String selectName, int index, String[] optionValues) {
577     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
578     if (!select.isMultiple() && optionValues.length > 1)
579       throw new RuntimeException("Multiselect not enabled");
580     for (String option : optionValues) {
581       boolean found = false;
582       for (WebElement opt : select.getOptions()) {
583         if (opt.getAttribute("value").equals(option)) {
584           select.deselectByValue(option);
585           found = true;
586           break;
587         }
588       }
589       if (!found) {
590         throw new RuntimeException("Option " + option
591           + " not found");
592       }
593     }
594   }
595 
596   public boolean hasSelectOption(String selectName, String optionLabel) {
597     return hasSelectOption(selectName, 0, optionLabel);
598   }
599 
600   public boolean hasSelectOptionValue(String selectName, String optionValue) {
601     return hasSelectOptionValue(selectName, 0, optionValue);
602   }
603 
604   public boolean hasSelectOption(String selectName, int index, String optionLabel) {
605     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
606     for (WebElement opt : select.getOptions()) {
607       if (opt.getText().equals(optionLabel)) {
608         return true;
609       }
610     }
611     return false;
612   }
613 
614   public boolean hasSelectOptionValue(String selectName, int index, String optionValue) {
615     Select select = new Select(getWebElementByXPath("//select[@name=" + escapeQuotes(selectName) + "][" + (index + 1) + "]", true, true));
616     for (WebElement opt : select.getOptions()) {
617       if (opt.getAttribute("value").equals(optionValue)) {
618         return true;
619       }
620     }
621     return false;
622   }
623 
624   public boolean isCheckboxSelected(String checkBoxName) {
625     WebElement e = getWebElementByXPath("//input[@type='checkbox' and @name=" + escapeQuotes(checkBoxName) + "]", true, true);
626     return e.isSelected();
627   }
628 
629   public boolean isCheckboxSelected(String checkBoxName, String checkBoxValue) {
630     WebElement e = getWebElementByXPath("//input[@type='checkbox' and @name=" + escapeQuotes(checkBoxName) + " and @value=" + escapeQuotes(checkBoxValue) + "]", true, true);
631     return e.isSelected();
632   }
633 
634   public void checkCheckbox(String checkBoxName) {
635     WebElement e = getWebElementByXPath("//input[@type='checkbox' and @name=" + escapeQuotes(checkBoxName) + "]", true, true);
636     if (!e.isSelected()) {
637       e.click();
638     }
639   }
640 
641   public void checkCheckbox(String checkBoxName, String checkBoxValue) {
642     WebElement e = getWebElementByXPath("//input[@type='checkbox' and @name=" + escapeQuotes(checkBoxName) + " and @value=" + escapeQuotes(checkBoxValue) + "]", true, true);
643     if (!e.isSelected()) {
644       e.click();
645     }
646   }
647 
648   public void uncheckCheckbox(String checkBoxName) {
649     WebElement e = getWebElementByXPath("//input[@type='checkbox' and @name=" + escapeQuotes(checkBoxName) + "]", true, true);
650     if (e.isSelected()) {
651       e.click();
652     }
653   }
654 
655   public void uncheckCheckbox(String checkBoxName, String value) {
656     WebElement e = getWebElementByXPath("//input[@type='checkbox' and @name=" + escapeQuotes(checkBoxName) + " and @value=" + escapeQuotes(value) + "]", true, true);
657     if (e.isSelected()) {
658       e.click();
659     }
660   }
661 
662   public void clickRadioOption(String radioGroup, String radioOptionValue) {
663     WebElement e = getWebElementByXPath("//input[@type='radio' and @name=" + escapeQuotes(radioGroup) + " and @value=" + escapeQuotes(radioOptionValue) + "]", false, true);
664     e.click();
665   }
666 
667   public boolean hasRadioOption(String radioGroup, String radioOptionValue) {
668     WebElement e = getWebElementByXPath("//input[@type='radio' and @name=" + escapeQuotes(radioGroup) + " and @value=" + escapeQuotes(radioOptionValue) + "]", false, true);
669     return e != null;
670   }
671 
672   public String getSelectedRadio(String radioGroup) {
673     List<WebElement> radios = getWebElementsByXPath("/input[@type='radio' and @name=" + escapeQuotes(radioGroup) + "]");
674     for (WebElement r : radios) {
675       if (r.isSelected()) {
676         return r.getAttribute("value");
677       }
678     }
679     return null;
680   }
681 
682   public boolean hasSubmitButton() {
683     return (getWebElementByXPath("//input[@type='submit' or @type='image']", true, true) != null) || (getWebElementByXPath("//button[@type='submit']", false, true) != null);
684   }
685 
686   public boolean hasSubmitButton(String nameOrID) {
687     return (getWebElementByXPath("//input[(@type='submit' or @type='image') and (@name=" + escapeQuotes(nameOrID) + " or @id=" + nameOrID + ")]", true, true) != null)
688       || (getWebElementByXPath("//button[@type='submit' and (@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + ")]", true, true) != null);
689   }
690 
691   public boolean hasSubmitButton(String nameOrID, String value) {
692     return (getWebElementByXPath("//input[(@type='submit' or @type='image') and (@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + ") and @value="
693       + escapeQuotes(value) + "]", true, true) != null)
694       || (getWebElementByXPath("//button[@type='submit' and (@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + ") and @value=" + escapeQuotes(value) + "]",
695         true, true) != null);
696   }
697 
698   public void submit() {
699     WebElement e = getWebElementByXPath("//input[@type='submit' or @type='image']", true, true);
700     if (e == null) {
701       e = getWebElementByXPath("//button[@type='submit']", true, true);
702     }
703     e.submit();
704     throwFailingHttpStatusCodeExceptionIfNecessary(
705       getServerResponseCode(), driver.getCurrentUrl());
706   }
707 
708   public void submit(String nameOrID) {
709     WebElement e = getWebElementByXPath("//input[(@type='submit' or @type='image') and (@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + ")]", true, true);
710     if (e == null) {
711       e = getWebElementByXPath("//button[@type='submit' and (@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + ")]", true, true);
712     }
713     e.submit();
714     throwFailingHttpStatusCodeExceptionIfNecessary(
715       getServerResponseCode(), driver.getCurrentUrl());
716   }
717 
718   public void submit(String buttonName, String buttonValue) {
719     WebElement e = getWebElementByXPath("//input[(@type='submit' or @type='image') and (@name=" + escapeQuotes(buttonName) + " or @id=" + escapeQuotes(buttonName)
720       + ") and @value=" + escapeQuotes(buttonValue) + "]", true, true);
721     if (e == null) {
722       e = getWebElementByXPath("//button[@type='submit' and (@name=" + escapeQuotes(buttonName) + " or @id=" + escapeQuotes(buttonName) + ") and @value="
723         + escapeQuotes(buttonValue) + "]", true, true);
724     }
725     e.submit();
726     throwFailingHttpStatusCodeExceptionIfNecessary(
727       getServerResponseCode(), driver.getCurrentUrl());
728   }
729 
730   public boolean hasResetButton() {
731     return getWebElementByXPath("//input[@type='reset']", false, true) != null;
732   }
733 
734   public boolean hasResetButton(String nameOrID) {
735     return getWebElementByXPath("//input[@type='reset' and (@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + ")]", true, true) != null;
736   }
737 
738   public void reset() {
739     getWebElementByXPath("//input[@type='reset']", true, true).click();
740     ;
741   }
742 
743   private WebElement getButton(String nameOrID) {
744     WebElement e = getWebElementByXPath("//input[(@type='submit' or @type='image' or @type='reset' or @type='button') and (@name=" + escapeQuotes(nameOrID) + " or @id="
745       + escapeQuotes(nameOrID) + ")]", false, true);
746     if (e == null) {
747       e = getWebElementByXPath("//button[@name=" + escapeQuotes(nameOrID) + " or @id=" + escapeQuotes(nameOrID) + "]", false, true);
748     }
749     return e;
750   }
751 
752   private WebElement getButtonWithText(String text) {
753     WebElement e = getWebElementByXPath("//input[(@type='submit' or @type='reset' or @type='button') and contains(@value," + escapeQuotes(text) + ")]", false, true);
754     if (e == null) {
755       e = getWebElementByXPath("//button[contains(.," + escapeQuotes(text) + ")]", false, true);
756     }
757     return e;
758   }
759 
760   public boolean hasButtonWithText(String text) {
761     return getButtonWithText(text) != null;
762   }
763 
764   public boolean hasButton(String buttonId) {
765     return getButton(buttonId) != null;
766   }
767 
768   public void clickButton(String buttonId) {
769     getButton(buttonId).click();
770   }
771 
772   public void clickButtonWithText(String buttonValueText) {
773     getButtonWithText(buttonValueText).click();
774   }
775 
776   public URL getPageURL() {
777     try {
778       return new URL(driver.getCurrentUrl());
779     } catch (MalformedURLException ex) {
780       throw new RuntimeException(ex);
781     }
782   }
783 
784   public String getPageText() {
785     try {
786       return driver.findElement(By.tagName("body")).getText();
787     } catch (Exception e) {
788       // Maybe this is not HTML
789       return driver.getPageSource();
790     }
791   }
792 
793   public String getPageSource() {
794     return driver.getPageSource();
795     // Do not work when there are requests following loading of main page (like frames)
796     // String encoding = "ISO-8859-1";
797     // if (response.getEntity().getContentEncoding() != null) {
798     // encoding = response.getEntity().getContentEncoding().getValue();
799     // }
800     //
801     // try {
802     // return IOUtils.toString(response.getEntity().getContent(), encoding);
803     // }
804     // catch (IOException e) {
805     // throw new RuntimeException(e);
806     // }
807   }
808 
809   public String getPageTitle() {
810     return driver.getTitle();
811   }
812 
813   public String getServerResponse() {
814     throw new UnsupportedOperationException("Not supported yet.");
815   }
816 
817   public InputStream getInputStream() {
818     try {
819       return response.getRawResponse().getEntity().getContent();
820     } catch (Exception e) {
821       throw new TestingEngineResponseException(e);
822     }
823   }
824 
825   public InputStream getInputStream(URL url) throws TestingEngineResponseException {
826     try {
827       return url.openStream();// TODO support proxy
828     } catch (IOException e) {
829       throw new TestingEngineResponseException(e);
830     }
831   }
832 
833   public boolean hasTable(String tableSummaryNameOrId) {
834     return getHtmlTable(tableSummaryNameOrId) != null;
835   }
836 
837   public Table getTable(String tableSummaryNameOrId) {
838     WebElement table = getHtmlTable(tableSummaryNameOrId);
839     Table result = new Table();
840     List<WebElement> trs = table.findElements(By.xpath("tr | tbody/tr"));
841     for (int i = 0; i < trs.size(); i++) {
842       Row newRow = new Row();
843       WebElement tr = trs.get(i);
844       List<WebElement> tds = tr.findElements(By.xpath("td | th"));
845       for (WebElement td : tds) {
846         int colspan;
847         try {
848           colspan = Integer.valueOf(td.getAttribute("colspan"));
849         } catch (NumberFormatException e) {
850           colspan = 1;
851         }
852         int rowspan;
853         try {
854           rowspan = Integer.valueOf(td.getAttribute("rowspan"));
855         } catch (NumberFormatException e) {
856           rowspan = 1;
857         }
858         newRow.appendCell(new Cell(td.getText(),
859           colspan,
860           rowspan));
861       }
862       result.appendRow(newRow);
863     }
864     return result;
865   }
866 
867   /**
868    * Return the Webdriver WebElement object representing a specified table in the current response. Null is returned if a
869    * parsing exception occurs looking for the table or no table with the id or summary could be found.
870    *
871    * @param tableSummaryOrId summary or id of the table to return.
872    */
873   private WebElement getHtmlTable(String tableSummaryOrId) {
874     try {
875       return driver.findElement(By.xpath("(//table[@id="
876         + escapeQuotes(tableSummaryOrId) + " or @summary=" + escapeQuotes(tableSummaryOrId) + "])"));
877     } catch (NoSuchElementException e) {
878       return null;
879     }
880   }
881 
882   private WebElement getLinkWithImage(String filename, int index) {
883     try {
884       return driver.findElement(By.xpath("(//a[img[contains(@src,"
885         + escapeQuotes(filename) + ")]])[" + (index + 1) + "]"));
886     } catch (NoSuchElementException e) {
887       return null;
888     }
889   }
890 
891   private WebElement getLinkWithText(String linkText, int index) {
892     List<WebElement> lnks = driver.findElements(By.xpath("//a"));
893     int count = 0;
894     for (WebElement lnk : lnks) {
895       if ((lnk.getText().indexOf(linkText) >= 0) && (count++ == index)) {
896         return lnk;
897       }
898     }
899     return null;
900   }
901 
902   private WebElement getLinkWithExactText(String linkText, int index) {
903     List<WebElement> lnks = driver.findElements(By.xpath("//a"));
904     int count = 0;
905     for (WebElement lnk : lnks) {
906       if (lnk.getText().equals(linkText) && (count++ == index)) {
907         return lnk;
908       }
909     }
910     return null;
911   }
912 
913   public boolean hasLinkWithText(String linkText, int index) {
914     return getLinkWithText(linkText, index) != null;
915   }
916 
917   public boolean hasLinkWithExactText(String linkText, int index) {
918     return getLinkWithExactText(linkText, index) != null;
919   }
920 
921   public boolean hasLinkWithImage(String imageFileName, int index) {
922     return getLinkWithImage(imageFileName, index) != null;
923   }
924 
925   public boolean hasLink(String anId) {
926     try {
927       driver.findElement(By.xpath("//a[@id='" + anId + "']"));
928       return true;
929     } catch (NoSuchElementException e) {
930       return false;
931     }
932   }
933 
934   public void clickLinkWithText(String linkText, int index) {
935     WebElement link = getLinkWithText(linkText, index);
936     if (link == null) {
937       throw new RuntimeException("No Link found for \"" + linkText
938         + "\" with index " + index);
939     }
940     link.click();
941   }
942 
943   public void clickLinkWithExactText(String linkText, int index) {
944     WebElement link = getLinkWithExactText(linkText, index);
945     if (link == null) {
946       throw new RuntimeException("No Link found for \"" + linkText
947         + "\" with index " + index);
948     }
949     link.click();
950   }
951 
952   public void clickLink(String anID) {
953     driver.findElement(By.xpath("//a[@id='" + anID + "']")).click();
954   }
955 
956   public void clickLinkWithImage(String imageFileName, int index) {
957     WebElement link = getLinkWithImage(imageFileName, index);
958     if (link == null) {
959       throw new RuntimeException("No Link found with filename \""
960         + imageFileName + "\" and index " + index);
961     }
962     link.click();
963   }
964 
965   public boolean hasElement(String anID) {
966     try {
967       driver.findElement(By.id(anID));
968       return true;
969     } catch (NoSuchElementException e) {
970       return false;
971     }
972   }
973 
974   public boolean hasElementByXPath(String xpath) {
975     try {
976       driver.findElement(By.xpath(xpath));
977       return true;
978     } catch (NoSuchElementException e) {
979       return false;
980     }
981   }
982 
983   public void clickElementByXPath(String xpath) {
984     driver.findElement(By.xpath(xpath)).click();
985   }
986 
987   public String getElementAttributByXPath(String xpath, String attribut) {
988     return driver.findElement(By.xpath(xpath)).getAttribute(attribut);
989   }
990 
991   public String getElementTextByXPath(String xpath) {
992     return driver.findElement(By.xpath(xpath)).getText();
993   }
994 
995   public boolean isTextInElement(String elementID, String text) {
996     return isTextInElement(driver.findElement(By.id(elementID)), text);
997   }
998 
999   /**
1000    * Return true if a given string is contained within the specified element.
1001    *
1002    * @param element element to inspect.
1003    * @param text text to check for.
1004    */
1005   private boolean isTextInElement(WebElement element, String text) {
1006     return element.getText().indexOf(text) >= 0;
1007   }
1008 
1009   public boolean isMatchInElement(String elementID, String regexp) {
1010     return isMatchInElement(driver.findElement(By.id(elementID)), regexp);
1011   }
1012 
1013   /**
1014    * Return true if a given regexp is contained within the specified element.
1015    *
1016    * @param element element to inspect.
1017    * @param regexp regexp to match.
1018    */
1019   private boolean isMatchInElement(WebElement element, String regexp) {
1020     RE re = getRE(regexp);
1021     return re.match(element.getText());
1022   }
1023 
1024   private RE getRE(String regexp) {
1025     try {
1026       return new RE(regexp, RE.MATCH_SINGLELINE);
1027     } catch (RESyntaxException e) {
1028       throw new RuntimeException(e);
1029     }
1030   }
1031 
1032   public void setExpectedJavaScriptAlert(JavascriptAlert[] alerts) throws ExpectedJavascriptAlertException {
1033     throw new UnsupportedOperationException("Not supported yet.");
1034   }
1035 
1036   public void setExpectedJavaScriptConfirm(JavascriptConfirm[] confirms) throws ExpectedJavascriptConfirmException {
1037     throw new UnsupportedOperationException("Not supported yet.");
1038   }
1039 
1040   public void setExpectedJavaScriptPrompt(JavascriptPrompt[] prompts) throws ExpectedJavascriptPromptException {
1041     throw new UnsupportedOperationException("Not supported yet.");
1042   }
1043 
1044   public IElement getElementByXPath(String xpath) {
1045     try {
1046       return new WebDriverElementImpl(driver, driver.findElement(By.xpath(xpath)));
1047     } catch (NoSuchElementException e) {
1048       return null;
1049     }
1050   }
1051 
1052   public IElement getElementByID(String id) {
1053     try {
1054       return new WebDriverElementImpl(driver, driver.findElement(By.id(id)));
1055     } catch (NoSuchElementException e) {
1056       return null;
1057     }
1058   }
1059 
1060   public List<IElement> getElementsByXPath(String xpath) {
1061     List<IElement> result = new ArrayList<IElement>();
1062     List<WebElement> elements = driver.findElements(By.xpath(xpath));
1063     for (WebElement child : elements) {
1064       result.add(new WebDriverElementImpl(driver, child));
1065     }
1066     return result;
1067   }
1068 
1069   public int getServerResponseCode() {
1070     return response.getRawResponse().getStatusLine().getStatusCode();
1071   }
1072 
1073   public String getHeader(String name) {
1074     return response.getHeader(name);
1075   }
1076 
1077   public Map<String, String> getAllHeaders() {
1078     Map<String, String> map = new java.util.HashMap<String, String>();
1079     for (Header header : response.getRawResponse().getAllHeaders()) {
1080       map.put(header.getName(), header.getValue());
1081     }
1082     return map;
1083   }
1084 
1085   public void setIgnoreFailingStatusCodes(boolean ignore) {
1086     this.ignoreFailingStatusCodes = ignore;
1087   }
1088 
1089   public List<String> getComments() {
1090     throw new UnsupportedOperationException("Not supported yet.");
1091   }
1092 
1093   public void setTimeout(int milliseconds) {
1094     throw new UnsupportedOperationException("Not supported yet.");
1095   }
1096 
1097   public List<HttpHeader> getResponseHeaders() {
1098     List<HttpHeader> result = new LinkedList<HttpHeader>();
1099     for (Header header : response.getRawResponse().getAllHeaders()) {
1100       result.add(new HttpHeader(header.getName(), header.getValue()));
1101     }
1102     return result;
1103   }
1104 
1105   /**
1106    * Copied from {@link Select}
1107    * @param toEscape
1108    * @return
1109    */
1110   protected String escapeQuotes(String toEscape) {
1111     // Convert strings with both quotes and ticks into: foo'"bar -> concat("foo'", '"', "bar")
1112     if (toEscape.indexOf("\"") > -1 && toEscape.indexOf("'") > -1) {
1113       boolean quoteIsLast = false;
1114       if (toEscape.indexOf("\"") == toEscape.length() - 1) {
1115         quoteIsLast = true;
1116       }
1117       String[] substrings = toEscape.split("\"");
1118 
1119       StringBuilder quoted = new StringBuilder("concat(");
1120       for (int i = 0; i < substrings.length; i++) {
1121         quoted.append("\"").append(substrings[i]).append("\"");
1122         quoted.append(((i == substrings.length - 1) ? (quoteIsLast ? ", '\"')" : ")") : ", '\"', "));
1123       }
1124       return quoted.toString();
1125     }
1126 
1127     // Escape string with just a quote into being single quoted: f"oo -> 'f"oo'
1128     if (toEscape.indexOf("\"") > -1) {
1129       return String.format("'%s'", toEscape);
1130     }
1131 
1132     // Otherwise return the quoted string
1133     return String.format("\"%s\"", toEscape);
1134   }
1135 
1136 }