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