View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  import static java.nio.charset.StandardCharsets.UTF_8;
19  import static org.junit.Assert.fail;
20  
21  import java.io.IOException;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import java.nio.charset.StandardCharsets;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Set;
32  
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  
36  import org.apache.commons.io.IOUtils;
37  import org.apache.commons.lang3.SerializationUtils;
38  import org.htmlunit.CollectingAlertHandler;
39  import org.htmlunit.ElementNotFoundException;
40  import org.htmlunit.HttpMethod;
41  import org.htmlunit.ImmediateRefreshHandler;
42  import org.htmlunit.IncorrectnessListener;
43  import org.htmlunit.MockWebConnection;
44  import org.htmlunit.OnbeforeunloadHandler;
45  import org.htmlunit.Page;
46  import org.htmlunit.SimpleWebTestCase;
47  import org.htmlunit.StringWebResponse;
48  import org.htmlunit.WebClient;
49  import org.htmlunit.WebRequest;
50  import org.htmlunit.WebResponse;
51  import org.htmlunit.html.HtmlElementTest.HtmlAttributeChangeListenerTestImpl;
52  import org.htmlunit.junit.BrowserRunner;
53  import org.htmlunit.junit.annotation.Alerts;
54  import org.htmlunit.util.Cookie;
55  import org.htmlunit.util.MimeType;
56  import org.htmlunit.util.NameValuePair;
57  import org.htmlunit.util.StringUtils;
58  import org.junit.Test;
59  import org.junit.runner.RunWith;
60  import org.w3c.dom.NodeList;
61  
62  /**
63   * Tests for {@link HtmlPage}.
64   *
65   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
66   * @author Noboru Sinohara
67   * @author David K. Taylor
68   * @author Andreas Hangler
69   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
70   * @author Marc Guillemot
71   * @author Ahmed Ashour
72   * @author Frank Danek
73   * @author Ronald Brill
74   */
75  @RunWith(BrowserRunner.class)
76  public class HtmlPageTest extends SimpleWebTestCase {
77  
78      /** The doctype prefix for standards mode. */
79      public static final String STANDARDS_MODE_PREFIX_
80          = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
81  
82      /**
83       * @throws Exception if the test fails
84       */
85      @Test
86      public void formSubmit() throws Exception {
87          final String htmlContent = DOCTYPE_HTML
88              + "<html>\n"
89              + "<head><title>foo</title></head>\n"
90              + "<body>\n"
91              + "<p>hello world</p>\n"
92              + "<form id='form1' action='/formSubmit' method='PoSt'>\n"
93              + "<input type='text' NAME='textInput1' value='textInput1'/>\n"
94              + "<input type='text' name='textInput2' value='textInput2'/>\n"
95              + "<input type='hidden' name='hidden1' value='hidden1'/>\n"
96              + "<input type='submit' name='submitInput1' value='push me'/>\n"
97              + "</form>\n"
98              + "</body></html>";
99          final HtmlPage page = loadPage(htmlContent);
100         final MockWebConnection webConnection = getMockConnection(page);
101 
102         final HtmlForm form = page.getHtmlElementById("form1");
103         final HtmlInput textInput = form.getInputByName("textInput1");
104         textInput.setValue("foo");
105 
106         final HtmlSubmitInput button = form.getInputByName("submitInput1");
107         final HtmlPage secondPage = button.click();
108 
109         final List<NameValuePair> expectedParameters = new ArrayList<>();
110         expectedParameters.add(new NameValuePair("textInput1", "foo"));
111         expectedParameters.add(new NameValuePair("textInput2", "textInput2"));
112         expectedParameters.add(new NameValuePair("hidden1", "hidden1"));
113         expectedParameters.add(new NameValuePair("submitInput1", "push me"));
114 
115         final URL expectedUrl = new URL(URL_FIRST, "formSubmit");
116         final URL actualUrl = secondPage.getUrl();
117         assertEquals("url", expectedUrl, actualUrl);
118         assertSame("method", HttpMethod.POST, webConnection.getLastMethod());
119         assertEquals("parameters", expectedParameters, webConnection.getLastParameters());
120         assertNotNull(secondPage);
121     }
122 
123     /**
124      * @throws Exception if the test fails
125      */
126     @Test
127     public void getElementByIdNull() throws Exception {
128         final String htmlContent = DOCTYPE_HTML
129             + "<html>\n"
130             + "<head><title>foo</title></head>\n"
131             + "<body>\n"
132             + "<p>hello world</p>\n"
133             + "</body></html>";
134 
135         final HtmlPage page = loadPage(htmlContent);
136 
137         assertNull(page.getElementById(null));
138     }
139 
140     /**
141      * @throws Exception if the test fails
142      */
143     @Test
144     public void getElementsByIdNull() throws Exception {
145         final String htmlContent = DOCTYPE_HTML
146             + "<html>\n"
147             + "<head><title>foo</title></head>\n"
148             + "<body>\n"
149             + "<p>hello world</p>\n"
150             + "</body></html>";
151 
152         final HtmlPage page = loadPage(htmlContent);
153 
154         final List<DomElement> elements = page.getElementsById(null);
155         assertNotNull(elements);
156         assertEquals(0, elements.size());
157     }
158 
159     /**
160      * @throws Exception if the test fails
161      */
162     @Test
163     public void getElementsByIdOrNameNull() throws Exception {
164         final String htmlContent = DOCTYPE_HTML
165             + "<html>\n"
166             + "<head><title>foo</title></head>\n"
167             + "<body>\n"
168             + "<p>hello world</p>\n"
169             + "</body></html>";
170 
171         final HtmlPage page = loadPage(htmlContent);
172 
173         final List<DomElement> elements = page.getElementsByIdAndOrName(null);
174         assertNotNull(elements);
175         assertEquals(0, elements.size());
176     }
177 
178     /**
179      * @throws Exception if the test fails
180      */
181     @Test(expected = ElementNotFoundException.class)
182     public void getElementByNameNull() throws Exception {
183         final String htmlContent = DOCTYPE_HTML
184             + "<html>\n"
185             + "<head><title>foo</title></head>\n"
186             + "<body>\n"
187             + "<p>hello world</p>\n"
188             + "</body></html>";
189 
190         final HtmlPage page = loadPage(htmlContent);
191 
192         assertNull(page.getElementByName(null));
193     }
194 
195     /**
196      * @throws Exception if the test fails
197      */
198     @Test
199     public void getElementsByNameNull() throws Exception {
200         final String htmlContent = DOCTYPE_HTML
201             + "<html>\n"
202             + "<head><title>foo</title></head>\n"
203             + "<body>\n"
204             + "<p>hello world</p>\n"
205             + "</body></html>";
206 
207         final HtmlPage page = loadPage(htmlContent);
208 
209         final List<DomElement> elements = page.getElementsByName(null);
210         assertNotNull(elements);
211         assertEquals(0, elements.size());
212     }
213 
214     /**
215      * Tests getHtmlElement() for all elements that can be loaded.
216      * @throws Exception if the test fails
217      */
218     @Test
219     public void getHtmlElement() throws Exception {
220         final String htmlContent = DOCTYPE_HTML
221             + "<html>\n"
222             + "<head><title>foo</title></head>\n"
223             + "<body>\n"
224             + "  <p>hello world</p>\n"
225             + "  <form id='form1' id='form1' action='/formSubmit' method='post'>\n"
226             + "  <input type='text' NAME='textInput1' value='textInput1'/>\n"
227             + "  <button type='submit' name='button1'>foobar</button>\n"
228             + "  <select name='select1'>\n"
229             + "    <option value='option1'>Option1</option>\n"
230             + "  </select>\n"
231             + "  <textarea name='textArea1'>foobar</textarea>\n"
232             + "  </form>\n"
233             + "  <a href='http://www.foo.com' name='anchor1'>foo.com</a>\n"
234             + "  <table id='table1'>\n"
235             + "    <tr>\n"
236             + "      <th id='header1'>Header</th>\n"
237             + "      <td id='data1'>Data</td>\n"
238             + "    </tr>\n"
239             + "  </table>\n"
240             + "</body></html>";
241         final HtmlPage page = loadPage(htmlContent);
242 
243         final HtmlForm form = page.getHtmlElementById("form1");
244         assertSame("form1", form, page.getHtmlElementById("form1")); //huh??
245 
246         final HtmlInput input = form.getInputByName("textInput1");
247         assertSame("input1", input, form.getInputByName("textInput1")); //??
248 
249         final HtmlButton button = form.getButtonByName("button1");
250         assertSame("button1", button, form.getButtonByName("button1"));
251 
252         final HtmlSelect select = form.getSelectsByName("select1").get(0);
253         assertSame("select1", select, form.getSelectsByName("select1").get(0));
254 
255         final HtmlOption option = select.getOptionByValue("option1");
256         assertSame("option1", option, select.getOptionByValue("option1"));
257 
258         final HtmlTable table = page.getHtmlElementById("table1");
259         assertSame("table1", table, page.getHtmlElementById("table1"));
260 
261         final HtmlAnchor anchor = page.getAnchorByName("anchor1");
262         assertSame("anchor1", anchor, page.getAnchorByName("anchor1"));
263         assertSame("anchor3", anchor, page.getAnchorByHref("http://www.foo.com"));
264         assertSame("anchor4", anchor, page.getAnchorByText("foo.com"));
265 
266         final HtmlTableRow tableRow = table.getRow(0);
267         assertSame("tableRow1", tableRow, table.getRow(0));
268 
269         final HtmlTableHeaderCell tableHeaderCell = (HtmlTableHeaderCell) tableRow.getCell(0);
270         assertSame("tableHeaderCell1", tableHeaderCell, tableRow.getCell(0));
271         assertSame("tableHeaderCell2", tableHeaderCell, page.getHtmlElementById("header1"));
272 
273         final HtmlTableDataCell tableDataCell = (HtmlTableDataCell) tableRow.getCell(1);
274         assertSame("tableDataCell1", tableDataCell, tableRow.getCell(1));
275         assertSame("tableDataCell2", tableDataCell, page.getHtmlElementById("data1"));
276 
277         final HtmlTextArea textArea = form.getTextAreaByName("textArea1");
278         assertSame("textArea1", textArea, form.getTextAreaByName("textArea1"));
279     }
280 
281     /**
282      * Tests getHtmlElement() for all elements that can be loaded.
283      * @throws Exception if the test fails
284      */
285     @Test
286     public void getAnchorByText() throws Exception {
287         final String htmlContent = DOCTYPE_HTML
288             + "<html>\n"
289             + "<head></head>\n"
290             + "<body>\n"
291             + "  <a href='http://www.foo.com' id='anchor1'>anchor text</a>\n"
292             + "  <a href='http://www.foo.com' id='anchor2'><span>anchor text inside span</span></a>\n"
293             + "  <a href='http://www.foo.com' id='anchor3'>"
294                 + "<svg><rect x='1' y='11' width='8' height='8'/></svg>"
295                 + "<span>complex</span>"
296                 + "</a>\n"
297             + "</body></html>";
298         final HtmlPage page = loadPage(htmlContent);
299 
300         assertSame("anchor1", page.getElementById("anchor1"), page.getAnchorByText("anchor text"));
301         assertSame("anchor2", page.getElementById("anchor2"), page.getAnchorByText("anchor text inside span"));
302         assertSame("anchor3", page.getElementById("anchor3"), page.getAnchorByText("complex"));
303     }
304 
305     /**
306      * @throws Exception if the test fails
307      */
308     @Test
309     public void getTabbableElements_None() throws Exception {
310         final String htmlContent = DOCTYPE_HTML
311             + "<html>\n"
312             + "<head><title>foo</title></head>\n"
313             + "<body>\n"
314             + "<p>hello world</p>\n"
315             + "<table><tr><td>foo</td></tr></table>\n"
316             + "</body></html>";
317 
318         final HtmlPage page = loadPage(htmlContent);
319 
320         assertEquals(Collections.EMPTY_LIST, page.getTabbableElements());
321     }
322 
323     /**
324      * @throws Exception if the test fails
325      */
326     @Test
327     public void getTabbableElements_OneEnabled_OneDisabled() throws Exception {
328         final String htmlContent = DOCTYPE_HTML
329             + "<html>\n"
330             + "<head><title>foo</title></head>\n"
331             + "<body>\n"
332             + "<form><p>hello world</p>\n"
333             + "<input name='foo' type='submit' disabled='disabled' id='foo'/>\n"
334             + "<input name='bar' type='submit' id='bar'/>\n"
335             + "</form></body></html>";
336         final HtmlPage page = loadPage(htmlContent);
337 
338         final List<HtmlElement> expectedElements = new ArrayList<>();
339         expectedElements.add(page.getHtmlElementById("bar"));
340 
341         assertEquals(expectedElements, page.getTabbableElements());
342     }
343 
344     /**
345      * @throws Exception if the test fails
346      */
347     @Test
348     public void getTabbableElements() throws Exception {
349         final String htmlContent = DOCTYPE_HTML
350             + "<html>\n"
351             + "<head><title>foo</title></head>\n"
352             + "<body>\n"
353             + "<a id='a' tabindex='1'>foo</a>\n"
354             + "<a id='b'>foo</a>\n"
355             + "<form>\n"
356             + "<a id='c' tabindex='3'>foo</a>\n"
357             + "<a id='d' tabindex='2'>foo</a>\n"
358             + "<a id='e' tabindex='0'>foo</a>\n"
359             + "</form>\n"
360             + "<a id='f' tabindex='3'>foo</a>\n"
361             + "<a id='g' tabindex='1'>foo</a>\n"
362             + "<a id='q' tabindex='-1'>foo</a>\n"
363             + "<form><p>hello world</p>\n"
364             + "<input name='foo' type='submit' disabled='disabled' id='foo'/>\n"
365             + "<input name='bar' type='submit' id='bar'/>\n"
366             + "</form></body></html>";
367         final HtmlPage page = loadPage(htmlContent);
368 
369         final List<HtmlElement> expectedElements = Arrays.asList(new HtmlElement[] {page.getHtmlElementById("a"),
370                 page.getHtmlElementById("g"), page.getHtmlElementById("d"),
371                 page.getHtmlElementById("c"), page.getHtmlElementById("f"),
372                 page.getHtmlElementById("e"), page.getHtmlElementById("b"),
373                 page.getHtmlElementById("bar")});
374 
375         assertEquals(expectedElements, page.getTabbableElements());
376 
377         final String[] expectedIds = {"a", "g", "d", "c", "f", "e", "b", "bar"};
378         assertEquals(expectedIds, page.getTabbableElementIds());
379     }
380 
381     /**
382      * @throws Exception if the test fails
383      */
384     @Test
385     public void getHtmlElementByAccessKey() throws Exception {
386         final String htmlContent = DOCTYPE_HTML
387             + "<html>\n"
388             + "<head><title>foo</title></head>\n"
389             + "<body>\n"
390             + "<a id='a' accesskey='a'>foo</a>\n"
391             + "<a id='b'>foo</a>\n"
392             + "<form>\n"
393             + "<a id='c' accesskey='c'>foo</a>\n"
394             + "</form>\n"
395             + "<form><p>hello world</p>\n"
396             + "<input name='foo' type='submit' disabled='disabled' id='foo' accesskey='f'/>\n"
397             + "<input name='bar' type='submit' id='bar'/>\n"
398             + "</form></body></html>";
399         final HtmlPage page = loadPage(htmlContent);
400 
401         assertEquals(page.getHtmlElementById("a"), page.getHtmlElementByAccessKey('A'));
402         assertEquals(page.getHtmlElementById("c"), page.getHtmlElementByAccessKey('c'));
403         assertNull(page.getHtmlElementByAccessKey('z'));
404     }
405 
406     /**
407      * @throws Exception if the test fails
408      */
409     @Test
410     public void getHtmlElementsByAccessKey() throws Exception {
411         final String htmlContent = DOCTYPE_HTML
412             + "<html>\n"
413             + "<head><title>foo</title></head><body>\n"
414             + "<a id='a' accesskey='a'>foo</a>\n"
415             + "<a id='b' accesskey='a'>foo</a>\n"
416             + "<form>\n"
417             + "<a id='c' accesskey='c'>foo</a>\n"
418             + "</form></body></html>";
419         final HtmlPage page = loadPage(htmlContent);
420 
421         final List<HtmlElement> expectedElements = Arrays.asList(new HtmlElement[] {page.getHtmlElementById("a"),
422                 page.getHtmlElementById("b")});
423         final List<HtmlElement> collectedElements = page.getHtmlElementsByAccessKey('a');
424         assertEquals(expectedElements, collectedElements);
425     }
426 
427     /**
428      * @throws Exception if the test fails
429      */
430     @Test
431     public void getFullQualifiedUrl_NoBaseSpecified() throws Exception {
432         final String htmlContent = DOCTYPE_HTML
433             + "<html><head><title>foo</title></head><body>\n"
434             + "<form id='form1'>\n"
435             + "<table><tr><td><input type='text' id='foo'/></td></tr></table>\n"
436             + "</form></body></html>";
437         final WebClient client = getWebClient();
438 
439         final MockWebConnection webConnection = new MockWebConnection();
440         webConnection.setDefaultResponse(htmlContent);
441         client.setWebConnection(webConnection);
442 
443         final String urlString = URL_FIRST.toExternalForm();
444         final HtmlPage page = client.getPage(URL_FIRST);
445 
446         assertEquals(urlString, page.getFullyQualifiedUrl(""));
447         assertEquals(urlString + "foo", page.getFullyQualifiedUrl("foo"));
448         assertEquals("http://foo.com/bar", page.getFullyQualifiedUrl("http://foo.com/bar"));
449         assertEquals("mailto:me@foo.com", page.getFullyQualifiedUrl("mailto:me@foo.com"));
450 
451         assertEquals(urlString + "foo", page.getFullyQualifiedUrl("foo"));
452         assertEquals(urlString + "bbb", page.getFullyQualifiedUrl("aaa/../bbb"));
453         assertEquals(urlString + "c/d", page.getFullyQualifiedUrl("c/./d"));
454 
455         final HtmlPage secondPage = client.getPage(urlString + "foo/bar?a=b&c=d");
456         assertEquals(urlString + "foo/bar?a=b&c=d", secondPage.getFullyQualifiedUrl(""));
457         assertEquals(urlString + "foo/one", secondPage.getFullyQualifiedUrl("one"));
458         assertEquals(urlString + "two", secondPage.getFullyQualifiedUrl("/two"));
459         assertEquals(urlString + "foo/two?a=b", secondPage.getFullyQualifiedUrl("two?a=b"));
460 
461         final HtmlPage thirdPage = client.getPage("http://foo.com/dog/cat/one.html");
462         assertEquals("http://foo.com/dog/cat/one.html", thirdPage.getFullyQualifiedUrl(""));
463         assertEquals("http://foo.com/dog/cat/two.html", thirdPage.getFullyQualifiedUrl("two.html"));
464     }
465 
466     /**
467      * @throws Exception if the test fails
468      */
469     @Test
470     public void getFullQualifiedUrl_WithBase() throws Exception {
471         testGetFullQualifiedUrl_WithBase("http", "");
472         testGetFullQualifiedUrl_WithBase("http", ":8080");
473         testGetFullQualifiedUrl_WithBase("https", "");
474         testGetFullQualifiedUrl_WithBase("https", ":2005");
475     }
476 
477     /**
478      * @throws Exception if the test fails
479      */
480     private void testGetFullQualifiedUrl_WithBase(final String baseProtocol, final String basePortPart)
481         throws Exception {
482 
483         final String baseUrl = baseProtocol + "://second" + basePortPart;
484         final String htmlContent = DOCTYPE_HTML
485             + "<html><head><title>foo</title>\n"
486             + "<base href='" + baseUrl + "'>\n"
487             + "</head><body>\n"
488             + "<form id='form1'>\n"
489             + "<table><tr><td><input type='text' id='foo'/></td></tr></table>\n"
490             + "</form></body></html>";
491         final HtmlPage page = loadPage(htmlContent);
492 
493         assertEquals(baseUrl, page.getFullyQualifiedUrl(""));
494         assertEquals(baseUrl + "/foo", page.getFullyQualifiedUrl("foo"));
495         assertEquals(baseUrl + "/foo.js", page.getFullyQualifiedUrl("/foo.js"));
496         assertEquals("http://foo.com/bar", page.getFullyQualifiedUrl("http://foo.com/bar"));
497         assertEquals("mailto:me@foo.com", page.getFullyQualifiedUrl("mailto:me@foo.com"));
498 
499         assertEquals(baseUrl + "/bbb", page.getFullyQualifiedUrl("aaa/../bbb"));
500         assertEquals(baseUrl + "/c/d", page.getFullyQualifiedUrl("c/./d"));
501     }
502 
503     /**
504      * @throws Exception if the test fails
505      */
506     @Test(expected = MalformedURLException.class)
507     public void getFullQualifiedUrl_invalid() throws Exception {
508         final String htmlContent = DOCTYPE_HTML + "<html><body></body></html>";
509         final HtmlPage page = loadPage(htmlContent);
510 
511         page.getFullyQualifiedUrl("http://");
512     }
513 
514     /**
515      * @throws Exception if an error occurs
516      */
517     @Test
518     public void testBase_Multiple() throws Exception {
519         final String html = DOCTYPE_HTML
520             + "<html>\n"
521             + "<head>\n"
522             + "<base href='" + URL_SECOND + "'>\n"
523             + "<base href='" + URL_THIRD + "'>\n"
524             + "</head>\n"
525             + "<body>\n"
526             + "  <a href='somepage.html'>\n"
527             + "</body></html>";
528 
529         final WebClient webClient = getWebClient();
530         final List<String> collectedIncorrectness = new ArrayList<>();
531         final IncorrectnessListener listener = new IncorrectnessListener() {
532             @Override
533             public void notify(final String message, final Object origin) {
534                 collectedIncorrectness.add(message);
535             }
536         };
537         webClient.setIncorrectnessListener(listener);
538 
539         final MockWebConnection webConnection = new MockWebConnection();
540         webClient.setWebConnection(webConnection);
541         webConnection.setDefaultResponse(html);
542         final HtmlPage page = webClient.getPage(URL_FIRST);
543         page.getAnchors().get(0).click();
544 
545         final String[] expectedIncorrectness = {"Multiple 'base' detected, only the first is used.",
546             "Multiple 'base' detected, only the first is used."};
547         assertEquals(expectedIncorrectness, collectedIncorrectness);
548     }
549 
550     /**
551      * @throws Exception if an error occurs
552      */
553     @Test
554     public void testBase_InsideBody() throws Exception {
555         final String html = DOCTYPE_HTML
556             + "<html><body>\n"
557             + "  <base href='" + URL_SECOND + "'>\n"
558             + "  <a href='somepage.html'>link</a>\n"
559             + "</body></html>";
560 
561         final WebClient webClient = getWebClient();
562 
563         final MockWebConnection webConnection = new MockWebConnection();
564         webClient.setWebConnection(webConnection);
565         webConnection.setDefaultResponse(html);
566         final HtmlPage page = webClient.getPage(URL_FIRST);
567         final HtmlAnchor anchor = page.getAnchors().get(0);
568         final HtmlPage secondPage = anchor.click();
569 
570         assertEquals(URL_SECOND + "somepage.html", secondPage.getUrl());
571     }
572 
573     /**
574      * @throws Exception if the test fails
575      */
576     @Test
577     public void onLoadHandler_BodyStatement() throws Exception {
578         final String htmlContent = DOCTYPE_HTML
579             + "<html><head><title>foo</title>\n"
580             + "</head><body onLoad='alert(\"foo\")'>\n"
581             + "</body></html>";
582         final List<String> collectedAlerts = new ArrayList<>();
583         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
584         assertEquals("foo", page.getTitleText());
585 
586         final String[] expectedAlerts = {"foo"};
587         assertEquals(expectedAlerts, collectedAlerts);
588     }
589 
590     /**
591      * If the onload handler contains two statements then only the first would execute.
592      * @throws Exception if the test fails
593      */
594     @Test
595     public void onLoadHandler_TwoBodyStatements() throws Exception {
596         final String htmlContent = DOCTYPE_HTML
597             + "<html><head><title>foo</title>\n"
598             + "</head><body onLoad='alert(\"foo\");alert(\"bar\")'>\n"
599             + "</body></html>";
600         final List<String> collectedAlerts = new ArrayList<>();
601         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
602         assertEquals("foo", page.getTitleText());
603 
604         final String[] expectedAlerts = {"foo", "bar"};
605         assertEquals(expectedAlerts, collectedAlerts);
606     }
607 
608     /**
609      * Regression test for bug #69.
610      * @throws Exception if the test fails
611      */
612     @Test
613     public void onLoadHandler_BodyName() throws Exception {
614         final String htmlContent = DOCTYPE_HTML
615             + "<html><head><title>foo</title>\n"
616             + "<script type='text/javascript'>\n"
617             + "  window.onload = function() {alert('foo')}</script>\n"
618             + "</head><body></body></html>";
619         final List<String> collectedAlerts = new ArrayList<>();
620         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
621         assertEquals("foo", page.getTitleText());
622 
623         final String[] expectedAlerts = {"foo"};
624         assertEquals(expectedAlerts, collectedAlerts);
625     }
626 
627     /**
628      * Regression test for bug #69.
629      * @throws Exception if the test fails
630      */
631     @Test
632     public void onLoadHandler_BodyName_NotAFunction() throws Exception {
633         final String htmlContent = DOCTYPE_HTML
634             + "<html><head><title>foo</title></head>\n"
635             + "<body onLoad='foo=4711'>\n"
636             + "<a name='alert' href='javascript:alert(foo)'/>\n"
637             + "</body></html>";
638         final List<String> collectedAlerts = new ArrayList<>();
639         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
640         assertEquals("foo", page.getTitleText());
641 
642         page.getAnchorByName("alert").click();
643 
644         final String[] expectedAlerts = {"4711"};
645         assertEquals(expectedAlerts, collectedAlerts);
646     }
647 
648     /**
649      * Regression test for window.onload property.
650      * @throws Exception if the test fails
651      */
652     @Test
653     public void onLoadHandler_ScriptName() throws Exception {
654         final String htmlContent = DOCTYPE_HTML
655             + "<html><head><title>foo</title>\n"
656             + "<script type='text/javascript'>\n"
657             + "load=function() {alert('foo')};\n"
658             + "onload=load\n"
659             + "</script></head><body></body></html>";
660         final List<String> collectedAlerts = new ArrayList<>();
661         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
662         assertEquals("foo", page.getTitleText());
663 
664         final String[] expectedAlerts = {"foo"};
665         assertEquals(expectedAlerts, collectedAlerts);
666     }
667 
668     /**
669      * @throws Exception if the test fails
670      */
671     @Test
672     public void embeddedMetaTag_Regression() throws Exception {
673         final String htmlContent = DOCTYPE_HTML
674             + "<html><head><title>foo</title>\n"
675             + "</head><body>\n"
676             + "<table><tr><td>\n"
677             + "<meta name=vs_targetSchema content=\"http://schemas.microsoft.com/intellisense/ie5\">\n"
678             + "<form name='form1'>\n"
679             + "  <input type='text' name='textfield1' id='textfield1' value='foo' />\n"
680             + "  <input type='text' name='textfield2' id='textfield2'/>\n"
681             + "</form>\n"
682             + "</td></tr></table>\n"
683             + "</body></html>";
684         final List<String> collectedAlerts = new ArrayList<>();
685 
686         // This used to blow up on page load
687         final HtmlPage page = loadPage(htmlContent, collectedAlerts);
688         assertEquals("foo", page.getTitleText());
689 
690         final List<?> expectedAlerts = Collections.EMPTY_LIST;
691         assertEquals(expectedAlerts, collectedAlerts);
692     }
693 
694     /**
695      * Verifies that an empty charset in a content-type meta tag is ignored. See bug #752.
696      * @throws Exception if an error occurs
697      */
698     @Test
699     public void getPageEncoding_EmptyCharset() throws Exception {
700         final String html = DOCTYPE_HTML
701             + "<html><head>\n"
702             + "<meta http-equiv='Content-Type' content='text/html; charset='>\n"
703             + "</head><body>abc</body></html>";
704         final HtmlPage page = loadPage(html);
705         assertEquals(ISO_8859_1, page.getCharset());
706     }
707 
708     /**
709      * Bug #914.
710      *
711      * @throws Exception if an error occurs
712      */
713     @Test
714     public void getPageEncoding_HeaderHasPrecedenceOverMetaTag() throws Exception {
715         final String html = DOCTYPE_HTML
716             + "<html><head><meta content='text/html; charset=iso-8859-1' http-equiv='Content-Type'/>\n"
717             + "</head><body></body></html>";
718         final MockWebConnection conn = new MockWebConnection();
719         conn.setResponse(URL_FIRST, html, "text/html; charset=UTF-8");
720         final WebClient client = getWebClient();
721         client.setWebConnection(conn);
722         final HtmlPage page = client.getPage(URL_FIRST);
723         assertSame(UTF_8, page.getCharset());
724         assertEquals(page.getWebResponse().getContentCharset(), page.getCharset());
725     }
726 
727     /**
728      * @throws Exception if the test fails
729      */
730     @Test
731     public void getForms() throws Exception {
732         final String htmlContent = DOCTYPE_HTML
733             + "<html>\n"
734             + "<head><title>foo</title></head>\n"
735             + "<body>\n"
736             + "<form name='one'>\n"
737             + "<a id='c' accesskey='c'>foo</a>\n"
738             + "</form>\n"
739             + "<form name='two'>\n"
740             + "<a id='c' accesskey='c'>foo</a>\n"
741             + "</form>\n"
742             + "<input name='foo' type='submit' disabled='disabled' id='foo' accesskey='f'/>\n"
743             + "<input name='bar' type='submit' id='bar'/>\n"
744             + "</form></body></html>";
745 
746         final HtmlPage page = loadPage(htmlContent);
747 
748         final List<HtmlForm> expectedForms = Arrays.asList(new HtmlForm[] {page.getFormByName("one"),
749                 page.getFormByName("two")});
750         assertEquals(expectedForms, page.getForms());
751     }
752 
753     /**
754      * Test auto-refresh from a meta tag.
755      * @throws Exception if the test fails
756      */
757     @Test
758     public void refresh_MetaTag_DefaultRefreshHandler() throws Exception {
759         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2;URL=§§URL§§\">");
760     }
761 
762     /**
763      * @throws Exception if the test fails
764      */
765     @Test
766     public void refresh_MetaTag_caseSensitivity() throws Exception {
767         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2;Url=§§URL§§\">");
768     }
769 
770     /**
771      * Regression test for bug #954.
772      * @throws Exception if the test fails
773      */
774     @Test
775     public void refresh_MetaTag_spaceSeparator() throws Exception {
776         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2 Url=§§URL§§\">");
777         testRefresh_MetaTag("<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2\nUrl=§§URL§§\">");
778     }
779 
780     /**
781      * Test auto-refresh from a meta tag with no URL.
782      * @throws Exception if the test fails
783      */
784     @Test
785     public void refresh_MetaTag_NoUrl() throws Exception {
786         final String firstContent = DOCTYPE_HTML
787             + "<html><head><title>first</title>\n"
788             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"1\">\n"
789             + "</head><body></body></html>";
790 
791         final WebClient client = getWebClient();
792         final List<Object> collectedItems = new ArrayList<>();
793         client.setRefreshHandler(new LoggingRefreshHandler(collectedItems));
794 
795         final MockWebConnection webConnection = new MockWebConnection();
796         webConnection.setResponse(URL_FIRST, firstContent);
797         client.setWebConnection(webConnection);
798 
799         client.getPage(URL_FIRST);
800 
801         // avoid using equals() on URL because it takes to much time (due to ip resolution)
802         assertEquals("first", collectedItems.get(0));
803         assertEquals(URL_FIRST, (URL) collectedItems.get(1));
804         assertEquals(Integer.valueOf(1), collectedItems.get(2));
805     }
806 
807     /**
808      * Ensures that if a page is supposed to refresh itself every certain amount of
809      * time, and the ImmediateRefreshHandler is being used, an OOME is avoided by
810      * not performing the refresh.
811      * @throws Exception if the test fails
812      */
813     @Test
814     public void refresh_ImmediateRefresh_AvoidOOME() throws Exception {
815         final String firstContent = DOCTYPE_HTML
816             + "<html><head><title>first</title>\n"
817             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"1\">\n"
818             + "</head><body></body></html>";
819 
820         final WebClient client = getWebClient();
821         assertTrue(ImmediateRefreshHandler.class.isInstance(client.getRefreshHandler()));
822         try {
823             loadPage(firstContent);
824             fail("should have thrown");
825         }
826         catch (final RuntimeException e) {
827             assertTrue(e.getMessage().indexOf("could have caused an OutOfMemoryError") > -1);
828         }
829         Thread.sleep(1000);
830     }
831 
832     /**
833      * Test auto-refresh from a meta tag with URL quoted.
834      * @throws Exception if the test fails
835      */
836     @Test
837     public void refresh_MetaTagQuoted() throws Exception {
838         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT='0;URL=\"§§URL§§\"'>");
839     }
840 
841     /**
842      * Test auto-refresh from a meta tag with URL partly quoted.
843      * @throws Exception if the test fails
844      */
845     @Test
846     public void refresh_MetaTagPartlyQuoted() throws Exception {
847         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT=\"0;URL='§§URL§§\">");
848     }
849 
850     private void testRefresh_MetaTag(final String metaTag) throws Exception {
851         final String firstContent = DOCTYPE_HTML
852             + "<html><head><title>first</title>\n"
853             + metaTag.replace("§§URL§§", URL_SECOND.toString()) + "\n"
854             + "<META HTTP-EQUIV='Refresh' CONTENT=\"0;URL='" + URL_SECOND + "\">\n"
855             + "</head><body></body></html>";
856         final String secondContent = DOCTYPE_HTML
857                 + "<html><head><title>second</title></head><body></body></html>";
858 
859         final WebClient client = getWebClient();
860 
861         final MockWebConnection webConnection = new MockWebConnection();
862         webConnection.setResponse(URL_FIRST, firstContent);
863         webConnection.setResponse(URL_SECOND, secondContent);
864         client.setWebConnection(webConnection);
865 
866         final HtmlPage page = client.getPage(URL_FIRST);
867 
868         assertEquals("second", page.getTitleText());
869     }
870 
871     /**
872      * Test auto-refresh from a meta tag inside noscript.
873      * @throws Exception if the test fails
874      */
875     @Test
876     public void refresh_MetaTagNoScript() throws Exception {
877         final String firstContent = DOCTYPE_HTML
878             + "<html><head><title>first</title>\n"
879             + "<noscript>\n"
880             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"0;URL=" + URL_SECOND + "\">\n"
881             + "</noscript>\n"
882             + "</head><body></body></html>";
883         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
884 
885         final WebClient client = getWebClient();
886 
887         final MockWebConnection webConnection = new MockWebConnection();
888         webConnection.setResponse(URL_FIRST, firstContent);
889         webConnection.setResponse(URL_SECOND, secondContent);
890         client.setWebConnection(webConnection);
891 
892         HtmlPage page = client.getPage(URL_FIRST);
893         assertEquals("first", page.getTitleText());
894 
895         client.getOptions().setJavaScriptEnabled(false);
896         page = client.getPage(URL_FIRST);
897         assertEquals("second", page.getTitleText());
898     }
899 
900     /**
901      * Test auto-refresh from a meta tag with a refresh handler that doesn't refresh.
902      * @throws Exception if the test fails
903      */
904     @Test
905     public void refresh_MetaTag_CustomRefreshHandler() throws Exception {
906         final String firstContent = DOCTYPE_HTML
907             + "<html><head><title>first</title>\n"
908             + "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"3;URL=" + URL_SECOND + "\">\n"
909             + "</head><body></body></html>";
910         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
911 
912         final WebClient client = getWebClient();
913         final List<Object> collectedItems = new ArrayList<>();
914         client.setRefreshHandler(new LoggingRefreshHandler(collectedItems));
915 
916         final MockWebConnection webConnection = new MockWebConnection();
917         webConnection.setResponse(URL_FIRST, firstContent);
918         webConnection.setResponse(URL_SECOND, secondContent);
919         client.setWebConnection(webConnection);
920 
921         final HtmlPage page = client.getPage(URL_FIRST);
922 
923         assertEquals("first", page.getTitleText());
924 
925         // avoid using equals() on URL because it takes to much time (due to ip resolution)
926         assertEquals("first", collectedItems.get(0));
927         assertEquals(URL_SECOND, (URL) collectedItems.get(1));
928         assertEquals(Integer.valueOf(3), collectedItems.get(2));
929     }
930 
931     /**
932      * Test that whitespace before and after ';' is permitted.
933      *
934      * @throws Exception if the test fails
935      */
936     @Test
937     public void refresh_MetaTag_Whitespace() throws Exception {
938         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT='0  ;  URL=§§URL§§'>");
939     }
940 
941     /**
942      * Test that the refresh time can be a double ("3.4", for example), not just an integer.
943      *
944      * @throws Exception if an error occurs
945      */
946     @Test
947     public void refresh_MetaTag_Double() throws Exception {
948         testRefresh_MetaTag("<META HTTP-EQUIV='Refresh' CONTENT='1.2  ;  URL=§§URL§§'>");
949     }
950 
951     /**
952      * Test auto-refresh from a response header.
953      * @throws Exception if the test fails
954      */
955     @Test
956     public void refresh_HttpResponseHeader() throws Exception {
957         final String firstContent = DOCTYPE_HTML
958             + "<html><head><title>first</title>\n"
959             + "</head><body></body></html>";
960         final String secondContent = DOCTYPE_HTML + "<html><head><title>second</title></head><body></body></html>";
961 
962         final WebClient client = getWebClient();
963 
964         final MockWebConnection webConnection = new MockWebConnection();
965         webConnection.setResponse(URL_FIRST, firstContent, 200, "OK", MimeType.TEXT_HTML, Collections
966                 .singletonList(new NameValuePair("Refresh", "2;URL=" + URL_SECOND)));
967         webConnection.setResponse(URL_SECOND, secondContent);
968         client.setWebConnection(webConnection);
969 
970         final HtmlPage page = client.getPage(URL_FIRST);
971 
972         assertEquals("second", page.getTitleText());
973     }
974 
975     /**
976      * Test that the parent of the DOM Document (HtmlPage) is null.
977      * @throws Exception if the test fails
978      */
979     @Test
980     public void documentParentIsNull() throws Exception {
981         final String htmlContent = DOCTYPE_HTML
982             + "<html>\n"
983             + "<head><title>foo</title></head>\n"
984             + "<body>\n"
985             + "</body></html>";
986         final HtmlPage page = loadPage(htmlContent);
987 
988         assertNotNull(page);
989         assertNull(page.getParentNode());
990     }
991 
992     /**
993      * @throws Exception if the test fails
994      */
995     @Test
996     public void documentElement() throws Exception {
997         final String htmlContent = DOCTYPE_HTML
998             + "<html>\n"
999             + "<head><title>foo</title></head>\n"
1000             + "<body>\n"
1001             + "</body></html>";
1002         final HtmlPage page = loadPage(htmlContent);
1003 
1004         final DomElement root = page.getDocumentElement();
1005 
1006         assertNotNull(root);
1007         assertEquals("html", root.getTagName());
1008         assertSame(page, root.getParentNode());
1009     }
1010 
1011     /**
1012      * @throws Exception if the test fails
1013      */
1014     @Test
1015     public void documentNodeType() throws Exception {
1016         final String htmlContent = DOCTYPE_HTML
1017             + "<html>\n"
1018             + "<head><title>foo</title></head>\n"
1019             + "<body>\n"
1020             + "</body></html>";
1021         final HtmlPage page = loadPage(htmlContent);
1022 
1023         final DomElement root = page.getDocumentElement();
1024 
1025         assertEquals(org.w3c.dom.Node.DOCUMENT_NODE, page.getNodeType());
1026         assertEquals(org.w3c.dom.Node.ELEMENT_NODE, root.getNodeType());
1027         assertEquals("#document", page.getNodeName());
1028     }
1029 
1030     /**
1031      * @throws Exception if the test fails
1032      */
1033     @Test
1034     public void deregisterFrameWithoutSrc() throws Exception {
1035         final String htmlContent = DOCTYPE_HTML
1036             + "<html>\n"
1037             + "<head><title>foo</title></head>\n"
1038             + "<body>\n"
1039             + "<iframe></iframe>\n"
1040             + "<a href='about:blank'>link</a>\n"
1041             + "</body></html>";
1042 
1043         final HtmlPage page = loadPage(htmlContent);
1044         final HtmlAnchor link = page.getAnchors().get(0);
1045         link.click();
1046     }
1047 
1048     /**
1049      * Tests that a return statement in onload doesn't throw any exception.
1050      * @throws Exception if the test fails
1051      */
1052     @Test
1053     public void onLoadReturn() throws Exception {
1054         final String htmlContent = DOCTYPE_HTML
1055             + "<html><head><title>foo</title></head>\n"
1056             + "<body onload='return true'>\n"
1057             + "</body></html>";
1058 
1059         loadPage(htmlContent);
1060     }
1061 
1062     /**
1063      * @exception Exception If the test fails
1064      */
1065     @Test
1066     public void asXml() throws Exception {
1067         final String htmlContent =
1068             "<html><head><title>foo</title></head>"
1069             + "<body><p>helloworld</p></body>"
1070             + "</html>";
1071 
1072         final HtmlPage page = loadPage(htmlContent);
1073         String xml = page.asXml();
1074         final String prefix = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
1075         assertTrue(xml.startsWith(prefix));
1076         xml = xml.substring(prefix.length());
1077         assertEquals(htmlContent, xml.replaceAll("\\s", ""));
1078     }
1079 
1080     /**
1081      * Tests that the generated XML is valid as HTML code too.
1082      * @exception Exception If the test fails
1083      */
1084     @Test
1085     public void asXmlValidHtmlOutput() throws Exception {
1086         final String html =
1087             "<html><head><title>foo</title>"
1088             + "<script src='script.js'></script></head>"
1089             + "<body><div></div><iframe src='about:blank'></iframe></body>"
1090             + "</html>";
1091 
1092         final WebClient client = getWebClient();
1093         final MockWebConnection webConnection = new MockWebConnection();
1094         webConnection.setDefaultResponse(html);
1095         webConnection.setResponse(new URL(URL_FIRST, "script.js"), "", "text/javascript");
1096         client.setWebConnection(webConnection);
1097 
1098         final HtmlPage page = client.getPage(URL_FIRST);
1099 
1100         String xml = page.asXml();
1101         final String prefix = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
1102         assertTrue(xml.startsWith(prefix));
1103         xml = xml.substring(prefix.length());
1104         assertEquals(html, xml
1105                 .replaceAll("[\\n\\r]", "")
1106                 .replaceAll("\\s\\s+", "")
1107                 .replaceAll("\"", "'"));
1108     }
1109 
1110     /**
1111      * @exception Exception If the test fails
1112      */
1113     @Test
1114     public void asXml2() throws Exception {
1115         final String htmlContent = DOCTYPE_HTML
1116             + "<html><head><title>foo</title>\n"
1117             + "<script>var test = 15 < 16;</script></head>\n"
1118             + "</head>\n"
1119             + "<body onload='test=(1 > 2) && (45 < 78)'><p>helloworld &amp;amp; helloall</p></body>\n"
1120             + "</html>";
1121 
1122         final HtmlPage page = loadPage(htmlContent);
1123         assertNotNull("xml document could not be parsed", page.asXml());
1124         final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1125         final DocumentBuilder builder = factory.newDocumentBuilder();
1126         builder.parse(IOUtils.toInputStream(page.asXml(), StandardCharsets.ISO_8859_1));
1127     }
1128 
1129     /**
1130      * @throws Exception if the test fails
1131      */
1132     @Test
1133     public void asXml_unicode() throws Exception {
1134         final String unicodeString = "\u064A\u0627 \u0644\u064A\u064A\u0644";
1135         final String html = DOCTYPE_HTML
1136             + "<html>\n"
1137             + "<head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'></head>\n"
1138             + "<body><span id='foo'>" + unicodeString + "</span></body></html>";
1139 
1140         final WebClient client = getWebClient();
1141         final MockWebConnection webConnection = new MockWebConnection();
1142 
1143         webConnection.setDefaultResponse(StringUtils.toByteArray(html, UTF_8), 200, "OK", MimeType.TEXT_HTML);
1144         client.setWebConnection(webConnection);
1145 
1146         final HtmlPage page = client.getPage(URL_FIRST);
1147         final String xml = page.asXml();
1148         assertTrue(xml.contains("<?xml "));
1149         assertTrue(xml.contains(unicodeString));
1150     }
1151 
1152     /**
1153      * @throws Exception if the test fails
1154      */
1155     @Test
1156     public void asXml_noscript() throws Exception {
1157         final String html = DOCTYPE_HTML
1158             + "<html>"
1159             + "<body>"
1160             + "<noscript><p><strong>your browser does not support JavaScript</strong></p></noscript>"
1161             + "</body></html>";
1162 
1163         final String expected = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"
1164                 + "<html>"
1165                 + "  <head/>"
1166                 + "  <body>"
1167                 + "    <noscript>"
1168                 + "      &lt;p&gt;&lt;strong&gt;your browser does not support JavaScript&lt;/strong&gt;&lt;/p&gt;"
1169                 + "    </noscript>"
1170                 + "  </body>"
1171                 + "</html>";
1172 
1173         final HtmlPage page = loadPage(html);
1174         assertEquals(expected, page.asXml().replaceAll("[\\n\\r]", ""));
1175     }
1176 
1177     /**
1178      * @exception Exception if the test fails
1179      */
1180     @Test
1181     public void getElementsById() throws Exception {
1182         final String html = DOCTYPE_HTML
1183                 + "<html><body>"
1184                 + "<div id='a'>foo</div>"
1185                 + "<div id='b'/><div id='b'/>"
1186                 + "<div id='c'><div id='c'/></div>"
1187                 + "</body></html>";
1188         final HtmlPage page = loadPage(html);
1189         assertEquals(1, page.getElementsById("a").size());
1190         assertEquals(2, page.getElementsById("b").size());
1191         assertEquals(2, page.getElementsById("c").size());
1192         assertEquals(0, page.getElementsById("d").size());
1193 
1194         final DomElement a = page.getElementsById("a").get(0);
1195         a.remove();
1196         assertEquals(0, page.getElementsById("a").size());
1197 
1198         final DomElement b1 = page.getElementsById("b").get(0);
1199         b1.appendChild(a);
1200 
1201         assertEquals(1, page.getElementsById("a").size());
1202     }
1203 
1204     /**
1205      * @exception Exception if the test fails
1206      */
1207     @Test
1208     public void getElementsByName() throws Exception {
1209         final String html = DOCTYPE_HTML
1210                 + "<html><body><div name='a'>foo</div><div name='b'/><div name='b'/></body></html>";
1211         final HtmlPage page = loadPage(html);
1212         assertEquals(1, page.getElementsByName("a").size());
1213         assertEquals(2, page.getElementsByName("b").size());
1214         assertEquals(0, page.getElementsByName("c").size());
1215 
1216         final DomElement a = page.getElementsByName("a").get(0);
1217         a.remove();
1218         assertEquals(0, page.getElementsByName("a").size());
1219 
1220         final DomElement b1 = page.getElementsByName("b").get(0);
1221         b1.appendChild(a);
1222         assertEquals(1, page.getElementsByName("a").size());
1223     }
1224 
1225     /**
1226      * @exception Exception if the test fails
1227      */
1228     @Test
1229     public void getElementByName() throws Exception {
1230         final String html = DOCTYPE_HTML
1231             + "<html><body>\n"
1232             + "<div id='a' name='a'>foo</div>\n"
1233             + "<div id='b1' name='b'>bar</div>\n"
1234             + "<div id='b2' name='b'>baz</div></body></html>";
1235         final HtmlPage page = loadPage(html);
1236         assertEquals(page.getElementById("a"), page.getElementByName("a"));
1237         assertEquals(page.getElementById("b1"), page.getElementByName("b"));
1238 
1239         page.getElementByName("b").remove();
1240         assertEquals(page.getElementById("b2"), page.getElementByName("b"));
1241     }
1242 
1243     /**
1244      * @exception Exception if the test fails
1245      */
1246     @Test(expected = ElementNotFoundException.class)
1247     public void getElementByNameNotfound() throws Exception {
1248         final String html = DOCTYPE_HTML
1249             + "<html><body>\n"
1250             + "<div id='a' name='a'>foo</div>\n"
1251             + "<div id='b1' name='b'>bar</div>\n"
1252             + "<div id='b2' name='b'>baz</div></body></html>";
1253         final HtmlPage page = loadPage(html);
1254         page.getElementByName("c");
1255     }
1256 
1257     /**
1258      * @exception Exception if the test fails
1259      */
1260     @Test
1261     public void getHtmlElementsByIdAndOrName() throws Exception {
1262         final String html = DOCTYPE_HTML
1263                 + "<html><body><div name='a' id='a'>foo</div><div name='b' id='c'>bar</div>"
1264                 + "<div name='b' id='d'>bar</div></body></html>";
1265         final HtmlPage page = loadPage(html);
1266         assertEquals(1, page.getElementsByIdAndOrName("a").size());
1267         assertEquals(2, page.getElementsByIdAndOrName("b").size());
1268         assertEquals(1, page.getElementsByIdAndOrName("c").size());
1269         assertEquals(1, page.getElementsByIdAndOrName("d").size());
1270 
1271         final DomElement a = page.getElementsByIdAndOrName("a").get(0);
1272         a.remove();
1273         assertEquals(0, page.getElementsByIdAndOrName("a").size());
1274 
1275         final DomElement b1 = page.getElementsByIdAndOrName("b").get(0);
1276         b1.appendChild(a);
1277         assertEquals(1, page.getElementsByIdAndOrName("a").size());
1278     }
1279 
1280     /**
1281      * Regression test for bug #287.
1282      * @exception Exception If the test fails
1283      */
1284     @Test
1285     public void getHtmlElementByIdAfterRemove() throws Exception {
1286         final String htmlContent = DOCTYPE_HTML
1287             + "<html><head><title>foo</title></head>\n"
1288             + "<body>\n"
1289             + "<div id='div1'>\n"
1290             + "<div id='div2'>\n"
1291             + "</div>\n"
1292             + "</div>\n"
1293             + "</body>\n"
1294             + "</html>";
1295 
1296         final HtmlPage page = loadPage(htmlContent);
1297         final HtmlElement div1 = page.getHtmlElementById("div1");
1298         page.getHtmlElementById("div2"); // would throw if not found
1299         div1.remove();
1300         try {
1301             page.getHtmlElementById("div1"); // throws if not found
1302             fail("div1 should have been removed");
1303         }
1304         catch (final ElementNotFoundException e) {
1305             // nothing
1306         }
1307 
1308         try {
1309             page.getHtmlElementById("div2"); // throws if not found
1310             fail("div2 should have been removed");
1311         }
1312         catch (final ElementNotFoundException e) {
1313             // nothing
1314         }
1315     }
1316 
1317     /**
1318      * Test getHtmlElementById() when 2 elements have the same id and the first one
1319      * is removed.
1320      * @exception Exception If the test fails
1321      */
1322     @Test
1323     public void getHtmlElementById_idTwice() throws Exception {
1324         final String htmlContent = DOCTYPE_HTML
1325             + "<html><head><title>foo</title></head>\n"
1326             + "<body>\n"
1327             + "<div id='id1'>foo</div>\n"
1328             + "<span id='id1'>bla</span>\n"
1329             + "</body>\n"
1330             + "</html>";
1331 
1332         final HtmlPage page = loadPage(htmlContent);
1333         final HtmlElement elt1 = page.getHtmlElementById("id1");
1334         assertEquals("div", elt1.getNodeName());
1335         elt1.remove();
1336         assertEquals("span", page.getHtmlElementById("id1").getNodeName());
1337     }
1338 
1339     /**
1340      * Test the "set-cookie" meta tag.
1341      * @throws Exception if the test fails
1342      */
1343     @Test
1344     @Alerts("webm=none")
1345     public void setCookieMetaTag() throws Exception {
1346         final String content = DOCTYPE_HTML
1347             + "<html><head>\n"
1348             + "<meta http-equiv='set-cookie' content='webm=none; path=/;'>\n"
1349             + "</head><body>\n"
1350             + "<script>document.title = document.cookie</script>\n"
1351             + "</body></html>";
1352 
1353         final HtmlPage page = loadPage(content);
1354         assertEquals(getExpectedAlerts()[0], page.getTitleText());
1355 
1356         final Set<Cookie> cookies = page.getWebClient().getCookieManager().getCookies();
1357         assertEquals(1, cookies.size());
1358         final Cookie cookie = cookies.iterator().next();
1359         assertEquals(page.getUrl().getHost(), cookie.getDomain());
1360         assertEquals("webm", cookie.getName());
1361         assertEquals("none", cookie.getValue());
1362         assertEquals("/", cookie.getPath());
1363     }
1364 
1365     /**
1366      * Test for bug #474.
1367      * @throws Exception if the test fails
1368      */
1369     @Test
1370     public void noSlashURL() throws Exception {
1371         testNoSlashURL("http:/second");
1372         testNoSlashURL("http:second");
1373     }
1374 
1375     private void testNoSlashURL(final String url) throws Exception {
1376         final String firstContent = DOCTYPE_HTML
1377             + "<html><body>\n"
1378             + "<iframe id='myIFrame' src='" + url + "'></iframe>\n"
1379             + "</body></html>";
1380 
1381         final String secondContent = DOCTYPE_HTML + "<html><body></body></html>";
1382         final WebClient client = getWebClient();
1383 
1384         final URL secondURL = new URL("http://second/");
1385         final MockWebConnection webConnection = new MockWebConnection();
1386         webConnection.setResponse(URL_FIRST, firstContent);
1387         webConnection.setResponse(secondURL, secondContent);
1388 
1389         client.setWebConnection(webConnection);
1390 
1391         final HtmlPage firstPage = client.getPage(URL_FIRST);
1392         final HtmlInlineFrame iframe = firstPage.getHtmlElementById("myIFrame");
1393 
1394         assertEquals(secondURL, iframe.getEnclosedPage().getUrl());
1395     }
1396 
1397     /**
1398      * @throws Exception failure
1399      */
1400     @Test
1401     public void metaTagWithEmptyURL() throws Exception {
1402         final WebClient client = getWebClient();
1403         client.setRefreshHandler(new ImmediateRefreshHandler());
1404 
1405         // connection will return a page with <meta ... refresh> for the first call
1406         // and the same page without it for the other calls
1407         final MockWebConnection webConnection = new MockWebConnection() {
1408             private int nbCalls_ = 0;
1409             @Override
1410             public WebResponse getResponse(final WebRequest request) throws IOException {
1411                 String content = DOCTYPE_HTML + "<html><head>\n";
1412                 if (nbCalls_ == 0) {
1413                     content += "<meta http-equiv='refresh' content='1; URL='>\n";
1414                 }
1415                 content += "</head><body></body></html>";
1416                 nbCalls_++;
1417 
1418                 final StringWebResponse response = new StringWebResponse(content, request.getUrl());
1419                 response.getWebRequest().setHttpMethod(request.getHttpMethod());
1420                 return response;
1421             }
1422         };
1423         client.setWebConnection(webConnection);
1424 
1425         final WebRequest request = new WebRequest(URL_FIRST);
1426         request.setHttpMethod(HttpMethod.POST);
1427         client.getPage(request);
1428     }
1429 
1430     /**
1431      * @throws Exception if the test fails
1432      */
1433     @Test
1434     public void serialization() throws Exception {
1435         // The document.all and form.elements calls are important because they trigger the creation
1436         // of HTMLCollections, which have caused serialization problems in the past (see bug #606).
1437 
1438         final String content = DOCTYPE_HTML
1439             + "<html><body>\n"
1440             + "<div id='myId'>Hello there!</div>\n"
1441             + "<script>\n"
1442             + "  var x = document.all;\n"
1443             + "  window.onload = function() {alert('foo')};\n"
1444 
1445             // this tests 3103703
1446             // we don't store the jobs are pending at the moment of serialization
1447             + "  var aktiv = window.setInterval('foo()', 1000);\n"
1448             + "  var i = 0;\n"
1449             + "  function foo() {\n"
1450             + "    i = i + 1;\n"
1451             + "    if (i >= 10)\n"
1452             + "      window.clearInterval(aktiv);\n"
1453             + "  }\n"
1454             + "</script>\n"
1455             + "<form name='f' id='f'></form>\n"
1456             + "<script>var y = document.getElementById('f').elements;</script>\n"
1457             + "</body></html>";
1458 
1459         // waiting for the alerts creates some more js objects associated with the page
1460         // this tests 3103703
1461         final List<String> expectedAlerts = new LinkedList<>();
1462         expectedAlerts.add("foo");
1463 
1464         final HtmlPage page1 = loadPage(content, expectedAlerts);
1465         final byte[] bytes = SerializationUtils.serialize(page1);
1466 
1467         final HtmlPage page2 = (HtmlPage) SerializationUtils.deserialize(bytes);
1468 
1469         final Iterator<HtmlElement> iterator1 = page1.getHtmlElementDescendants().iterator();
1470         final Iterator<HtmlElement> iterator2 = page2.getHtmlElementDescendants().iterator();
1471         while (iterator1.hasNext()) {
1472             assertTrue(iterator2.hasNext());
1473             final HtmlElement element1 = iterator1.next();
1474             final HtmlElement element2 = iterator2.next();
1475             assertEquals(element1.getNodeName(), element2.getNodeName());
1476         }
1477         assertFalse(iterator2.hasNext());
1478         assertEquals("Hello there!", page2.getHtmlElementById("myId").getFirstChild().getNodeValue());
1479     }
1480 
1481     /**
1482      * Test issue 513.
1483      * @throws Exception if the test fails
1484      */
1485     @Test
1486     public void serializationLambda() throws Exception {
1487         final String content = DOCTYPE_HTML
1488             + "<html><body>\n"
1489             + "<div id='myId'>Hello there!</div>\n"
1490             + "<form name='f' id='f'></form>\n"
1491             + "<script>var y = document.body.getElementsByTagName('form').elements;</script>\n"
1492             + "</body></html>";
1493 
1494         final HtmlPage page1 = loadPage(content);
1495         final byte[] bytes = SerializationUtils.serialize(page1);
1496 
1497         final HtmlPage page2 = (HtmlPage) SerializationUtils.deserialize(bytes);
1498 
1499         final Iterator<HtmlElement> iterator1 = page1.getHtmlElementDescendants().iterator();
1500         final Iterator<HtmlElement> iterator2 = page2.getHtmlElementDescendants().iterator();
1501         while (iterator1.hasNext()) {
1502             assertTrue(iterator2.hasNext());
1503             final HtmlElement element1 = iterator1.next();
1504             final HtmlElement element2 = iterator2.next();
1505             assertEquals(element1.getNodeName(), element2.getNodeName());
1506         }
1507         assertFalse(iterator2.hasNext());
1508         assertEquals("Hello there!", page2.getHtmlElementById("myId").getFirstChild().getNodeValue());
1509     }
1510 
1511     /**
1512      * @throws Exception if the test fails
1513      */
1514     @Test
1515     public void serializationStaticDomNodeList() throws Exception {
1516         final String content = DOCTYPE_HTML
1517             + "<html><body>\n"
1518             + "<div id='myId'>Hello there!</div>\n"
1519             + "<form name='f' id='f'></form>\n"
1520             + "<script>var y = document.querySelectorAll('*');</script>\n"
1521             + "</body></html>";
1522 
1523         final HtmlPage page1 = loadPage(content);
1524         final byte[] bytes = SerializationUtils.serialize(page1);
1525 
1526         final HtmlPage page2 = (HtmlPage) SerializationUtils.deserialize(bytes);
1527 
1528         final Iterator<HtmlElement> iterator1 = page1.getHtmlElementDescendants().iterator();
1529         final Iterator<HtmlElement> iterator2 = page2.getHtmlElementDescendants().iterator();
1530         while (iterator1.hasNext()) {
1531             assertTrue(iterator2.hasNext());
1532             final HtmlElement element1 = iterator1.next();
1533             final HtmlElement element2 = iterator2.next();
1534             assertEquals(element1.getNodeName(), element2.getNodeName());
1535         }
1536         assertFalse(iterator2.hasNext());
1537         assertEquals("Hello there!", page2.getHtmlElementById("myId").getFirstChild().getNodeValue());
1538     }
1539 
1540     /**
1541      * Verifies that a cloned HtmlPage has its own "idMap_".
1542      * @throws Exception if the test fails
1543      */
1544     @Test
1545     public void clonedPageHasOwnIdMap() throws Exception {
1546         final String content = DOCTYPE_HTML
1547             + "<html><head><title>foo</title>"
1548             + "<body>"
1549             + "<div id='id1' class='cl1'><div id='id2' class='cl2'></div></div>"
1550             + "</body></html>";
1551 
1552         final HtmlPage page = loadPage(content);
1553         final HtmlElement id1 = (HtmlElement) page.getDocumentElement().getLastChild().getLastChild();
1554         assertEquals("id1", id1.getId());
1555         assertSame(id1, page.getHtmlElementById("id1"));
1556         final HtmlPage clone = page.cloneNode(true);
1557         assertSame(id1, page.getHtmlElementById("id1"));
1558         final HtmlElement id1clone = (HtmlElement) clone.getDocumentElement().getLastChild().getLastChild();
1559         assertNotSame(id1, id1clone);
1560         assertEquals("id1", id1clone.getId());
1561         assertSame(id1clone, clone.getHtmlElementById("id1"));
1562         assertNotSame(id1clone, page.getHtmlElementById("id1"));
1563         assertNotSame(id1, clone.getHtmlElementById("id1"));
1564 
1565         page.getHtmlElementById("id2").remove();
1566         try {
1567             page.getHtmlElementById("id2");
1568             fail("should have thrown ElementNotFoundException");
1569         }
1570         catch (final ElementNotFoundException expected) {
1571         }
1572         assertNotNull(clone.getHtmlElementById("id2"));
1573     }
1574 
1575     /**
1576      * Verifies that a cloned HtmlPage has its own "documentElement".
1577      * @throws Exception if the test fails
1578      */
1579     @Test
1580     public void clonedPageHasOwnDocumentElement() throws Exception {
1581         final String content = DOCTYPE_HTML
1582             + "<html><head><title>foo</title>\n"
1583             + "<body>\n"
1584             + "<div id='id1' class='cl1'><div id='id2' class='cl2'></div></div>\n"
1585             + "</body></html>";
1586 
1587         final HtmlPage page = loadPage(content);
1588         final HtmlPage clone = page.cloneNode(true);
1589         assertTrue(page != clone);
1590         final DomElement doc = page.getDocumentElement();
1591         final DomElement docclone = clone.getDocumentElement();
1592         assertTrue(doc != docclone);
1593     }
1594 
1595     /**
1596      * @throws Exception if the test fails
1597      */
1598     @Test
1599     public void htmlAttributeChangeListener_AddAttribute() throws Exception {
1600         final String htmlContent = DOCTYPE_HTML
1601             + "<html><head><title>foo</title>\n"
1602             + "<script>\n"
1603             + "  function clickMe() {\n"
1604             + "    var p1 = document.getElementById('p1');\n"
1605             + "    p1.setAttribute('title', 'myTitle');\n"
1606             + "  }\n"
1607             + "</script>\n"
1608             + "</head>\n"
1609             + "<body>\n"
1610             + "<p id='p1'></p>\n"
1611             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1612             + "</body></html>";
1613 
1614         final String[] expectedValues = {"attributeAdded: p,title,myTitle"};
1615         final HtmlPage page = loadPage(htmlContent);
1616         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1617         page.addHtmlAttributeChangeListener(listenerImpl);
1618         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1619 
1620         myButton.click();
1621         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1622     }
1623 
1624     /**
1625      * @throws Exception if the test fails
1626      */
1627     @Test
1628     public void htmlAttributeChangeListener_ReplaceAttribute() throws Exception {
1629         final String htmlContent = DOCTYPE_HTML
1630             + "<html><head><title>foo</title>\n"
1631             + "<script>\n"
1632             + "  function clickMe() {\n"
1633             + "    var p1 = document.getElementById('p1');\n"
1634             + "    p1.setAttribute('title', p1.getAttribute('title') + 'a');\n"
1635             + "  }\n"
1636             + "</script>\n"
1637             + "</head>\n"
1638             + "<body>\n"
1639             + "<p id='p1' title='myTitle'></p>\n"
1640             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1641             + "</body></html>";
1642 
1643         final String[] expectedValues = {"attributeReplaced: p,title,myTitle"};
1644         final HtmlPage page = loadPage(htmlContent);
1645         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1646         page.addHtmlAttributeChangeListener(listenerImpl);
1647         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1648 
1649         myButton.click();
1650         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1651     }
1652 
1653     /**
1654      * @throws Exception if the test fails
1655      */
1656     @Test
1657     public void htmlAttributeChangeListener_RemoveAttribute() throws Exception {
1658         final String htmlContent = DOCTYPE_HTML
1659             + "<html><head><title>foo</title>\n"
1660             + "<script>\n"
1661             + "  function clickMe() {\n"
1662             + "    var p1 = document.getElementById('p1');\n"
1663             + "    p1.removeAttribute('title');\n"
1664             + "  }\n"
1665             + "</script>\n"
1666             + "</head>\n"
1667             + "<body>\n"
1668             + "<p id='p1' title='myTitle'></p>\n"
1669             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1670             + "</body></html>";
1671 
1672         final String[] expectedValues = {"attributeRemoved: p,title,myTitle"};
1673         final HtmlPage page = loadPage(htmlContent);
1674         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1675         page.addHtmlAttributeChangeListener(listenerImpl);
1676         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1677 
1678         myButton.click();
1679         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1680     }
1681 
1682     /**
1683      * @throws Exception if the test fails
1684      */
1685     @Test
1686     public void htmlAttributeChangeListener_RemoveListener() throws Exception {
1687         final String htmlContent = DOCTYPE_HTML
1688             + "<html><head><title>foo</title>\n"
1689             + "<script>\n"
1690             + "  function clickMe() {\n"
1691             + "    var p1 = document.getElementById('p1');\n"
1692             + "    p1.setAttribute('title', p1.getAttribute('title') + 'a');\n"
1693             + "  }\n"
1694             + "</script>\n"
1695             + "</head>\n"
1696             + "<body>\n"
1697             + "<p id='p1' title='myTitle'></p>\n"
1698             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
1699             + "</body></html>";
1700 
1701         final String[] expectedValues = {"attributeReplaced: p,title,myTitle"};
1702         final HtmlPage page = loadPage(htmlContent);
1703         final HtmlAttributeChangeListenerTestImpl listenerImpl = new HtmlAttributeChangeListenerTestImpl();
1704         page.addHtmlAttributeChangeListener(listenerImpl);
1705         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
1706 
1707         myButton.click();
1708         page.removeHtmlAttributeChangeListener(listenerImpl);
1709         myButton.click();
1710         assertEquals(expectedValues, listenerImpl.getCollectedValues());
1711     }
1712 
1713     /**
1714      * @throws Exception if the test fails
1715      */
1716     @Test
1717     public void htmlAttributeChangeListener_ListenerRegistersNewListener() throws Exception {
1718         final String htmlContent = DOCTYPE_HTML
1719             + "<html><head><title>foo</title>\n"
1720             + "</head>\n"
1721             + "<body>\n"
1722             + "<p id='p1' title='myTitle'></p>\n"
1723             + "</body></html>";
1724 
1725         final HtmlPage page = loadPage(htmlContent);
1726 
1727         final List<String> collector = new ArrayList<>();
1728         final HtmlAttributeChangeListener listener2 = new HtmlAttributeChangeListenerTestImpl() {
1729             @Override
1730             public void attributeReplaced(final HtmlAttributeChangeEvent event) {
1731                 collector.add("in listener 2");
1732             }
1733         };
1734         final HtmlAttributeChangeListener listener1 = new HtmlAttributeChangeListenerTestImpl() {
1735             @Override
1736             public void attributeReplaced(final HtmlAttributeChangeEvent event) {
1737                 collector.add("in listener 1");
1738                 page.addHtmlAttributeChangeListener(listener2);
1739             }
1740         };
1741 
1742         page.addHtmlAttributeChangeListener(listener1);
1743 
1744         final HtmlElement p = page.getHtmlElementById("p1");
1745         p.setAttribute("title", "new title");
1746 
1747         final String[] expectedValues = {"in listener 1"};
1748         assertEquals(expectedValues, collector);
1749         collector.clear();
1750 
1751         p.setAttribute("title", "new new title");
1752         final String[] expectedValues2 = {"in listener 1", "in listener 2"};
1753         assertEquals(expectedValues2, collector);
1754     }
1755 
1756     /**
1757      * @throws Exception if the test fails
1758      */
1759     @Test
1760     public void caseInsensitiveRegexReplacement() throws Exception {
1761         final String html = DOCTYPE_HTML
1762             + "<html><body><script>\n"
1763             + "var r = /^([#.]?)([a-z0-9\\*_-]*)/i;\n"
1764             + "var s = '#userAgent';\n"
1765             + "s = s.replace(r, '');\n"
1766             + "alert(s.length);\n"
1767             + "</script></body></html>";
1768         final String[] expectedAlerts = {"0"};
1769         final List<String> actualAlerts = new ArrayList<>();
1770         loadPage(html, actualAlerts);
1771         assertEquals(expectedAlerts, actualAlerts);
1772     }
1773 
1774     /**
1775      * @throws Exception if the test fails
1776      */
1777     @Test
1778     public void regexReplacementWithFunction() throws Exception {
1779         final String html = DOCTYPE_HTML
1780             + "<html><body><script>\n"
1781             + "var r = /-([a-z])/ig;\n"
1782             + "var s = 'font-size';\n"
1783             + "s = s.replace(r, function(z,b){return b.toUpperCase();});\n"
1784             + "alert(s);\n"
1785             + "</script></body></html>";
1786         final String[] expectedAlerts = {"fontSize"};
1787         final List<String> collectedAlerts = new ArrayList<>();
1788         loadPage(html, collectedAlerts);
1789         assertEquals(expectedAlerts, collectedAlerts);
1790     }
1791 
1792     /**
1793      * @throws Exception if the test fails
1794      */
1795     @Test
1796     public void title_EmptyXmlTagExpanded() throws Exception {
1797         final String content = DOCTYPE_HTML
1798             + "<html><head><title/></head>\n"
1799             + "<body>Hello World!</body></html>";
1800         final HtmlPage page = loadPage(content);
1801         assertTrue(page.asXml().indexOf("</title>") != -1);
1802     }
1803 
1804     /**
1805      * @throws Exception if the test fails
1806      */
1807     @Test
1808     public void onunLoadHandler() throws Exception {
1809         final String htmlContent = DOCTYPE_HTML
1810             + "<html><head><title>foo</title>\n"
1811             + "</head><body onunload='alert(\"foo\");alert(\"bar\")'>\n"
1812             + "</body></html>";
1813 
1814         final String[] expectedAlerts = {"foo", "bar"};
1815         final List<String> collectedAlerts = new ArrayList<>();
1816         final WebClient client = getWebClient();
1817         client.setAlertHandler(new CollectingAlertHandler(collectedAlerts));
1818         final MockWebConnection conn = new MockWebConnection();
1819         conn.setResponse(URL_FIRST, htmlContent);
1820         client.setWebConnection(conn);
1821 
1822         client.getPage(URL_FIRST);
1823         client.getPage(URL_FIRST);
1824 
1825         assertEquals(expectedAlerts, collectedAlerts);
1826     }
1827 
1828     /**
1829      * @throws Exception if the test fails
1830      */
1831     @Test
1832     public void onbeforeunloadHandler_ok() throws Exception {
1833         testOnbeforeunloadHandler(true, "second");
1834     }
1835 
1836     /**
1837      * @throws Exception if the test fails
1838      */
1839     @Test
1840     public void onbeforeunloadHandler_cancel() throws Exception {
1841         testOnbeforeunloadHandler(false, "first");
1842     }
1843 
1844     /**
1845      * @param browserVersion the browser version to use
1846      * @param handlerOk whether <tt>OnbeforeunloadHandler.handleEvent</tt> will return {@code true} of {@code false}
1847      * @param expectedPageTitle the expected title of the page after clicking
1848      */
1849     private void testOnbeforeunloadHandler(final boolean handlerOk, final String expectedPageTitle) throws Exception {
1850         final WebClient webClient = getWebClient();
1851         final MockWebConnection webConnection = new MockWebConnection();
1852         final List<String> collectedConfirms = new ArrayList<>();
1853 
1854         webClient.setOnbeforeunloadHandler(new OnbeforeunloadHandler() {
1855             @Override
1856             public boolean handleEvent(final Page page, final String message) {
1857                 collectedConfirms.add(message);
1858                 return handlerOk;
1859             }
1860         });
1861 
1862         final String expectedMessage = "Any string value here forces a dialog box to appear before closing the window.";
1863         final String firstContent = DOCTYPE_HTML
1864             + "<html><head><title>first</title>\n"
1865             + "<script>\n"
1866             + "  function closeIt(event) {\n"
1867             + "    event.returnValue = '" + expectedMessage + "';\n"
1868             + "  }\n"
1869             + "</script>\n"
1870             + "</head><body onbeforeunload='closeIt(event)'>\n"
1871             + "  <a href='" + URL_SECOND + "'>Second page</a>\n"
1872             + "</body></html>";
1873 
1874         final String secondContent = DOCTYPE_HTML
1875             + "<html><head><title>second</title>\n"
1876             + "</head><body>\n"
1877             + "</body></html>";
1878 
1879         webConnection.setResponse(URL_FIRST, firstContent);
1880         webConnection.setResponse(URL_SECOND, secondContent);
1881         webClient.setWebConnection(webConnection);
1882 
1883         final HtmlPage page = webClient.getPage(URL_FIRST);
1884         final HtmlAnchor anchor = page.getAnchors().get(0);
1885         final HtmlPage secondPage = anchor.click();
1886 
1887         assertEquals(new String[] {expectedMessage}, collectedConfirms);
1888         assertEquals(expectedPageTitle, secondPage.getTitleText());
1889     }
1890 
1891     /**
1892      * @throws Exception if an error occurs
1893      */
1894     @Test
1895     public void srcJavaScript() throws Exception {
1896         final String htmlContent = DOCTYPE_HTML
1897             + "<html><head><title>foo</title></head><body>\n"
1898             + "<script id='ie_ready' src='javascript:void(0)'></script>\n"
1899             + "</body></html>";
1900 
1901         loadPage(htmlContent);
1902     }
1903 
1904     /**
1905      * Regression test for asNormalizedText() which would blow up.
1906      *
1907      * @exception Exception If the test fails
1908      */
1909     @Test
1910     public void asNormalizedText() throws Exception {
1911         final String htmlContent = DOCTYPE_HTML
1912             + "<html><head><title>test</title></head>\n"
1913             + "<body><table>\n"
1914             + "<tr><form><td>a</td></form></tr>\n"
1915             + "</table></body></html>";
1916 
1917         final HtmlPage page = loadPage(htmlContent);
1918         assertEquals("test\na", page.asNormalizedText());
1919     }
1920 
1921     /**
1922      * @throws Exception if the test fails
1923      */
1924     @Test
1925     public void getElementsByTagName() throws Exception {
1926         final String firstContent = DOCTYPE_HTML
1927             + "<html><head><title>First</title></head>\n"
1928             + "<body>\n"
1929             + "<form><input type='button' name='button1' value='pushme'></form>\n"
1930             + "<div>a</div> <div>b</div> <div>c</div>\n"
1931             + "</body></html>";
1932 
1933         final HtmlPage page = loadPage(firstContent);
1934 
1935         NodeList inputs = page.getElementsByTagName("input");
1936         assertEquals(1, inputs.getLength());
1937         assertEquals("button", inputs.item(0).getAttributes().getNamedItem("type").getNodeValue());
1938 
1939         final NodeList divs = page.getElementsByTagName("div");
1940         assertEquals(3, divs.getLength());
1941 
1942         final HtmlDivision newDiv = new HtmlDivision(HtmlDivision.TAG_NAME, page, null);
1943         page.getBody().appendChild(newDiv);
1944         assertEquals(4, divs.getLength());
1945 
1946         // case sensitive
1947         inputs = page.getElementsByTagName("inPUT");
1948         assertEquals(1, inputs.getLength());
1949 
1950         // empty
1951         inputs = page.getElementsByTagName("");
1952         assertEquals(0, inputs.getLength());
1953 
1954         // null
1955         inputs = page.getElementsByTagName(null);
1956         assertEquals(0, inputs.getLength());
1957     }
1958 
1959     /**
1960      * HtmlPage.getReadyState() should give the same information than the document element.
1961      * @see <a href="http://sourceforge.net/p/htmlunit/bugs/402/">402</a>
1962      * @exception Exception If the test fails
1963      */
1964     @Test
1965     public void readyState() throws Exception {
1966         final String htmlContent = DOCTYPE_HTML
1967             + "<html><head><title>test</title></head>\n"
1968             + "<body><table>\n"
1969             + "<tr><form><td>a</td></form></tr>\n"
1970             + "</table></body></html>";
1971 
1972         final HtmlPage page = loadPage(htmlContent);
1973         assertEquals(DomNode.READY_STATE_COMPLETE, page.getReadyState());
1974     }
1975 
1976     /**
1977      * @exception Exception If the test fails
1978      */
1979     @Test
1980     public void cloneNode() throws Exception {
1981         final String html = DOCTYPE_HTML
1982             + "<html>\n"
1983             + "<head><title>foo</title></head>\n"
1984             + "<body>\n"
1985             + "<p>hello world</p>\n"
1986             + "</body></html>";
1987         final HtmlPage page = loadPage(html);
1988         page.getByXPath("//p");
1989         final HtmlPage clonedPage = page.cloneNode(true);
1990         clonedPage.getByXPath("//p");
1991     }
1992 
1993     /**
1994      * @exception Exception If the test fails
1995      */
1996     @Test
1997     public void cloneHtmlPageWithFrame() throws Exception {
1998         final String html = DOCTYPE_HTML
1999                 + "<html>\n"
2000                 + "<head></head><body>\n"
2001                 + "<div id='content'>"
2002                 + "  <iframe name='content' src='frame1.html'></iframe>"
2003                 + "</div>"
2004                 + "</body></html>";
2005 
2006         final String frameContent = DOCTYPE_HTML
2007                 + "<html>\n"
2008                 + "<head></head>\n"
2009                 + "<body>"
2010                 + "  <p>frame1</p>"
2011                 + "</body></html>";
2012 
2013         final MockWebConnection webConnection = getMockWebConnection();
2014         webConnection.setResponse(new URL("http://example/index.html"), html);
2015         webConnection.setResponse(new URL("http://example/frame1.html"), frameContent);
2016 
2017         final WebClient webClient = getWebClientWithMockWebConnection();
2018 
2019         final HtmlPage page = webClient.getPage("http://example/index.html");
2020 
2021         // check frame on page
2022         final List<FrameWindow> frames = page.getFrames();
2023         assertEquals(1, frames.size());
2024         assertEquals("frame1", ((HtmlPage) frames.get(0).getEnclosedPage()).asNormalizedText());
2025 
2026         // clone page with deep false
2027         HtmlPage clonedPage = page.cloneNode(false);
2028 
2029         assertEquals(1, page.getFrames().size());
2030         assertEquals(1, clonedPage.getFrames().size());
2031 
2032         // clone page with deep true
2033         clonedPage = page.cloneNode(true);
2034 
2035         // must be equals 1
2036         assertEquals(1, page.getFrames().size());
2037         assertEquals(1, clonedPage.getFrames().size());
2038 
2039         // clone page with deep true
2040         page.executeJavaScript("document.cloneNode(true)");
2041 
2042         // must be equals 1
2043         assertEquals(1, page.getFrames().size());
2044         assertEquals(1, clonedPage.getFrames().size());
2045     }
2046 
2047     /**
2048      * @exception Exception If the test fails
2049      */
2050     @Test
2051     public void addAutoCloseable() throws Exception {
2052         final String html = "";
2053         final HtmlPage page = loadPage(html);
2054         page.addAutoCloseable(new AutoCloseable() {
2055             @Override
2056             public void close() throws Exception {
2057                 page.addAutoCloseable(new AutoCloseable() {
2058                     @Override
2059                     public void close() throws Exception {
2060                         throw new NullPointerException("close failed");
2061                     }
2062                 });
2063             }
2064         });
2065         page.cleanUp();
2066     }
2067 
2068     /**
2069      * @exception Exception If the test fails
2070      */
2071     @Test
2072     public void addAutoCloseableNull() throws Exception {
2073         final String html = "";
2074         final HtmlPage page = loadPage(html);
2075         page.addAutoCloseable(null);
2076         page.cleanUp();
2077     }
2078 
2079     /**
2080      * @exception Exception If the test fails
2081      */
2082     @Test
2083     public void getBaseUrl() throws Exception {
2084         final String html = DOCTYPE_HTML
2085                 + "<html>\n"
2086                 + "<head></head>\n"
2087                 + "<body>body</body>\n"
2088                 + "</html>";
2089 
2090         HtmlPage page = loadPage(getBrowserVersion(), html, null, URL_FIRST);
2091         assertEquals(URL_FIRST.toExternalForm(), page.getBaseURL().toExternalForm());
2092 
2093         // see also org.htmlunit.javascript.host.html.HTMLDocumentTest.baseURI_noBaseTag()
2094         String path = "details/abc";
2095         page = loadPage(getBrowserVersion(), html, null, new URL(URL_FIRST.toString() + path));
2096         assertEquals(URL_FIRST.toExternalForm() + path, page.getBaseURL().toExternalForm());
2097 
2098         path = "details/abc?x=y&z=z";
2099         page = loadPage(getBrowserVersion(), html, null, new URL(URL_FIRST.toString() + path));
2100         assertEquals(URL_FIRST.toExternalForm() + path, page.getBaseURL().toExternalForm());
2101 
2102         path = "details/abc;jsessionid=42?x=y&z=z";
2103         page = loadPage(getBrowserVersion(), html, null, new URL(URL_FIRST.toString() + path));
2104         assertEquals(URL_FIRST.toExternalForm() + path, page.getBaseURL().toExternalForm());
2105     }
2106 }