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;
16  
17  import static java.nio.charset.StandardCharsets.UTF_8;
18  import static org.junit.Assert.assertArrayEquals;
19  
20  import java.net.URL;
21  import java.nio.charset.Charset;
22  import java.nio.charset.StandardCharsets;
23  import java.security.SecureRandom;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.zip.Deflater;
27  
28  import org.apache.commons.io.IOUtils;
29  import org.htmlunit.html.HtmlInlineFrame;
30  import org.htmlunit.junit.BrowserRunner;
31  import org.htmlunit.junit.annotation.Alerts;
32  import org.htmlunit.junit.annotation.HtmlUnitNYI;
33  import org.htmlunit.util.MimeType;
34  import org.htmlunit.util.NameValuePair;
35  import org.junit.Test;
36  import org.junit.runner.RunWith;
37  import org.openqa.selenium.By;
38  import org.openqa.selenium.WebDriver;
39  import org.openqa.selenium.WebElement;
40  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
41  
42  /**
43   * Tests for {@link WebClient} using WebDriverTestCase.
44   *
45   * @author Marc Guillemot
46   * @author Frank Danek
47   * @author Ronald Brill
48   */
49  @RunWith(BrowserRunner.class)
50  public class WebClient3Test extends WebDriverTestCase {
51  
52      static final SecureRandom RANDOM = new SecureRandom();
53  
54      /**
55       * Regression test for bug 3012067: a null pointer exception was occurring.
56       * @throws Exception if an error occurs
57       */
58      @Test
59      public void bug3012067_npe() throws Exception {
60          final String html = DOCTYPE_HTML
61              + "<html><body>\n"
62              + "<form action='" + URL_FIRST + "#foo' method='post'></form>\n"
63              + "<script>\n"
64              + "function doWork() {\n"
65              + "  var f = document.forms[0];\n"
66              + "  f.submit();\n"
67              + "  f.submit();\n"
68              + "}\n"
69              + "</script>\n"
70              + "<span id='clickMe' onclick='doWork()'>click</span>\n"
71              + "</body></html>";
72  
73          final WebDriver driver = loadPage2(html);
74          driver.findElement(By.id("clickMe")).click();
75      }
76  
77      /**
78       * Ensure that response stream can be read more than one time.
79       * @throws Exception if an error occurs
80       */
81      @Test
82      public void readStreamTwice() throws Exception {
83          final String html = DOCTYPE_HTML
84              + "<html>\n"
85              + "<body>\n"
86              + "<iframe src='binaryFile.bin'></iframe>\n"
87              + "<iframe src='foo.html'></iframe>\n"
88              + "</body></html>";
89  
90          final MockWebConnection mockConnection = getMockWebConnection();
91          final byte[] binaryContent = new byte[4818];
92          for (int i = 0; i < binaryContent.length; i++) {
93              binaryContent[i] = (byte) (RANDOM.nextInt(Byte.MAX_VALUE));
94          }
95          mockConnection.setDefaultResponse(binaryContent, 200, "OK", MimeType.APPLICATION_OCTET_STREAM);
96          final URL urlFoo = new URL(URL_FIRST, "foo.html");
97          mockConnection.setResponse(urlFoo, DOCTYPE_HTML + "<html></html>");
98  
99          final WebDriver driver = loadPage2(html);
100         final WebElement iframe1 = driver.findElement(By.tagName("iframe"));
101         if (driver instanceof HtmlUnitDriver) {
102             final HtmlInlineFrame htmlUnitIFrame1 = (HtmlInlineFrame) toHtmlElement(iframe1);
103             final WebResponse iframeWebResponse = htmlUnitIFrame1.getEnclosedPage().getWebResponse();
104             byte[] receivedBytes = IOUtils.toByteArray(iframeWebResponse.getContentAsStream());
105             receivedBytes = IOUtils.toByteArray(iframeWebResponse.getContentAsStream());
106             assertArrayEquals(binaryContent, receivedBytes);
107         }
108     }
109 
110     /**
111      * Was causing an Exception in IE simulation
112      * as of HtmlUnit-2.8-SNAPSHOT on Aug. 04, 2010.
113      * @throws Exception if the test fails
114      */
115     @Test
116     public void escapeRequestQuery() throws Exception {
117         getMockWebConnection().setDefaultResponse("");
118 
119         loadPage2("", new URL(URL_FIRST, "foo?a=<b>i</b>"));
120     }
121 
122     /**
123      * Was causing a "java.net.URISyntaxException: Malformed escape pair".
124      * HtmlUnit now escapes the "%%" to "%25%25" to build a valid URL but FF doesn't care
125      * and sends the invalid "%%" sequence as it.
126      * This will be quite difficult to simulate FF here as HttpClient's HttpRequestBase
127      * uses URI and "%%" can't be part of the query string for a URI.
128      * @throws Exception if the test fails
129      */
130     @Test
131     @Alerts("0")
132     @HtmlUnitNYI(CHROME = "1",
133             EDGE = "1",
134             FF = "1",
135             FF_ESR = "1")
136     public void escapeRequestQuery2a() throws Exception {
137         getMockWebConnection().setDefaultResponse("");
138 
139         final URL url = new URL(URL_FIRST, "foo.png?cb=%%RANDOM_NUMBER%%");
140         loadPage2("", url);
141 
142         // real browsers do not send this request
143         // 'Unable to parse URI query'
144         assertEquals(Integer.parseInt(getExpectedAlerts()[0]), getMockWebConnection().getRequestCount());
145     }
146 
147     /**
148      * Was causing a "java.net.URISyntaxException: Malformed escape pair".
149      * This is a simplified version of {@link #escapeRequestQuery2a()} only testing
150      * that no exception is thrown. The request performed is not fully correct.
151      * This test can be removed once {@link #escapeRequestQuery2a()} runs correctly.
152      * @throws Exception if the test fails
153      */
154     @Test
155     public void escapeRequestQuery2b() throws Exception {
156         getMockWebConnection().setDefaultResponse("");
157 
158         final URL url = new URL(URL_FIRST, "foo.png?cb=%%RANDOM_NUMBER%%");
159         loadPage2("", url);
160     }
161 
162     /**
163      * Regression test for issue 3193004.
164      * Ensure that the click returns once the target page has been loaded into the target window.
165      * @throws Exception if an error occurs
166      */
167     @Test
168     public void clickReturnsWhenThePageHasBeenCompleteLoaded() throws Exception {
169         final String firstContent = DOCTYPE_HTML
170             + "<html><head>\n"
171             + "<script>window.setInterval(\'',1);</script></head>\n"
172             + "<body><a href='" + URL_SECOND + "'>to second</a></body></html>";
173         final String secondContent = DOCTYPE_HTML + "<html><body></body></html>";
174 
175         final MockWebConnection webConnection = getMockWebConnection();
176         webConnection.setResponse(URL_SECOND, secondContent);
177 
178         for (int i = 1; i < 100; i++) {
179             final WebDriver webDriver = loadPage2(firstContent);
180             webDriver.findElement(By.tagName("a")).click();
181             assertEquals("Run " + i, URL_SECOND.toExternalForm(), webDriver.getCurrentUrl());
182         }
183     }
184 
185     /**
186      * Ensures, that a window opened by an anchor with target attribute is attached
187      * to the javascript event loop.
188      *
189      * @throws Exception if an error occurs
190      */
191     @Test
192     @Alerts({"open", "first", "second"})
193     public void windowOpenedByAnchorTargetIsAttachedToJavascriptEventLoop() throws Exception {
194         final String firstContent = DOCTYPE_HTML
195             + "<html>\n"
196             + "<head>\n"
197             + "<script type='text/javascript'>\n"
198             + "  function info(msg) {\n"
199             + "    alert(msg);\n"
200             + "  }\n"
201             + "</script>\n"
202             + "</head>\n"
203             + "<body>\n"
204             + "  <a id='testAnchor' href='" + URL_SECOND + "' target='_blank' onclick='info(\"open\")'>to second</a>\n"
205             + "</body></html>";
206         final String secondContent = DOCTYPE_HTML
207             + "<html><head>\n"
208             + "<script type='text/javascript'>\n"
209             + "  function first() {\n"
210             + "    window.opener.info('first');\n"
211             + "    window.setTimeout(second, 10);\n"
212             + "  }\n"
213             + "  function second() {\n"
214             + "    window.opener.info('second');\n"
215             + "    window.close();\n"
216             + "  }\n"
217             + "</script>\n"
218             + "</head>\n"
219 
220             + "<body onLoad='window.setTimeout(first, 5);'></body></html>";
221 
222         getMockWebConnection().setResponse(URL_SECOND, secondContent);
223 
224         final WebDriver driver = loadPage2(firstContent);
225         driver.findElement(By.id("testAnchor")).click();
226         if (useRealBrowser()) {
227             Thread.sleep(400);
228         }
229 
230         verifyAlerts(driver, getExpectedAlerts());
231     }
232 
233     /**
234      * Ensures, that a window opened by a form with target attribute is attached
235      * to the javascript event loop.
236      *
237      * @throws Exception if an error occurs
238      */
239     @Test
240     @Alerts({"open", "first", "second"})
241     public void windowOpenedByFormTargetIsAttachedToJavascriptEventLoop() throws Exception {
242         final String firstContent = DOCTYPE_HTML
243             + "<html>\n"
244             + "<head>\n"
245             + "<script type='text/javascript'>\n"
246             + "  function info(msg) {\n"
247             + "    alert(msg);\n"
248             + "  }\n"
249             + "</script>\n"
250             + "</head>\n"
251             + "<body>\n"
252             + "<form action='" + URL_SECOND + "' target='_blank'>\n"
253             + "  <input id='testSubmit' type='submit' value='Submit' onclick='info(\"open\")'>\n"
254             + "</form>\n"
255             + "</body></html>";
256         final String secondContent = DOCTYPE_HTML
257             + "<html><head>\n"
258             + "<script type='text/javascript'>\n"
259             + "  function first() {\n"
260             + "    window.opener.info('first');\n"
261             + "    window.setTimeout(second, 10);\n"
262             + "  }\n"
263             + "  function second() {\n"
264             + "    window.opener.info('second');\n"
265             + "    window.close();\n"
266             + "  }\n"
267             + "</script>\n"
268             + "</head>\n"
269 
270             + "<body onLoad='window.setTimeout(first, 5);'></body></html>";
271 
272         getMockWebConnection().setResponse(URL_SECOND, secondContent);
273 
274         final WebDriver driver = loadPage2(firstContent);
275         driver.findElement(By.id("testSubmit")).click();
276         if (useRealBrowser()) {
277             Thread.sleep(400);
278         }
279 
280         verifyAlerts(driver, getExpectedAlerts());
281     }
282 
283     /**
284      * Ensures, that a window opened by javascript window.open is attached
285      * to the javascript event loop.
286      *
287      * @throws Exception if an error occurs
288      */
289     @Test
290     @Alerts({"open", "first", "second"})
291     public void windowOpenedByJavascriptIsAttachedToJavascriptEventLoop() throws Exception {
292         final String firstContent = DOCTYPE_HTML
293             + "<html>\n"
294             + "<head>\n"
295             + "<script type='text/javascript'>\n"
296             + "  function info(msg) {\n"
297             + "    alert(msg);\n"
298             + "  }\n"
299             + "</script>\n"
300             + "</head>\n"
301             + "<body>\n"
302             + "  <a id='testAnchor' href='#'"
303             + "    onclick='info(\"open\");window.open(\"" + URL_SECOND + "\", \"Popup\", \"\");'>open window</a>\n"
304             + "</body></html>";
305         final String secondContent = DOCTYPE_HTML
306             + "<html><head>\n"
307             + "<script type='text/javascript'>\n"
308             + "  function first() {\n"
309             + "    window.opener.info('first');\n"
310             + "    window.setTimeout(second, 10);\n"
311             + "  }\n"
312             + "  function second() {\n"
313             + "    window.opener.info('second');\n"
314             + "    window.close();\n"
315             + "  }\n"
316             + "</script>\n"
317             + "</head>\n"
318 
319             + "<body onLoad='window.setTimeout(first, 5);'></body></html>";
320 
321         getMockWebConnection().setResponse(URL_SECOND, secondContent);
322 
323         final WebDriver driver = loadPage2(firstContent);
324         driver.findElement(By.id("testAnchor")).click();
325 
326         verifyAlerts(driver, getExpectedAlerts());
327     }
328 
329     /**
330      * Ensures, that a window opened by javascript and than filled by a form with target attribute
331      * is attached to the javascript event loop.
332      *
333      * @throws Exception if an error occurs
334      */
335     @Test
336     @Alerts({"open", "first", "second"})
337     public void windowOpenedByJavascriptFilledByFormTargetIsAttachedToJavascriptEventLoop() throws Exception {
338         final String firstContent = DOCTYPE_HTML
339             + "<html>\n"
340             + "<head>\n"
341             + "<script type='text/javascript'>\n"
342             + "  function info(msg) {\n"
343             + "    alert(msg);\n"
344             + "  }\n"
345             + "</script>\n"
346             + "</head>\n"
347             + "<body>\n"
348             + "<form action='" + URL_SECOND + "' name='myForm'>\n"
349             + "  <input id='testSubmit' type='button' value='Submit' "
350             + "    onclick='info(\"open\");"
351             + "    window.open(\"" + URL_SECOND + "\", \"Popup\");"
352             + "    document.myForm.target = \"Popup\";'"
353             + "  >\n"
354             + "</form>\n"
355             + "</body></html>";
356         final String secondContent = DOCTYPE_HTML
357             + "<html><head>\n"
358             + "<script type='text/javascript'>\n"
359             + "  function first() {\n"
360             + "    window.opener.info('first');\n"
361             + "    window.setTimeout(second, 10);\n"
362             + "  }\n"
363             + "  function second() {\n"
364             + "    window.opener.info('second');\n"
365             + "    window.close();\n"
366             + "  }\n"
367             + "</script>\n"
368             + "</head>\n"
369 
370             + "<body onLoad='window.setTimeout(first, 5);'></body></html>";
371 
372         getMockWebConnection().setResponse(URL_SECOND, secondContent);
373 
374         final WebDriver driver = loadPage2(firstContent);
375         driver.findElement(By.id("testSubmit")).click();
376 
377         verifyAlerts(driver, getExpectedAlerts());
378     }
379 
380     /**
381      * @throws Exception if an error occurs
382      */
383     @Test
384     @Alerts({"Executed", "later"})
385     public void execJavascriptOnErrorPages() throws Exception {
386         final String errorHtml = DOCTYPE_HTML
387                 + "<html>\n"
388                 + "<head>\n"
389                 + "</head>\n"
390                 + "<body>\n"
391                 + "<script type='text/javascript'>\n"
392                 + LOG_TITLE_FUNCTION
393                 + "  log('Executed');\n"
394                 + "  setTimeout(\"log('later')\", 10);\n"
395                 + "</script>\n"
396                 + "</body></html>\n";
397 
398         final MockWebConnection conn = getMockWebConnection();
399         conn.setResponse(URL_FIRST, errorHtml, 404, "Not Found", MimeType.TEXT_HTML, new ArrayList<>());
400 
401         loadPage2(URL_FIRST, StandardCharsets.UTF_8);
402         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
403     }
404 
405     /**
406      * This test was failing due to a change made in revision 7104 (not in any release)
407      * that was transforming %20 into %2520.
408      * @throws Exception if an error occurs
409      */
410     @Test
411     @Alerts("hello")
412     public void urlEncodingPercent20() throws Exception {
413         final String html = DOCTYPE_HTML
414                 + "<html><body>\n"
415                 + "<script src='a%20b.js'></script>\n"
416                 + "</body></html>";
417 
418         final MockWebConnection conn = getMockWebConnection();
419         conn.setResponse(new URL(URL_FIRST, "a%20b.js"), "alert('hello');", "text/javascript");
420 
421         loadPageWithAlerts2(html);
422     }
423 
424     /**
425      * Test "deflate" encoding without ZLIB header and checksum fields.
426      * @throws Exception if the test fails
427      */
428     @Test
429     @Alerts("modified")
430     public void deflateCompressionGZipCompatible() throws Exception {
431         doTestDeflateCompression(true);
432     }
433 
434     /**
435      * Test "deflate" encoding with ZLIB header and checksum fields.
436      * @throws Exception if the test fails
437      */
438     @Test
439     @Alerts("modified")
440     // IE does not support deflate compression anymore but I couldn't find a way to disable it in HttpClient
441     public void deflateCompressionNonGZipCompatible() throws Exception {
442         doTestDeflateCompression(false);
443     }
444 
445     private void doTestDeflateCompression(final boolean gzipCompatibleCompression) throws Exception {
446         final byte[] input = "document.title = 'modified';".getBytes(UTF_8);
447 
448         final byte[] buffer = new byte[100];
449         final Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, gzipCompatibleCompression);
450         deflater.setInput(input);
451         deflater.finish();
452 
453         final int compressedDataLength = deflater.deflate(buffer);
454         final byte[] content = new byte[compressedDataLength];
455         System.arraycopy(buffer, 0, content, 0, compressedDataLength);
456 
457         final List<NameValuePair> headers = new ArrayList<>();
458         headers.add(new NameValuePair("Content-Encoding", "deflate"));
459         headers.add(new NameValuePair(HttpHeader.CONTENT_LENGTH, String.valueOf(compressedDataLength)));
460 
461         final MockWebConnection conn = getMockWebConnection();
462         conn.setResponse(URL_SECOND, content, 200, "OK", "text/javascript", headers);
463 
464         final String html = DOCTYPE_HTML
465             + "<html><head>\n"
466             + "<title>Hello world</title>\n"
467             + "<script src='" + URL_SECOND + "'></script>\n"
468             + "</head><body><script>alert(document.title)</script></body></html>";
469         loadPageWithAlerts2(html);
470     }
471 
472     /**
473      * @throws Exception if something goes wrong
474      */
475     @Test
476     @Alerts("executed")
477     public void javascriptContentDetectorWithoutContentType() throws Exception {
478         final MockWebConnection conn = getMockWebConnection();
479         conn.setDefaultResponse("<script>alert('executed')</script>", 200, "OK", null);
480         loadPageWithAlerts2(URL_FIRST);
481     }
482 
483     /**
484      * @throws Exception if something goes wrong
485      */
486     @Test
487     @Alerts("executed")
488     public void javascriptContentDetectorWithoutContentType500() throws Exception {
489         final MockWebConnection conn = getMockWebConnection();
490         conn.setDefaultResponse("<script>alert('executed')</script>", 500, "OK", null);
491         loadPageWithAlerts2(URL_FIRST);
492     }
493 
494     /**
495      * @throws Exception if something goes wrong
496      */
497     @Test
498     @Alerts("executed")
499     public void javascriptContentDetectorWithoutContentTypeWhitespace() throws Exception {
500         final MockWebConnection conn = getMockWebConnection();
501         conn.setDefaultResponse(" \t \r\n \n   <script>alert('executed')</script>", 200, "OK", null);
502         loadPageWithAlerts2(URL_FIRST);
503     }
504 
505     /**
506      * @throws Exception if something goes wrong
507      */
508     @Test
509     public void javascriptContentDetectorWithoutContentTypeTextBefore() throws Exception {
510         final MockWebConnection conn = getMockWebConnection();
511         conn.setDefaultResponse("Attention<script>alert('executed')</script>", 200, "OK", null);
512         loadPageWithAlerts2(URL_FIRST);
513     }
514 
515     /**
516      * @throws Exception if something goes wrong
517      */
518     @Test
519     @Alerts("executed")
520     public void javascriptContentDetectorWithoutContentUppercase() throws Exception {
521         final MockWebConnection conn = getMockWebConnection();
522         conn.setDefaultResponse("<SCRIPT>alert('executed')</SCRIPT>", 200, "OK", null);
523         loadPageWithAlerts2(URL_FIRST);
524     }
525 
526     /**
527      * @throws Exception if something goes wrong
528      */
529     @Test
530     @Alerts("executed")
531     public void javascriptContentDetectorWithoutContentMixedCase() throws Exception {
532         final MockWebConnection conn = getMockWebConnection();
533         conn.setDefaultResponse("<scRIPt>alert('executed')</scRIPt>", 200, "OK", null);
534         loadPageWithAlerts2(URL_FIRST);
535     }
536 
537     /**
538      * @throws Exception if something goes wrong
539      */
540     @Test
541     public void javascriptContentDetectorContentTypeTextPlain() throws Exception {
542         final MockWebConnection conn = getMockWebConnection();
543         conn.setDefaultResponse("<script>alert('executed')</script>", 200, "OK", MimeType.TEXT_PLAIN);
544         loadPageWithAlerts2(URL_FIRST);
545     }
546 
547     /**
548      * @throws Exception if something goes wrong
549      */
550     @Test
551     @Alerts({})
552     @HtmlUnitNYI(CHROME = "executed",
553             EDGE = "executed",
554             FF = "executed",
555             FF_ESR = "executed")
556     public void javascriptContentDetectorContentTypeApplicationJavascript() throws Exception {
557         final MockWebConnection conn = getMockWebConnection();
558         conn.setDefaultResponse("<script>alert('executed')</script>", 200, "OK", MimeType.TEXT_JAVASCRIPT);
559         loadPageWithAlerts2(URL_FIRST);
560     }
561 
562     /**
563      * @throws Exception if the test fails
564      */
565     @Test
566     public void encodingCharsetGB2312() throws Exception {
567         encodingCharset("\u6211\u662F\u6211\u7684 Abc", "GB2312", "GB2312");
568     }
569 
570     /**
571      * @throws Exception if the test fails
572      */
573     @Test
574     public void encodingCharsetGB2312GBKChar() throws Exception {
575         encodingCharset("\u4eb8 Abc", "GB2312", "GBK");
576     }
577 
578     /**
579      * @throws Exception if the test fails
580      */
581     @Test
582     public void encodingCharsetGBK() throws Exception {
583         encodingCharset("\u6211\u662F\u6211\u7684 \u4eb8 Abc", "GBK", "GBK");
584     }
585 
586     private void encodingCharset(final String title,
587             final String metaCharset, final String responseCharset) throws Exception {
588         final String html = DOCTYPE_HTML
589             + "<html><head>\n"
590             + "<meta http-equiv='Content-Type' content='text/html; charset=" + metaCharset + "'>\n"
591             + "<title>" + title + "</title>\n"
592             + "</head>\n"
593             + "<body>\n"
594             + "</body></html>";
595 
596         final String firstResponse = "HTTP/1.1 200 OK\r\n"
597                 + "Content-Length: " + html.length() + "\r\n"
598                 + "Content-Type: text/html\r\n"
599                 + "Connection: close\r\n"
600                 + "\r\n" + html;
601 
602         shutDownAll();
603         try (PrimitiveWebServer primitiveWebServer =
604                 new PrimitiveWebServer(Charset.forName(responseCharset), firstResponse, firstResponse)) {
605             final String url = "http://localhost:" + primitiveWebServer.getPort() + "/";
606             final WebDriver driver = getWebDriver();
607 
608             driver.get(url);
609             assertEquals(title, driver.getTitle());
610         }
611     }
612 
613     /**
614      * @throws Exception if the test fails
615      */
616     @Test
617     @Alerts({"en-US", "en-US,en"})
618     public void language() throws Exception {
619         final String html = DOCTYPE_HTML
620             + "<html><head><script>\n"
621             + LOG_TITLE_FUNCTION
622             + "function test() {\n"
623             + "  log(navigator.language);\n"
624             + "  log(navigator.languages);\n"
625             + "}\n"
626             + "</script></head><body onload='test()'>\n"
627             + "</body></html>";
628 
629         shutDownAll();
630         try {
631             loadPageVerifyTitle2(html);
632         }
633         finally {
634             shutDownAll();
635         }
636     }
637 
638     /**
639      * @throws Exception if the test fails
640      */
641     @Test
642     @Alerts(DEFAULT = {"de-DE", "de-DE,de,en-US,en"},
643             EDGE = {"de", "de,de-DE,en,en-GB,en-US"},
644             FF = {"de-DE", "de-DE,de"},
645             FF_ESR = {"de-DE", "de-DE,de"})
646     @HtmlUnitNYI(CHROME = {"de-DE", "de-DE,de"},
647             EDGE = {"de-DE", "de-DE,de"})
648     public void languageDE() throws Exception {
649         final String html = DOCTYPE_HTML
650             + "<html><head><script>\n"
651             + LOG_TITLE_FUNCTION
652             + "function test() {\n"
653             + "  log(navigator.language);\n"
654             + "  log(navigator.languages);\n"
655             + "}\n"
656             + "</script></head><body onload='test()'>\n"
657             + "</body></html>";
658 
659         shutDownAll();
660         try {
661             final BrowserVersion.BrowserVersionBuilder builder
662                 = new BrowserVersion.BrowserVersionBuilder(getBrowserVersion());
663             builder.setBrowserLanguage("de-DE");
664             builder.setAcceptLanguageHeader("de-DE,de");
665             setBrowserVersion(builder.build());
666 
667             loadPageVerifyTitle2(html);
668         }
669         finally {
670             shutDownAll();
671         }
672     }
673 }