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