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.javascript.host;
16  
17  import static java.nio.charset.StandardCharsets.UTF_16LE;
18  
19  import java.io.IOException;
20  import java.nio.ByteBuffer;
21  import java.util.Set;
22  import java.util.concurrent.CopyOnWriteArraySet;
23  
24  import org.eclipse.jetty.websocket.api.Session;
25  import org.eclipse.jetty.websocket.api.WebSocketAdapter;
26  import org.eclipse.jetty.websocket.server.WebSocketHandler;
27  import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
28  import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
29  import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
30  import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
31  import org.htmlunit.HttpHeader;
32  import org.htmlunit.WebDriverTestCase;
33  import org.htmlunit.junit.BrowserRunner;
34  import org.htmlunit.junit.annotation.Alerts;
35  import org.htmlunit.junit.annotation.HtmlUnitNYI;
36  import org.junit.After;
37  import org.junit.Test;
38  import org.junit.runner.RunWith;
39  import org.openqa.selenium.By;
40  import org.openqa.selenium.WebDriver;
41  import org.openqa.selenium.WebElement;
42  
43  /**
44   * Tests for {@link WebSocket}.
45   *
46   * @author Ahmed Ashour
47   * @author Ronald Brill
48   * @author Madis Pärn
49   */
50  @RunWith(BrowserRunner.class)
51  public class WebSocketTest extends WebDriverTestCase {
52  
53      /**
54       * @throws Exception if the test fails
55       */
56      @Test
57      @Alerts({"§§URL§§", "", "blob"})
58      public void initialNoServerAvailable() throws Exception {
59          final String html = DOCTYPE_HTML
60              + "<html>\n"
61              + "<head>\n"
62              + "<script>\n"
63              + LOG_TITLE_FUNCTION
64              + "  function test() {\n"
65              + "    var location = 'ws://localhost:" + PORT2 + "/';\n"
66              + "    var ws = new WebSocket(location);\n"
67              + "    log(ws.url);\n"
68              + "    log(ws.protocol);\n"
69              // this makes our test instable because the real connect is
70              // done by an executor and maybe already finished
71              // + "    log(ws.readyState);\n"
72              + "    log(ws.binaryType);\n"
73              + "  }\n"
74              + "</script>\n"
75              + "</head>\n"
76              + "<body onload='test()'>\n"
77              + "</body></html>";
78  
79          expandExpectedAlertsVariables("ws://localhost:" + PORT2 + "/");
80          final WebDriver driver = loadPage2(html);
81          verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
82      }
83  
84      /**
85       * @throws Exception if the test fails
86       */
87      @Test
88      @Alerts({"[object WebSocket]", "§§URL§§"})
89      public void earlyConstruction() throws Exception {
90          final String html = DOCTYPE_HTML
91              + "<html><head><script>\n"
92              + LOG_TITLE_FUNCTION
93              + "  function test() {\n"
94              + "    var location = 'ws://localhost:" + PORT + "/';\n"
95              + "    var ws = new WebSocket(location);\n"
96              + "    log(ws);\n"
97              + "    log(ws.url);\n"
98              + "  }\n"
99              + "  test();\n"
100             + "</script>\n"
101             + "</head>\n"
102             + "<body>\n"
103             + "</body></html>";
104 
105         expandExpectedAlertsVariables("ws://localhost:" + PORT + "/");
106         final WebDriver driver = loadPage2(html);
107         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
108     }
109 
110     /**
111      * @throws Exception if the test fails
112      */
113     @Test
114     @Alerts(DEFAULT = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
115                        "ws://localhost:22222/", "ws://localhost:22222/", "exception invalid"},
116             FF = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
117                   "exception empty", "ws://localhost:22222/", "exception invalid"},
118             FF_ESR = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
119                       "exception empty", "ws://localhost:22222/", "exception invalid"})
120     @HtmlUnitNYI(
121             CHROME = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
122                       "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"},
123             EDGE = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
124                     "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"},
125             FF = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
126                   "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"},
127             FF_ESR = {"exception no param", "ws://localhost:22222/undefined", "ws://localhost:22222/null",
128                       "ws://localhost:22222/", "ws://localhost:22222/", "ws://localhost:22222/#"})
129     public void initialWithoutUrl() throws Exception {
130         final String html = DOCTYPE_HTML
131             + "<html><head><script>\n"
132             + LOG_TITLE_FUNCTION
133             + "  function test() {\n"
134             + "    try {\n"
135             + "      let ws = new WebSocket();\n"
136             + "      log(ws.url);"
137             + "    } catch(e) { log('exception no param') }\n"
138 
139             + "    try {\n"
140             + "      let ws = new WebSocket(undefined);\n"
141             + "      log(ws.url);"
142             + "    } catch(e) { log('exception undefined') }\n"
143 
144             + "    try {\n"
145             + "      let ws = new WebSocket(null);\n"
146             + "      log(ws.url);"
147             + "    } catch(e) { log('exception null') }\n"
148 
149             + "    try {\n"
150             + "      let ws = new WebSocket('');\n"
151             + "      log(ws.url);"
152             + "    } catch(e) { log('exception empty') }\n"
153 
154             + "    try {\n"
155             + "      let ws = new WebSocket(' ');\n"
156             + "      log(ws.url);"
157             + "    } catch(e) { log('exception blank') }\n"
158 
159             + "    try {\n"
160             + "      let ws = new WebSocket('#');\n"
161             + "      log(ws.url);"
162             + "    } catch(e) { log('exception invalid') }\n"
163             + "  }\n"
164             + "</script></head><body onload='test()'>\n"
165             + "</body></html>";
166 
167         loadPageVerifyTitle2(html);
168     }
169 
170     /**
171      * @throws Exception if the test fails
172      */
173     @Test
174     @Alerts({"blob", "blob", "arraybuffer", "blob", "blob"})
175     public void binaryType() throws Exception {
176         final String html = DOCTYPE_HTML
177             + "<html><head><script>\n"
178             + LOG_TITLE_FUNCTION
179             + "  function test() {\n"
180             + "    var location = 'ws://localhost:" + PORT + "/';\n"
181             + "    var ws = new WebSocket(location);\n"
182             + "    log(ws.binaryType);\n"
183 
184             + "    try {\n"
185             + "      ws.binaryType = 'abc';\n"
186             + "      log(ws.binaryType);\n"
187             + "    } catch(e) { logEx(e) }\n"
188 
189             + "    try {\n"
190             + "      ws.binaryType = 'arraybuffer';\n"
191             + "      log(ws.binaryType);\n"
192             + "    } catch(e) { logEx(e) }\n"
193 
194             + "    try {\n"
195             + "      ws.binaryType = 'blob';\n"
196             + "      log(ws.binaryType);\n"
197             + "    } catch(e) { logEx(e) }\n"
198 
199             + "    try {\n"
200             + "      ws.binaryType = '';\n"
201             + "      log(ws.binaryType);\n"
202             + "    } catch(e) { logEx(e) }\n"
203             + "  }\n"
204             + "</script></head><body onload='test()'>\n"
205             + "</body></html>";
206 
207         loadPageVerifyTitle2(html);
208     }
209 
210     /**
211      * Test case taken from <a href="http://angelozerr.wordpress.com/2011/07/23/websockets_jetty_step1/">here</a>.
212      * @throws Exception if the test fails
213      */
214     @Test
215     public void chat() throws Exception {
216         final String firstResponse = "Browser: has joined!";
217         final String secondResponse = "Browser: Hope you are fine!";
218 
219         startWebServer("src/test/resources/org/htmlunit/javascript/host",
220             null, null, new ChatWebSocketHandler());
221         try {
222             final WebDriver driver = getWebDriver();
223             driver.get(URL_FIRST + "WebSocketTest_chat.html");
224 
225             driver.findElement(By.id("username")).sendKeys("Browser");
226             driver.findElement(By.id("joinB")).click();
227 
228             assertVisible("joined", driver);
229 
230             final WebElement chatE = driver.findElement(By.id("chat"));
231             long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
232 
233             do {
234                 Thread.sleep(100);
235             }
236             while (chatE.getText().length() <= firstResponse.length() && System.currentTimeMillis() < maxWait);
237 
238             assertEquals(firstResponse, chatE.getText());
239 
240             driver.findElement(By.id("phrase")).sendKeys("Hope you are fine!");
241             driver.findElement(By.id("sendB")).click();
242 
243             maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
244             do {
245                 Thread.sleep(100);
246             }
247             while (!chatE.getText().contains(secondResponse) && System.currentTimeMillis() < maxWait);
248 
249             assertEquals(firstResponse + "\n" + secondResponse, chatE.getText());
250         }
251         finally {
252             stopWebServers();
253         }
254     }
255 
256     private static class ChatWebSocketHandler extends WebSocketHandler {
257 
258         private final Set<ChatWebSocket> webSockets_ = new CopyOnWriteArraySet<>();
259 
260         ChatWebSocketHandler() {
261         }
262 
263         @Override
264         public void configure(final WebSocketServletFactory factory) {
265             factory.register(ChatWebSocket.class);
266             factory.setCreator(new WebSocketCreator() {
267                 @Override
268                 public Object createWebSocket(final ServletUpgradeRequest servletUpgradeRequest,
269                         final ServletUpgradeResponse servletUpgradeResponse) {
270                     return new ChatWebSocket();
271                 }
272             });
273         }
274 
275         private class ChatWebSocket extends WebSocketAdapter {
276             private Session session_;
277 
278             ChatWebSocket() {
279             }
280 
281             @Override
282             public void onWebSocketConnect(final Session session) {
283                 session_ = session;
284                 webSockets_.add(this);
285             }
286 
287             @Override
288             public void onWebSocketText(final String data) {
289                 try {
290                     for (final ChatWebSocket webSocket : webSockets_) {
291                         webSocket.session_.getRemote().sendString(data);
292                     }
293                 }
294                 catch (final IOException e) {
295                     session_.close();
296                 }
297             }
298 
299             @Override
300             public void onWebSocketClose(final int closeCode, final String message) {
301                 webSockets_.remove(this);
302             }
303         }
304     }
305 
306     /**
307      * {@inheritDoc}
308      */
309     @After
310     @Override
311     public void releaseResources() {
312         super.releaseResources();
313 
314         for (final Thread thread : Thread.getAllStackTraces().keySet()) {
315             if (thread.getName().contains("WebSocket")) {
316                 try {
317                     // ok found one but let's wait a bit to start a second check before
318                     // pressing the panic button
319                     Thread.sleep(400);
320                 }
321                 catch (final InterruptedException e) {
322                     e.printStackTrace();
323                 }
324             }
325         }
326 
327         String lastFailing = null;
328         for (final java.util.Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
329             final Thread thread = entry.getKey();
330             if (thread.getName().contains("WebSocket")) {
331                 lastFailing = thread.getName();
332                 System.err.println();
333                 System.err.println("WebSocket thread named '" + lastFailing + "' still running");
334                 final StackTraceElement[] traces = entry.getValue();
335                 for (int i = 0; i < traces.length; i++) {
336                     System.err.println(traces[i]);
337                 }
338             }
339         }
340 
341         assertNull("WebSocket thread named '" + lastFailing + "' still running", lastFailing);
342     }
343 
344     /**
345      * @throws Exception if the test fails
346      */
347     @Test
348     @Alerts({": myname=My value!1", ": myname=My value!2"})
349     public void cookies() throws Exception {
350         final String[] expected = getExpectedAlerts();
351 
352         startWebServer("src/test/resources/org/htmlunit/javascript/host",
353             null, null, new CookiesWebSocketHandler());
354         try {
355             final WebDriver driver = getWebDriver();
356             driver.get(URL_FIRST + "WebSocketTest_cookies.html");
357 
358             driver.findElement(By.id("username")).sendKeys("Browser");
359             driver.findElement(By.id("joinB")).click();
360             final WebElement chatE = driver.findElement(By.id("chat"));
361 
362             long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
363             do {
364                 Thread.sleep(100);
365             }
366             while (chatE.getText().length() <= expected[0].length() && System.currentTimeMillis() < maxWait);
367 
368             assertEquals(expected[0], chatE.getText());
369 
370             driver.findElement(By.id("phrase")).sendKeys("Hope you are fine!");
371             driver.findElement(By.id("sendB")).click();
372 
373             maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
374             do {
375                 Thread.sleep(100);
376             }
377             while (!chatE.getText().contains(expected[1]) && System.currentTimeMillis() < maxWait);
378 
379             assertEquals(expected[0] + "\n" + expected[1], chatE.getText());
380         }
381         finally {
382             stopWebServers();
383         }
384     }
385 
386     private static class CookiesWebSocketHandler extends WebSocketHandler {
387 
388         private final Set<CookiesWebSocket> webSockets_ = new CopyOnWriteArraySet<>();
389 
390         CookiesWebSocketHandler() {
391         }
392 
393         @Override
394         public void configure(final WebSocketServletFactory factory) {
395             factory.register(CookiesWebSocket.class);
396             factory.setCreator(new WebSocketCreator() {
397                 @Override
398                 public Object createWebSocket(final ServletUpgradeRequest servletUpgradeRequest,
399                         final ServletUpgradeResponse servletUpgradeResponse) {
400                     return new CookiesWebSocket();
401                 }
402             });
403         }
404 
405         private class CookiesWebSocket extends WebSocketAdapter {
406             private Session session_;
407             private int counter_ = 1;
408 
409             CookiesWebSocket() {
410             }
411 
412             @Override
413             public void onWebSocketConnect(final Session session) {
414                 session_ = session;
415                 webSockets_.add(this);
416             }
417 
418             @Override
419             public void onWebSocketText(final String data) {
420                 try {
421                     final String cookie = session_.getUpgradeRequest().getHeaders()
422                                                         .get(HttpHeader.COOKIE).get(0) + counter_++;
423                     for (final CookiesWebSocket webSocket : webSockets_) {
424                         webSocket.session_.getRemote().sendString(cookie);
425                     }
426                 }
427                 catch (final IOException e) {
428                     session_.close();
429                 }
430             }
431 
432             @Override
433             public void onWebSocketClose(final int closeCode, final String message) {
434                 webSockets_.remove(this);
435             }
436         }
437     }
438 
439     /**
440      * @throws Exception if the test fails
441      */
442     @Test
443     @Alerts({"onOpenListener",
444              "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
445              "undefined", "undefined", "undefined", "undefined",
446              "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
447              "server_text", "§§URL§§", "", "null",
448              "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
449              "server_text", "§§URL§§", "", "null",
450              "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
451              "[object ArrayBuffer]", "§§URL§§", "", "null",
452              "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
453              "[object ArrayBuffer]", "§§URL§§", "", "null",
454              "onCloseListener code: 1000",
455              "onClose code: 1000"})
456     public void events() throws Exception {
457         expandExpectedAlertsVariables("ws://localhost:" + PORT);
458         final String expected = String.join("\n", getExpectedAlerts());
459 
460         startWebServer("src/test/resources/org/htmlunit/javascript/host",
461             null, null, new EventsWebSocketHandler());
462         try {
463             final WebDriver driver = getWebDriver();
464             driver.get(URL_FIRST + "WebSocketTest_events.html");
465 
466             final WebElement logElement = driver.findElement(By.id("log"));
467             final long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
468 
469             String text;
470             do {
471                 Thread.sleep(100);
472 
473                 text = logElement.getDomProperty("value").trim().replaceAll("\r", "");
474             }
475             while (text.length() <= expected.length() && System.currentTimeMillis() < maxWait);
476 
477             assertEquals(expected, text);
478         }
479         finally {
480             stopWebServers();
481         }
482     }
483 
484     /**
485      * @throws Exception if the test fails
486      */
487     @Test
488     @Alerts(DEFAULT = {"onOpenListener",
489                        "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
490                        "undefined", "undefined", "undefined", "undefined",
491                        "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
492                        "server_text", "§§URL§§", "", "null",
493                        "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
494                        "server_text", "§§URL§§", "", "null",
495                        "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
496                        "[object ArrayBuffer]", "§§URL§§", "", "null",
497                        "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
498                        "[object ArrayBuffer]", "§§URL§§", "", "null",
499                        "onCloseListener code: 1000  wasClean: true",
500                        "onClose code: 1000  wasClean: true"},
501             FF = {"onOpenListener",
502                   "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
503                   "undefined", "undefined", "undefined", "undefined",
504                   "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
505                   "server_text", "§§URL§§", "", "null",
506                   "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
507                   "server_text", "§§URL§§", "", "null",
508                   "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
509                   "[object ArrayBuffer]", "§§URL§§", "", "null",
510                   "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
511                   "[object ArrayBuffer]", "§§URL§§", "", "null",
512                   "onCloseListener code: 1000  wasClean: false",
513                   "onClose code: 1000  wasClean: false"},
514             FF_ESR = {"onOpenListener",
515                       "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
516                       "undefined", "undefined", "undefined", "undefined",
517                       "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
518                       "server_text", "§§URL§§", "", "null",
519                       "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
520                       "server_text", "§§URL§§", "", "null",
521                       "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
522                       "[object ArrayBuffer]", "§§URL§§", "", "null",
523                       "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
524                       "[object ArrayBuffer]", "§§URL§§", "", "null",
525                       "onCloseListener code: 1000  wasClean: false",
526                       "onClose code: 1000  wasClean: false"})
527     @HtmlUnitNYI(FF = {"onOpenListener",
528                        "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
529                        "undefined", "undefined", "undefined", "undefined",
530                        "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
531                        "server_text", "§§URL§§", "", "null",
532                        "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
533                        "server_text", "§§URL§§", "", "null",
534                        "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
535                        "[object ArrayBuffer]", "§§URL§§", "", "null",
536                        "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
537                        "[object ArrayBuffer]", "§§URL§§", "", "null",
538                        "onCloseListener code: 1000  wasClean: true",
539                        "onClose code: 1000  wasClean: true"},
540             FF_ESR = {"onOpenListener",
541                       "onOpen", "open", "[object WebSocket]", "[object WebSocket]",
542                       "undefined", "undefined", "undefined", "undefined",
543                       "onMessageTextListener", "message", "[object WebSocket]", "[object WebSocket]",
544                       "server_text", "§§URL§§", "", "null",
545                       "onMessageText", "message", "[object WebSocket]", "[object WebSocket]",
546                       "server_text", "§§URL§§", "", "null",
547                       "onMessageBinaryListener", "message", "[object WebSocket]", "[object WebSocket]",
548                       "[object ArrayBuffer]", "§§URL§§", "", "null",
549                       "onMessageBinary", "message", "[object WebSocket]", "[object WebSocket]",
550                       "[object ArrayBuffer]", "§§URL§§", "", "null",
551                       "onCloseListener code: 1000  wasClean: true",
552                       "onClose code: 1000  wasClean: true"})
553     public void wasClean() throws Exception {
554         expandExpectedAlertsVariables("ws://localhost:" + PORT);
555         final String expected = String.join("\n", getExpectedAlerts());
556 
557         startWebServer("src/test/resources/org/htmlunit/javascript/host",
558             null, null, new EventsWebSocketHandler());
559         try {
560             final WebDriver driver = getWebDriver();
561             driver.get(URL_FIRST + "WebSocketTest_wasClean.html");
562 
563             final WebElement logElement = driver.findElement(By.id("log"));
564             final long maxWait = System.currentTimeMillis() + DEFAULT_WAIT_TIME.toMillis();
565 
566             String text;
567             do {
568                 Thread.sleep(100);
569 
570                 text = logElement.getDomProperty("value").trim().replaceAll("\r", "");
571             }
572             while (text.length() <= expected.length() && System.currentTimeMillis() < maxWait);
573 
574             assertEquals(expected, text);
575         }
576         finally {
577             stopWebServers();
578         }
579     }
580 
581     /**
582      * @throws Exception if the test fails
583      */
584     @Test
585     @Alerts({"onError[object Event]",
586              "onCloseListener code: 1006  wasClean: false",
587              "onClose code: 1006  wasClean: false"})
588     public void eventsNoSocketServer() throws Exception {
589         startWebServer("src/test/resources/org/htmlunit/javascript/host", null, null, null);
590         try {
591             final WebDriver driver = getWebDriver();
592             driver.get(URL_FIRST + "WebSocketTest_wasClean.html");
593 
594             final WebElement logElement = driver.findElement(By.id("log"));
595             int counter = 0;
596             String text;
597             do {
598                 Thread.sleep(DEFAULT_WAIT_TIME.toMillis());
599 
600                 text = logElement.getDomProperty("value").trim().replaceAll("\r", "");
601             }
602             while (text.length() > 0 && counter++ < 10);
603 
604             assertEquals(String.join("\n", getExpectedAlerts()), text);
605         }
606         finally {
607             stopWebServers();
608         }
609     }
610 
611     private static void assertVisible(final String domId, final WebDriver driver) throws Exception {
612         final WebElement domE = driver.findElement(By.id(domId));
613         int counter = 0;
614         do {
615             Thread.sleep(100);
616         }
617         while (!domE.isDisplayed() && counter++ < 10);
618 
619         assertEquals("Node should be visible, domId: " + domId, true, domE.isDisplayed());
620     }
621 
622     private static class EventsWebSocketHandler extends WebSocketHandler {
623 
624         EventsWebSocketHandler() {
625         }
626 
627         @Override
628         public void configure(final WebSocketServletFactory factory) {
629             factory.register(EventsWebSocket.class);
630             factory.setCreator(new WebSocketCreator() {
631                 @Override
632                 public EventsWebSocket createWebSocket(final ServletUpgradeRequest servletUpgradeRequest,
633                         final ServletUpgradeResponse servletUpgradeResponse) {
634                     return new EventsWebSocket();
635                 }
636             });
637         }
638 
639         private static class EventsWebSocket extends WebSocketAdapter {
640 
641             EventsWebSocket() {
642             }
643 
644             @Override
645             public void onWebSocketText(final String data) {
646                 if ("text".equals(data)) {
647                     try {
648                         getRemote().sendString("server_text");
649                     }
650                     catch (final IOException e) {
651                         throw new IllegalStateException(e.getMessage(), e);
652                     }
653                 }
654                 else if ("close".equals(data)) {
655                     getSession().close();
656                 }
657                 else {
658                     throw new IllegalArgumentException("Unknown request: " + data);
659                 }
660             }
661 
662             @Override
663             public void onWebSocketBinary(final byte[] payload, final int offset, final int len) {
664                 final String data = new String(payload, offset, len, UTF_16LE);
665                 if ("binary".equals(data)) {
666                     final ByteBuffer response = ByteBuffer.wrap("server_binary".getBytes(UTF_16LE));
667                     try {
668                         getRemote().sendBytes(response);
669                     }
670                     catch (final IOException e) {
671                         throw new IllegalStateException(e.getMessage(), e);
672                     }
673                 }
674                 else {
675                     throw new IllegalArgumentException("Unknown request: " + data);
676                 }
677             }
678         }
679     }
680 
681     /**
682      * @throws Exception if the test fails
683      */
684     @Test
685     @Alerts("true")
686     public void prototypeUrl() throws Exception {
687         final String html = DOCTYPE_HTML
688             + "<html><head><script>\n"
689             + LOG_TITLE_FUNCTION
690             + "  function test() {\n"
691             + "    try {\n"
692             + "      var u = WebSocket.prototype.url;\n"
693             + "      log(u);\n"
694             + "    } catch(e) { log(e instanceof TypeError) }\n"
695             + "  }\n"
696             + "</script></head><body onload='test()'>\n"
697             + "</body></html>";
698 
699         loadPageVerifyTitle2(html);
700     }
701 
702     /**
703      * @throws Exception if the test fails
704      */
705     @Test
706     public void socketsGetClosedOnPageReplace() throws Exception {
707         startWebServer("src/test/resources/org/htmlunit/javascript/host",
708                 null, null, new ChatWebSocketHandler());
709         try {
710             final WebDriver driver = getWebDriver();
711             driver.get(URL_FIRST + "WebSocketTest_chat.html");
712 
713             driver.findElement(By.id("username")).sendKeys("Browser");
714             driver.findElement(By.id("joinB")).click();
715 
716             assertVisible("joined", driver);
717 
718             driver.get(URL_FIRST + "plain.html");
719         }
720         finally {
721             stopWebServers();
722         }
723     }
724 }