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 java.util.ArrayList;
18  import java.util.List;
19  
20  import org.htmlunit.ElementNotFoundException;
21  import org.htmlunit.SimpleWebTestCase;
22  import org.htmlunit.WebClient;
23  import org.htmlunit.html.DomNode.DescendantHtmlElementsIterator;
24  import org.htmlunit.junit.annotation.Alerts;
25  import org.htmlunit.util.MimeType;
26  import org.htmlunit.xml.XmlPage;
27  import org.junit.jupiter.api.Assertions;
28  import org.junit.jupiter.api.Test;
29  import org.xml.sax.helpers.AttributesImpl;
30  
31  /**
32   * Tests for {@link DomNode}.
33   *
34   * @author Chris Erskine
35   * @author Ahmed Ashour
36   * @author Ronald Brill
37   */
38  public class DomNodeTest extends SimpleWebTestCase {
39  
40      /**
41       * Test hasAttributes() on an element with attributes.
42       * @throws Exception if the test fails
43       */
44      @Test
45      public void elementHasAttributesWith() throws Exception {
46          final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
47          final HtmlPage page = loadPage(content);
48  
49          final DomNode node = page.getElementById("tag");
50          assertTrue("Element should have attribute", node.hasAttributes());
51      }
52  
53      /**
54       * Test hasAttributes() on an element with no attributes.
55       * @throws Exception if the test fails
56       */
57      @Test
58      public void elementHasAttributesNone() throws Exception {
59          final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
60          final HtmlPage page = loadPage(content);
61  
62          final DomNode node = page.getElementById("tag");
63          final DomNode parent = node.getParentNode();
64          assertFalse("Element should not have attribute", parent.hasAttributes());
65      }
66  
67      /**
68       * Test hasAttributes on a node that is not defined to have attributes.
69       * @throws Exception if the test fails
70       */
71      @Test
72      public void nonElementHasAttributes() throws Exception {
73          final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
74          final HtmlPage page = loadPage(content);
75  
76          final DomNode node = page.getElementById("tag");
77          final DomNode child = node.getFirstChild();
78          assertFalse("Text should not have attribute", child.hasAttributes());
79      }
80  
81      /**
82       * Test getPrefix on a node that is not defined to have a prefix.
83       * @throws Exception if the test fails
84       */
85      @Test
86      public void nonElementGetPrefix() throws Exception {
87          final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
88          final HtmlPage page = loadPage(content);
89  
90          final DomNode node = page.getElementById("tag");
91          final DomNode child = node.getFirstChild();
92          assertEquals("Text should not have a prefix", null, child.getPrefix());
93      }
94  
95      /**
96       * Test getNamespaceURI on a node that is not defined to have a namespace.
97       * @throws Exception if the test fails
98       */
99      @Test
100     public void nonElementGetNamespaceURI() throws Exception {
101         final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
102         final HtmlPage page = loadPage(content);
103 
104         final DomNode node = page.getElementById("tag");
105         final DomNode child = node.getFirstChild();
106         assertEquals("Text should not have a prefix", null, child.getNamespaceURI());
107     }
108 
109     /**
110      * Test getLocalName on a node that is not defined to have a local name.
111      * @throws Exception if the test fails
112      */
113     @Test
114     public void nonElementGetLocalName() throws Exception {
115         final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
116         final HtmlPage page = loadPage(content);
117 
118         final DomNode node = page.getElementById("tag");
119         final DomNode child = node.getFirstChild();
120         assertEquals("Text should not have a prefix", null, child.getLocalName());
121     }
122 
123     /**
124      * Test setPrefix on a node that is not defined to have a prefix.
125      * @throws Exception if the test fails
126      */
127     @Test
128     public void nonElementSetPrefix() throws Exception {
129         final String content = DOCTYPE_HTML + "<html><head></head><body id='tag'>text</body></html>";
130         final HtmlPage page = loadPage(content);
131 
132         final DomNode node = page.getElementById("tag");
133         final DomNode child = node.getFirstChild();
134         child.setPrefix("bar"); // This does nothing.
135         assertEquals("Text should not have a prefix", null, child.getPrefix());
136     }
137 
138     /**
139      * @throws Exception if the test fails
140      */
141     @Test
142     public void removeAllChildren() throws Exception {
143         final String content = DOCTYPE_HTML
144             + "<html><head></head><body>\n"
145             + "<p id='tag'><table>\n"
146             + "<tr><td>row 1</td></tr>\n"
147             + "<tr><td>row 2</td></tr>\n"
148             + "</table></p></body></html>";
149         final HtmlPage page = loadPage(content);
150 
151         final DomNode node = page.getElementById("tag");
152         node.removeAllChildren();
153         assertEquals("Did not remove all nodes", null, node.getFirstChild());
154     }
155 
156     /**
157      * @throws Exception if the test fails
158      */
159     @Test
160     public void replace() throws Exception {
161         final String content = DOCTYPE_HTML
162             + "<html><head></head><body>\n"
163             + "<br><div id='tag'></div><br><div id='tag2'/></body></html>";
164         final HtmlPage page = loadPage(content);
165 
166         final DomNode node = page.getElementById("tag");
167 
168         final DomNode previousSibling = node.getPreviousSibling();
169         final DomNode nextSibling = node.getNextSibling();
170         final DomNode parent = node.getParentNode();
171 
172         // position among parent's children
173         final int position = readPositionAmongParentChildren(node);
174 
175         final DomNode newNode = new DomText(page, "test");
176         node.replace(newNode);
177         assertSame("previous sibling", previousSibling, newNode.getPreviousSibling());
178         assertSame("next sibling", nextSibling, newNode.getNextSibling());
179         assertSame("parent", parent, newNode.getParentNode());
180         assertSame(newNode, previousSibling.getNextSibling());
181         assertSame(newNode, nextSibling.getPreviousSibling());
182         assertEquals(position, readPositionAmongParentChildren(newNode));
183 
184         final AttributesImpl attributes = new AttributesImpl();
185         attributes.addAttribute(null, "id", "id", null, "tag2"); // with the same id as the node to replace
186         final DomNode node2 = page.getHtmlElementById("tag2");
187         assertEquals("div", node2.getNodeName());
188 
189         final DomNode node3 = page.getWebClient().getPageCreator().getHtmlParser()
190                                 .getFactory(HtmlSpan.TAG_NAME)
191                                 .createElement(page, HtmlSpan.TAG_NAME, attributes);
192         node2.replace(node3);
193         assertEquals("span", page.getHtmlElementById("tag2").getTagName());
194     }
195 
196     /**
197      * @throws Exception if the test fails
198      */
199     @Test
200     public void getNewNodeById() throws Exception {
201         final String content = DOCTYPE_HTML
202             + "<html><head></head><body>\n"
203             + "<br><div id='tag'/></body></html>";
204         final HtmlPage page = loadPage(content);
205 
206         final DomNode node = page.getElementById("tag");
207 
208         final AttributesImpl attributes = new AttributesImpl();
209         attributes.addAttribute(null, "id", "id", null, "newElt");
210         final DomNode newNode = page.getWebClient().getPageCreator().getHtmlParser()
211                                     .getFactory(HtmlDivision.TAG_NAME)
212                                     .createElement(page, HtmlDivision.TAG_NAME, attributes);
213         try {
214             page.getHtmlElementById("newElt");
215             Assertions.fail("Element should not exist yet");
216         }
217         catch (final ElementNotFoundException e) {
218             // nothing to do, it's ok
219         }
220 
221         node.replace(newNode);
222 
223         page.getHtmlElementById("newElt");
224         try {
225             page.getHtmlElementById("tag");
226             Assertions.fail("Element should not exist anymore");
227         }
228         catch (final ElementNotFoundException e) {
229             // nothing to do, it's ok
230         }
231 
232         newNode.insertBefore(node);
233         page.getHtmlElementById("tag");
234     }
235 
236     /**
237      * @throws Exception if the test fails
238      */
239     @Test
240     public void appendChild() throws Exception {
241         final String content = DOCTYPE_HTML
242             + "<html><head></head><body>\n"
243             + "<br><div><div id='tag'></div></div><br></body></html>";
244         final HtmlPage page = loadPage(content);
245 
246         final DomNode node = page.getElementById("tag");
247 
248         final DomNode parent = node.getParentNode();
249 
250         // position among parent's children
251         final int position = readPositionAmongParentChildren(node);
252 
253         final DomNode newNode = new DomText(page, "test");
254         parent.appendChild(newNode);
255         assertSame("new node previous sibling", node, newNode.getPreviousSibling());
256         assertSame("new node next sibling", null, newNode.getNextSibling());
257         assertSame("next sibling", newNode, node.getNextSibling());
258         assertSame("parent", parent, newNode.getParentNode());
259         assertEquals(position + 1, readPositionAmongParentChildren(newNode));
260 
261         final DomNode newNode2 = new DomText(page, "test2");
262         parent.appendChild(newNode2);
263         page.getHtmlElementById("tag");
264     }
265 
266     /**
267      * @throws Exception if the test fails
268      */
269     @Test
270     public void insertBefore() throws Exception {
271         final String content = DOCTYPE_HTML
272             + "<html><head></head><body>\n"
273             + "<br><div id='tag'></div><br></body></html>";
274         final HtmlPage page = loadPage(content);
275 
276         final DomNode node = page.getElementById("tag");
277 
278         final DomNode previousSibling = node.getPreviousSibling();
279         final DomNode nextSibling = node.getNextSibling();
280         final DomNode parent = node.getParentNode();
281 
282         // position among parent's children
283         final int position = readPositionAmongParentChildren(node);
284 
285         final DomNode newNode = new DomText(page, "test");
286         node.insertBefore(newNode);
287         assertSame("new node previous sibling", previousSibling, newNode.getPreviousSibling());
288         assertSame("previous sibling", newNode, node.getPreviousSibling());
289         assertSame("new node next sibling", node, newNode.getNextSibling());
290         assertSame("next sibling", nextSibling, node.getNextSibling());
291         assertSame("parent", parent, newNode.getParentNode());
292         assertSame(newNode, previousSibling.getNextSibling());
293         assertSame(node, nextSibling.getPreviousSibling());
294         assertEquals(position, readPositionAmongParentChildren(newNode));
295     }
296 
297     /**
298      * Reads the position of the node among the children of its parent
299      * @param node the node to look at
300      * @return the position
301      */
302     private static int readPositionAmongParentChildren(final DomNode node) {
303         int i = 0;
304         for (final DomNode child : node.getParentNode().getChildren()) {
305             if (child == node) {
306                 return i;
307             }
308             i++;
309         }
310 
311         return -1;
312     }
313 
314     /**
315      * @throws Exception if the test fails
316      */
317     @Test
318     public void getByXPath() throws Exception {
319         final String htmlContent = DOCTYPE_HTML
320             + "<html>\n"
321             + "  <head>\n"
322             + "    <title>my title</title>\n"
323             + "  </head>"
324             + "  <body>\n"
325             + "    <div id='d1'>\n"
326             + "      <ul>\n"
327             + "        <li>foo 1</li>\n"
328             + "        <li>foo 2</li>\n"
329             + "      </ul>\n"
330             + "    </div>\n"
331             + "    <div><span>bla</span></div>\n"
332             + "</body>\n"
333             + "</html>";
334         final HtmlPage page = loadPage(htmlContent);
335 
336         final List<?> results = page.getByXPath("//title");
337         assertEquals(1, results.size());
338         final HtmlTitle title = (HtmlTitle) results.get(0);
339         assertEquals("my title", title.asNormalizedText());
340 
341         final HtmlHead head = (HtmlHead) title.getParentNode();
342         assertEquals(results, head.getByXPath("//title"));
343         assertEquals(results, head.getByXPath("./title"));
344         assertTrue(head.getByXPath("/title").isEmpty());
345         assertEquals(results, head.getByXPath("title"));
346 
347         final HtmlElement div = page.getFirstByXPath("//div");
348         assertSame(div, page.getHtmlElementById("d1"));
349         final List<?> lis = div.getByXPath("ul/li");
350         assertEquals(2, lis.size());
351         assertEquals(lis, page.getByXPath("//ul/li"));
352 
353         assertEquals(2, div.<Number>getFirstByXPath("count(//li)").intValue());
354     }
355 
356     /**
357      * @throws Exception if the test fails
358      */
359     @Test
360     public void getByXPathSelectedNode() throws Exception {
361         final String htmlContent = DOCTYPE_HTML
362             + "<html>\n"
363             + "  <head>\n"
364             + "    <title>my title</title>\n"
365             + "  </head>"
366             + "  <body>\n"
367             + "    <h1 id='outer_h1'>HtmlUnit</h1>\n"
368             + "    <div id='d1'>\n"
369             + "      <h1 id='h1'>HtmlUnit</h1>\n"
370             + "    </div>\n"
371             + "</body>\n"
372             + "</html>";
373         final HtmlPage page = loadPage(htmlContent);
374 
375         final HtmlDivision divNode = (HtmlDivision) page.getElementById("d1");
376 
377         assertEquals(page.getElementById("h1"), divNode.getByXPath(".//h1").get(0));
378         assertEquals(page.getElementById("outer_h1"), divNode.getByXPath("//h1").get(0));
379     }
380 
381     /**
382      * Regression test for bug #1149: xmlns value has to be trimmed.
383      * @throws Exception if the test fails
384      */
385     @Test
386     public void getByXPath_trim_namespace() throws Exception {
387         final String html = DOCTYPE_HTML
388             + "<html xmlns=' http://www.w3.org/1999/xhtml'>\n"
389             + "<body>\n"
390             + "<div><span>bla</span></div>\n"
391             + "</body></html>";
392         final HtmlPage page = loadPage(html);
393 
394         final List<?> results = page.getByXPath("//div");
395         assertEquals(1, results.size());
396     }
397 
398     /**
399      * Make sure display: none has no effect for xpath expressions.
400      * @throws Exception if the test fails
401      */
402     @Test
403     public void getFirstByXPathDisplayNone() throws Exception {
404         final String htmlContent = DOCTYPE_HTML
405             + "<html>\n"
406             + "  <head>\n"
407             + "    <title>my title</title>\n"
408             + "  </head>"
409             + "  <body>\n"
410             + "    <div id='d1'><span style='display: none;'>bla</span></div>\n"
411             + "</body>\n"
412             + "</html>";
413         final HtmlPage page = loadPage(htmlContent);
414 
415         HtmlElement span = page.getFirstByXPath("//div/span");
416         assertEquals("<span style=\"display: none;\">bla</span>", span.asXml());
417         assertFalse(span.isDisplayed());
418 
419         span = page.getFirstByXPath("//span[text()=\"bla\"]");
420         assertEquals("<span style=\"display: none;\">bla</span>", span.asXml());
421         assertFalse(span.isDisplayed());
422     }
423 
424     /**
425      * @throws Exception if the test fails
426      */
427     @Test
428     public void getFirstByXPath() throws Exception {
429         final String htmlContent = DOCTYPE_HTML
430             + "<html><head><title>my title</title></head><body>\n"
431             + "<div id='d1'><ul><li>foo 1</li><li>foo 2</li></ul></div>\n"
432             + "<div><span>bla</span></div>\n"
433             + "</body></html>";
434         final HtmlPage page = loadPage(htmlContent);
435 
436         final HtmlTitle title = page.getFirstByXPath("//title");
437         assertEquals("my title", title.asNormalizedText());
438 
439         final HtmlHead head = (HtmlHead) title.getParentNode();
440         assertSame(title, head.getFirstByXPath("//title"));
441         assertSame(title, head.getFirstByXPath("./title"));
442         assertNull(head.getFirstByXPath("/title"));
443         assertSame(title, head.getFirstByXPath("title"));
444 
445         final HtmlElement div = page.getFirstByXPath("//div");
446         assertSame(div, page.getHtmlElementById("d1"));
447         final HtmlListItem listItem = (HtmlListItem) div.getFirstByXPath("ul/li");
448         assertSame(listItem, page.getFirstByXPath("//ul/li"));
449 
450         assertEquals(2, ((Number) div.getFirstByXPath("count(//li)")).intValue());
451     }
452 
453     /**
454      * Verifies that {@link DomNode#getHtmlElementDescendants()} returns descendant elements in the correct order.
455      * @throws Exception if an error occurs
456      */
457     @Test
458     public void getHtmlElementDescendantsOrder() throws Exception {
459         final String html = DOCTYPE_HTML
460             + "<html><body id='0'>\n"
461             + "<span id='I'><span id='I.1'><span id='I.1.a'/><span id='I.1.b'/><span id='I.1.c'/></span>\n"
462             + "<span id='I.2'><span id='I.2.a'/></span></span>\n"
463             + "<span id='II'/>\n"
464             + "<span id='III'><span id='III.1'><span id='III.1.a'/></span></span>\n"
465             + "</body></html>";
466         final HtmlPage page = loadPage(html);
467         final DescendantHtmlElementsIterator iterator = (DescendantHtmlElementsIterator)
468             page.getDocumentElement().getHtmlElementDescendants().iterator();
469         assertEquals("", iterator.nextNode().getId());
470         assertEquals("0", iterator.nextNode().getId());
471         assertEquals("I", iterator.nextNode().getId());
472         assertEquals("I.1", iterator.nextNode().getId());
473         assertEquals("I.1.a", iterator.nextNode().getId());
474         assertEquals("I.1.b", iterator.nextNode().getId());
475         assertEquals("I.1.c", iterator.nextNode().getId());
476         assertEquals("I.2", iterator.nextNode().getId());
477         assertEquals("I.2.a", iterator.nextNode().getId());
478         assertEquals("II", iterator.nextNode().getId());
479         assertEquals("III", iterator.nextNode().getId());
480         assertEquals("III.1", iterator.nextNode().getId());
481         assertEquals("III.1.a", iterator.nextNode().getId());
482         assertFalse(iterator.hasNext());
483     }
484 
485     /**
486      * @throws Exception if an error occurs
487      */
488     @Test
489     public void getDescendants_remove() throws Exception {
490         final String html = DOCTYPE_HTML
491             + "<html><body id='body'>\n"
492             + "<div id='a'>a<div id='b'>b</div>a<div id='c'>c</div>a</div><div id='d'>d</div>\n"
493             + "</body></html>";
494         final HtmlPage page = loadPage(html);
495         assertEquals("abacad", page.asNormalizedText().replaceAll("\\s", ""));
496         final DescendantHtmlElementsIterator iterator = (DescendantHtmlElementsIterator)
497             page.getDocumentElement().getHtmlElementDescendants().iterator();
498         assertEquals("", iterator.nextNode().getId());
499         assertEquals("body", iterator.nextNode().getId());
500         assertEquals("a", iterator.nextNode().getId());
501         iterator.remove();
502         assertEquals("d", iterator.nextNode().getId());
503         assertFalse(iterator.hasNext());
504         assertEquals("d", page.asNormalizedText().replaceAll("\\s", ""));
505     }
506 
507     static class DomChangeListenerTestImpl implements DomChangeListener {
508         private final List<String> collectedValues_ = new ArrayList<>();
509         @Override
510         public void nodeAdded(final DomChangeEvent event) {
511             collectedValues_.add("nodeAdded: " + event.getParentNode().getNodeName() + ','
512                     + event.getChangedNode().getNodeName());
513         }
514         @Override
515         public void nodeDeleted(final DomChangeEvent event) {
516             collectedValues_.add("nodeDeleted: " + event.getParentNode().getNodeName() + ','
517                     + event.getChangedNode().getNodeName());
518         }
519         List<String> getCollectedValues() {
520             return collectedValues_;
521         }
522     }
523 
524     /**
525      * @throws Exception if the test fails
526      */
527     @Test
528     public void domChangeListenerTestImpl_insertBefore() throws Exception {
529         final String htmlContent = DOCTYPE_HTML
530             + "<html><head><title>foo</title>\n"
531             + "<script>\n"
532             + "  function clickMe() {\n"
533             + "    var p1 = document.getElementById('p1');\n"
534             + "    var div = document.createElement('DIV');\n"
535             + "    p1.insertBefore(div, null);\n"
536             + "  }\n"
537             + "</script>\n"
538             + "</head>\n"
539             + "<body>\n"
540             + "<p id='p1' title='myTitle'></p>\n"
541             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
542             + "</body></html>";
543 
544         final String[] expectedValues = {"nodeAdded: p,div", "nodeAdded: p,div"};
545         final HtmlPage page = loadPage(htmlContent);
546 
547         final HtmlElement p1 = page.getHtmlElementById("p1");
548         final DomChangeListenerTestImpl listenerImpl = new DomChangeListenerTestImpl();
549         p1.addDomChangeListener(listenerImpl);
550         page.addDomChangeListener(listenerImpl);
551         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
552 
553         myButton.click();
554         assertEquals(expectedValues, listenerImpl.getCollectedValues());
555     }
556 
557     /**
558      * @throws Exception if the test fails
559      */
560     @Test
561     public void domChangeListenerTestImpl_appendChild() throws Exception {
562         final String htmlContent = DOCTYPE_HTML
563             + "<html><head><title>foo</title>\n"
564             + "<script>\n"
565             + "  function clickMe() {\n"
566             + "    var p1 = document.getElementById('p1');\n"
567             + "    var div = document.createElement('DIV');\n"
568             + "    p1.appendChild(div);\n"
569             + "  }\n"
570             + "</script>\n"
571             + "</head>\n"
572             + "<body>\n"
573             + "<p id='p1' title='myTitle'></p>\n"
574             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
575             + "</body></html>";
576 
577         final String[] expectedValues = {"nodeAdded: p,div", "nodeAdded: p,div"};
578         final HtmlPage page = loadPage(htmlContent);
579         final HtmlElement p1 = page.getHtmlElementById("p1");
580         final DomChangeListenerTestImpl listenerImpl = new DomChangeListenerTestImpl();
581         p1.addDomChangeListener(listenerImpl);
582         page.addDomChangeListener(listenerImpl);
583         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
584 
585         myButton.click();
586         assertEquals(expectedValues, listenerImpl.getCollectedValues());
587     }
588 
589     /**
590      * @throws Exception if the test fails
591      */
592     @Test
593     @Alerts({"nodeDeleted: div,p", "nodeDeleted: div,p"})
594     public void domChangeListenerTestImpl_removeChild() throws Exception {
595         final String htmlContent = DOCTYPE_HTML
596             + "<html><head><title>foo</title>\n"
597             + "<script>\n"
598             + "  function clickMe() {\n"
599             + "    var p1 = document.getElementById('p1');\n"
600             + "    var div = document.getElementById('myDiv');\n"
601             + "    div.removeChild(p1);\n"
602             + "  }\n"
603             + "</script>\n"
604             + "</head>\n"
605             + "<body>\n"
606             + "<div id='myDiv'><p id='p1' title='myTitle'></p></div>\n"
607             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
608             + "</body></html>";
609 
610         final HtmlPage page = loadPage(htmlContent);
611         final HtmlElement p1 = page.getHtmlElementById("p1");
612         final DomChangeListenerTestImpl listenerImpl = new DomChangeListenerTestImpl();
613         p1.addDomChangeListener(listenerImpl);
614         page.addDomChangeListener(listenerImpl);
615         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
616 
617         myButton.click();
618         assertEquals(getExpectedAlerts(), listenerImpl.getCollectedValues());
619     }
620 
621     /**
622      * @throws Exception if the test fails
623      */
624     @Test
625     public void domChangeListenerRegisterNewListener() throws Exception {
626         final String htmlContent = DOCTYPE_HTML
627             + "<html><head><title>foo</title>\n"
628             + "<script>\n"
629             + "  function clickMe() {\n"
630             + "    var p1 = document.getElementById('p1');\n"
631             + "    var div = document.createElement('DIV');\n"
632             + "    p1.appendChild(div);\n"
633             + "  }\n"
634             + "</script>\n"
635             + "</head>\n"
636             + "<body>\n"
637             + "<p id='p1' title='myTitle'></p>\n"
638             + "<input id='myButton' type='button' onclick='clickMe()'>\n"
639             + "</body></html>";
640 
641         final HtmlPage page = loadPage(htmlContent);
642 
643         final List<String> l = new ArrayList<>();
644         final DomChangeListener listener2 = new DomChangeListenerTestImpl() {
645             @Override
646             public void nodeAdded(final DomChangeEvent event) {
647                 l.add("in listener 2");
648             }
649         };
650         final DomChangeListener listener1 = new DomChangeListenerTestImpl() {
651             @Override
652             public void nodeAdded(final DomChangeEvent event) {
653                 l.add("in listener 1");
654                 page.addDomChangeListener(listener2);
655             }
656         };
657 
658         page.addDomChangeListener(listener1);
659 
660         final HtmlButtonInput myButton = page.getHtmlElementById("myButton");
661         myButton.click();
662 
663         final String[] expectedValues = {"in listener 1"};
664         assertEquals(expectedValues, l);
665         l.clear();
666 
667         myButton.click();
668         final String[] expectedValues2 = {"in listener 1", "in listener 2"};
669         assertEquals(expectedValues2, l);
670     }
671 
672     /**
673      * @throws Exception if the test fails
674      */
675     @Test
676     public void getByXPath_XML() throws Exception {
677         final String xml
678             = "<books>\n"
679             + "  <book>\n"
680             + "    <title>Immortality</title>\n"
681             + "    <author>John Smith</author>\n"
682             + "  </book>\n"
683             + "</books>";
684 
685         getMockWebConnection().setResponse(URL_FIRST, xml, MimeType.TEXT_XML);
686         final WebClient client = getWebClientWithMockWebConnection();
687         final XmlPage page = (XmlPage) client.getPage(URL_FIRST);
688 
689         final List<?> results = page.getByXPath("//title");
690         assertEquals(1, results.size());
691     }
692 
693     /**
694      * @throws Exception if the test fails
695      */
696     @Test
697     public void getCanonicalXPath() throws Exception {
698         final String content = DOCTYPE_HTML + "<html><head></head><body><div id='div1'/><div id='div2'/></body></html>";
699         final HtmlPage page = loadPage(content);
700         for (final HtmlElement element : page.getHtmlElementDescendants()) {
701             final List<?> foundElements = page.getByXPath(element.getCanonicalXPath());
702             assertEquals(1, foundElements.size());
703             assertSame(element, foundElements.get(0));
704         }
705     }
706 
707     /**
708      * @throws Exception if the test fails
709      */
710     @Test
711     public void getChildNodes_remove() throws Exception {
712         final String content = DOCTYPE_HTML + "<html><body id='b'><div id='d1'></div><div id='d2'></div></body></html>";
713         final HtmlPage page = loadPage(content);
714         final DomNodeList<DomNode> children = page.getElementById("b").getChildNodes();
715         assertEquals(2, children.getLength());
716         page.getElementById("d1").remove();
717         assertEquals(1, children.getLength());
718         page.getElementById("d2").remove();
719         assertEquals(0, children.getLength());
720         assertNull(children.get(0));
721     }
722 
723     /**
724      * @throws Exception if an error occurs
725      */
726     @Test
727     public void serialization() throws Exception {
728         final String html = DOCTYPE_HTML + "<html><head></head><body></body></html>";
729         final DomChangeListenerTestImpl listener = new DomChangeListenerTestImpl();
730         HtmlPage page = loadPage(html);
731         page.addDomChangeListener(listener);
732         page = clone(page);
733         page.removeDomChangeListener(listener);
734     }
735 
736     /**
737      * @throws Exception if the test fails
738      */
739     @Test
740     public void isDisplayed() throws Exception {
741         final String html = DOCTYPE_HTML
742             + "<html><head>\n"
743             + "<style>\n"
744             + "#d2 { display: none; }\n"
745             + "#d3 { visibility: hidden; }\n"
746             + "#d4 { display: block !important; }\n"
747             + "</style>\n"
748             + "<div id='d1'>hello</div>\n"
749             + "<div id='d2'>world</div>\n"
750             + "<div id='d3'>again</div>\n"
751             + "<div id='d4' style='display: none' >important</div>\n"
752             + "</body></html>";
753 
754         final HtmlPage page = loadPage(html);
755         assertTrue(page.getElementById("d1").isDisplayed());
756         assertFalse(page.getElementById("d2").isDisplayed());
757         assertFalse(page.getElementById("d3").isDisplayed());
758         assertTrue(page.getElementById("d4").isDisplayed());
759 
760         getWebClient().getOptions().setCssEnabled(false);
761         assertTrue(page.getElementById("d1").isDisplayed());
762         assertTrue(page.getElementById("d2").isDisplayed());
763         assertTrue(page.getElementById("d3").isDisplayed());
764     }
765 
766     /**
767      * @throws Exception if the test fails
768      */
769     @Test
770     public void isDisplayedMouseOver() throws Exception {
771         final String html = DOCTYPE_HTML
772             + "<html><head>\n"
773             + "<style>\n"
774             + "#d2:hover { display: none; }\n"
775             + "#d3:hover { visibility: hidden; }\n"
776             + "</style>\n"
777             + "<div id='d1'>hello</div>\n"
778             + "<div id='d2'>world</div>\n"
779             + "<div id='d3'>again</div>\n"
780             + "<div id='d4' style='display: none' >important</div>\n"
781             + "</body></html>";
782 
783         final HtmlPage page = loadPage(html);
784         assertTrue(page.getElementById("d1").isDisplayed());
785 
786         HtmlElement elem = page.getHtmlElementById("d2");
787         assertTrue(elem.isDisplayed());
788         elem.mouseOver();
789         assertFalse(elem.isDisplayed());
790         elem.mouseOut();
791         assertTrue(elem.isDisplayed());
792 
793         elem = page.getHtmlElementById("d3");
794         assertTrue(elem.isDisplayed());
795         elem.mouseOver();
796         assertFalse(elem.isDisplayed());
797         elem.mouseOut();
798         assertTrue(elem.isDisplayed());
799     }
800 
801     /**
802      * @throws Exception if the test fails
803      */
804     @Test
805     public void isDisplayedMouseOverParent() throws Exception {
806         final String html = DOCTYPE_HTML
807             + "<html><head>\n"
808             + "<style>\n"
809             + "#d1:hover { display: none; }\n"
810             + "#d2:hover { visibility: hidden; }\n"
811             + "</style>\n"
812             + "<div id='d1'><div id='d1-1'>hello</div></div>\n"
813             + "<div id='d2'><div id='d2-1'>world</div></div>\n"
814             + "</body></html>";
815 
816         final HtmlPage page = loadPage(html);
817         HtmlElement elem = page.getHtmlElementById("d1");
818         assertTrue(elem.isDisplayed());
819         page.getHtmlElementById("d1-1").mouseOver();
820         assertFalse(elem.isDisplayed());
821         page.getHtmlElementById("d1-1").mouseOut();
822         assertTrue(elem.isDisplayed());
823 
824         elem = page.getHtmlElementById("d2");
825         assertTrue(elem.isDisplayed());
826         page.getHtmlElementById("d2-1").mouseOver();
827         assertFalse(elem.isDisplayed());
828         page.getHtmlElementById("d2-1").mouseOut();
829         assertTrue(elem.isDisplayed());
830     }
831 
832     /**
833      * @throws Exception if the test fails
834      */
835     @Test
836     public void isDisplayedMousePath() throws Exception {
837         final String html = DOCTYPE_HTML
838             + "<html><head>\n"
839             + "<style>\n"
840             + "#d1:hover #d2 { display: none; }\n"
841             + "</style>\n"
842             + "<div id='d1'>hello<div id='d2'>world</div></div>\n"
843             + "</body></html>";
844 
845         final HtmlPage page = loadPage(html);
846         final HtmlElement elem1 = page.getHtmlElementById("d1");
847         final HtmlElement elem2 = page.getHtmlElementById("d2");
848 
849         assertTrue(elem1.isDisplayed());
850         assertTrue(elem2.isDisplayed());
851 
852         elem1.mouseOver();
853         assertTrue(elem1.isDisplayed());
854         assertFalse(elem2.isDisplayed());
855 
856         elem1.mouseOut();
857         assertTrue(elem1.isDisplayed());
858         assertTrue(elem2.isDisplayed());
859     }
860 }