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.fetch;
16  
17  import java.util.ArrayList;
18  import java.util.List;
19  
20  import org.htmlunit.HttpHeader;
21  import org.htmlunit.WebDriverTestCase;
22  import org.htmlunit.WebRequest;
23  import org.htmlunit.junit.annotation.Alerts;
24  import org.htmlunit.junit.annotation.HtmlUnitNYI;
25  import org.htmlunit.util.MimeType;
26  import org.htmlunit.util.NameValuePair;
27  import org.junit.jupiter.api.Test;
28  import org.openqa.selenium.WebDriver;
29  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
30  
31  /**
32   * Tests for Fetch API.
33   *
34   * @author Ronald Brill
35   */
36  public class FetchTest extends WebDriverTestCase {
37  
38      /**
39       * @throws Exception if the test fails
40       */
41      @Test
42      @Alerts({"200", "OK", "true", "text/xml;charset=iso-8859-1",
43               "<xml><content>blah</content></xml>"})
44      public void fetchGet() throws Exception {
45          final String html = DOCTYPE_HTML
46              + "<html>\n"
47              + "  <body>\n"
48              + "    <script>\n"
49              + LOG_TITLE_FUNCTION_NORMALIZE
50              + "      fetch('" + URL_SECOND + "')\n"
51              + "        .then(response => {\n"
52              + "          log(response.status);\n"
53              + "          log(response.statusText);\n"
54              + "          log(response.ok);\n"
55              + "          log(response.headers.get('content-type'));\n"
56              + "          return response.text();"
57              + "         })\n"
58              + "        .then(text => log(text))\n"
59              + "        .catch(e => logEx(e));\n"
60              + "    </script>\n"
61              + "  </body>\n"
62              + "</html>";
63  
64          final String xml = "<xml><content>blah</content></xml>";
65          getMockWebConnection().setResponse(URL_SECOND, xml, MimeType.TEXT_XML);
66  
67          final WebDriver driver = enableFetchPolyfill();
68          loadPage2(html);
69          verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
70  
71          assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
72      }
73  
74      /**
75       * @throws Exception if the test fails
76       */
77      @Test
78      @Alerts("TypeError")
79      public void fetchGetWithBody() throws Exception {
80          final String html = DOCTYPE_HTML
81              + "<html>\n"
82              + "  <body>\n"
83              + "    <script>\n"
84              + LOG_TITLE_FUNCTION
85              + "      fetch('" + URL_SECOND + "', {\n"
86              + "        method: 'GET',\n"
87              + "        body: 'test data'\n"
88              + "      })\n"
89              + "        .then(response => {\n"
90              + "          log(response.status);\n"
91              + "          log(response.statusText);\n"
92              + "          log(response.ok);\n"
93              + "          log(response.headers.get('content-type'));\n"
94              + "          return response.text();"
95              + "         })\n"
96              + "        .then(text => log(text))\n"
97              + "        .catch(e => logEx(e));\n"
98              + "    </script>\n"
99              + "  </body>\n"
100             + "</html>";
101 
102         getMockWebConnection().setResponse(URL_SECOND, "<response/>", MimeType.TEXT_XML);
103 
104         final WebDriver driver = enableFetchPolyfill();
105         loadPage2(html);
106         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
107 
108         assertEquals(URL_FIRST, getMockWebConnection().getLastWebRequest().getUrl());
109     }
110 
111     /**
112      * @throws Exception if the test fails
113      */
114     @Test
115     @Alerts("TypeError")
116     public void fetchGetWrongUrl() throws Exception {
117         final String html = DOCTYPE_HTML
118             + "<html>\n"
119             + "  <body>\n"
120             + "    <script>\n"
121             + LOG_TITLE_FUNCTION
122             + "      fetch('https://this.does.not.exist/htmlunit')\n"
123             + "        .then(response => {\n"
124             + "          log(response.status);\n"
125             + "          log(response.statusText);\n"
126             + "          log(response.ok);\n"
127             + "          log(response.headers.get('content-type'));\n"
128             + "          return response.text();"
129             + "         })\n"
130             + "        .then(text => log(text))\n"
131             + "        .catch(e => logEx(e));\n"
132             + "    </script>\n"
133             + "  </body>\n"
134             + "</html>";
135 
136         getMockWebConnection().setResponse(URL_SECOND, "<response/>", MimeType.TEXT_XML);
137 
138         final WebDriver driver = enableFetchPolyfill();
139         loadPage2(html);
140         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
141 
142         assertEquals(1, getMockWebConnection().getRequestCount());
143     }
144 
145     /**
146      * Tests fetch with different HTTP methods.
147      * @throws Exception if the test fails
148      */
149     @Test
150     @Alerts({"200", "OK", "true", "text/xml;charset=iso-8859-1", "<response/>"})
151     public void fetchPost() throws Exception {
152         final String html = DOCTYPE_HTML
153             + "<html>\n"
154             + "  <body>\n"
155             + "    <script>\n"
156             + LOG_TITLE_FUNCTION_NORMALIZE
157             + "      fetch('" + URL_SECOND + "', {\n"
158             + "        method: 'POST',\n"
159             + "        body: 'test data'\n"
160             + "      })\n"
161             + "        .then(response => {\n"
162             + "          log(response.status);\n"
163             + "          log(response.statusText);\n"
164             + "          log(response.ok);\n"
165             + "          log(response.headers.get('content-type'));\n"
166             + "          return response.text();"
167             + "         })\n"
168             + "        .then(text => log(text))\n"
169             + "        .catch(e => logEx(e));\n"
170             + "    </script>\n"
171             + "  </body>\n"
172             + "</html>";
173 
174         getMockWebConnection().setResponse(URL_SECOND, "<response/>", MimeType.TEXT_XML);
175 
176         final WebDriver driver = enableFetchPolyfill();
177         loadPage2(html);
178         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
179 
180         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
181         assertEquals("test data", getMockWebConnection().getLastWebRequest().getRequestBody());
182     }
183 
184     /**
185      * @throws Exception if the test fails
186      */
187     @Test
188     @Alerts({"200", "OK", "true",
189              "text/plain;charset=iso-8859-1", "bla\\sbla"})
190     public void fetchGetText() throws Exception {
191         final String html = DOCTYPE_HTML
192             + "<html>\n"
193             + "  <body>\n"
194             + "    <script>\n"
195             + LOG_TITLE_FUNCTION_NORMALIZE
196             + "      fetch('" + URL_SECOND + "')\n"
197             + "        .then(response => {\n"
198             + "          log(response.status);\n"
199             + "          log(response.statusText);\n"
200             + "          log(response.ok);\n"
201             + "          log(response.headers.get('content-type'));\n"
202             + "          return response.text();"
203             + "         })\n"
204             + "        .then(text => log(text))\n"
205             + "        .catch(e => logEx(e));\n"
206             + "    </script>\n"
207             + "  </body>\n"
208             + "</html>";
209 
210         getMockWebConnection().setResponse(URL_SECOND, "bla bla", MimeType.TEXT_PLAIN);
211 
212         final WebDriver driver = enableFetchPolyfill();
213         loadPage2(html);
214         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
215 
216         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
217     }
218 
219     /**
220      * @throws Exception if the test fails
221      */
222     @Test
223     @Alerts({"200", "OK", "true",
224              "application/json;charset=iso-8859-1", "{\\s'Html':\\s'Unit'\\s}"})
225     public void fetchGetJsonText() throws Exception {
226         final String html = DOCTYPE_HTML
227             + "<html>\n"
228             + "  <body>\n"
229             + "    <script>\n"
230             + LOG_TITLE_FUNCTION_NORMALIZE
231             + "      fetch('" + URL_SECOND + "')\n"
232             + "        .then(response => {\n"
233             + "          log(response.status);\n"
234             + "          log(response.statusText);\n"
235             + "          log(response.ok);\n"
236             + "          log(response.headers.get('content-type'));\n"
237             + "          return response.text();"
238             + "         })\n"
239             + "        .then(text => log(text))\n"
240             + "        .catch(e => logEx(e));\n"
241             + "    </script>\n"
242             + "  </body>\n"
243             + "</html>";
244 
245         final String json = "{ 'Html': 'Unit' }";
246         getMockWebConnection().setResponse(URL_SECOND, json, MimeType.APPLICATION_JSON);
247 
248         final WebDriver driver = enableFetchPolyfill();
249         loadPage2(html);
250         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
251 
252         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
253     }
254 
255     /**
256      * @throws Exception if the test fails
257      */
258     @Test
259     @Alerts({"200", "OK", "true",
260              "application/json;charset=iso-8859-1",
261              "[object\\sObject]", "Unit", "{\"Html\":\"Unit\"}"})
262     public void fetchGetJson() throws Exception {
263         final String html = DOCTYPE_HTML
264             + "<html>\n"
265             + "  <body>\n"
266             + "    <script>\n"
267             + LOG_TITLE_FUNCTION_NORMALIZE
268             + "      fetch('" + URL_SECOND + "')\n"
269             + "        .then(response => {\n"
270             + "          log(response.status);\n"
271             + "          log(response.statusText);\n"
272             + "          log(response.ok);\n"
273             + "          log(response.headers.get('content-type'));\n"
274             + "          return response.json();"
275             + "         })\n"
276             + "        .then(json => {\n"
277             + "          log(json);\n"
278             + "          log(json.Html);\n"
279             + "          log(JSON.stringify(json));\n"
280             + "        })\n"
281             + "        .catch(e => logEx(e));\n"
282             + "    </script>\n"
283             + "  </body>\n"
284             + "</html>";
285 
286         final String json = "{ \"Html\": \"Unit\" }";
287         getMockWebConnection().setResponse(URL_SECOND, json, MimeType.APPLICATION_JSON);
288 
289         final WebDriver driver = enableFetchPolyfill();
290         loadPage2(html);
291         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
292 
293         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
294     }
295 
296     /**
297      * @throws Exception if the test fails
298      */
299     @Test
300     @Alerts(DEFAULT = {"200", "OK", "true", "text/plain;charset=iso-8859-1",
301                        "[object\\sBlob]", "4", "text/plain"},
302             FF = {"200", "OK", "true", "text/plain;charset=iso-8859-1",
303                   "[object\\sBlob]", "4", "text/plain;charset=iso-8859-1"},
304             FF_ESR = {"200", "OK", "true", "text/plain;charset=iso-8859-1",
305                       "[object\\sBlob]", "4", "text/plain;charset=iso-8859-1"})
306     @HtmlUnitNYI(
307             FF = {"200", "OK", "true", "text/plain;charset=iso-8859-1",
308                   "[object\\sBlob]", "4", "text/plain"},
309             FF_ESR = {"200", "OK", "true", "text/plain;charset=iso-8859-1",
310                       "[object\\sBlob]", "4", "text/plain"})
311     public void fetchGetBlob() throws Exception {
312         final String html = DOCTYPE_HTML
313             + "<html>\n"
314             + "  <body>\n"
315             + "    <script>\n"
316             + LOG_TITLE_FUNCTION_NORMALIZE
317             + "      fetch('" + URL_SECOND + "')\n"
318             + "        .then(response => {\n"
319             + "          log(response.status);\n"
320             + "          log(response.statusText);\n"
321             + "          log(response.ok);\n"
322             + "          log(response.headers.get('content-type'));\n"
323             + "          return response.blob();"
324             + "         })\n"
325             + "        .then(blob => {\n"
326             + "          log(blob);\n"
327             + "          log(blob.size);\n"
328             + "          log(blob.type);\n"
329             + "        })\n"
330             + "        .catch(e => logEx(e));\n"
331             + "    </script>\n"
332             + "  </body>\n"
333             + "</html>";
334 
335         getMockWebConnection().setResponse(URL_SECOND, "ABCD", MimeType.TEXT_PLAIN);
336 
337         final WebDriver driver = enableFetchPolyfill();
338         loadPage2(html);
339         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
340 
341         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
342     }
343 
344     /**
345      * @throws Exception if the test fails
346      */
347     @Test
348     @Alerts({"200", "OK", "true", "text/plain;charset=iso-8859-1",
349              "[object\\sArrayBuffer]", "4"})
350     public void fetchGetArrayBuffer() throws Exception {
351         final String html = DOCTYPE_HTML
352             + "<html>\n"
353             + "  <body>\n"
354             + "    <script>\n"
355             + LOG_TITLE_FUNCTION_NORMALIZE
356             + "      fetch('" + URL_SECOND + "')\n"
357             + "        .then(response => {\n"
358             + "          log(response.status);\n"
359             + "          log(response.statusText);\n"
360             + "          log(response.ok);\n"
361             + "          log(response.headers.get('content-type'));\n"
362             + "          return response.arrayBuffer();"
363             + "         })\n"
364             + "        .then(buffer => {\n"
365             + "          log(buffer);\n"
366             + "          log(buffer.byteLength);\n"
367             + "        })\n"
368             + "        .catch(e => logEx(e));\n"
369             + "    </script>\n"
370             + "  </body>\n"
371             + "</html>";
372 
373         getMockWebConnection().setResponse(URL_SECOND, "ABCD", MimeType.TEXT_PLAIN);
374 
375         final WebDriver driver = enableFetchPolyfill();
376         loadPage2(html);
377         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
378 
379         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
380     }
381 
382     /**
383      * @throws Exception if the test fails
384      */
385     @Test
386     @Alerts({"200", "OK", "true"})
387     public void fetchGetCustomHeader() throws Exception {
388         final String html = DOCTYPE_HTML
389             + "<html>\n"
390             + "  <body>\n"
391             + "    <script>\n"
392             + LOG_TITLE_FUNCTION_NORMALIZE
393             + "      fetch('" + URL_SECOND + "', {\n"
394             + "        method: 'GET',\n"
395             + "        headers: {\n"
396             + "          'Content-Type': 'application/json',\n"
397             + "          'X-Custom-Header': 'x-test'\n"
398             + "        }\n"
399             + "      })"
400             + "        .then(response => {\n"
401             + "          log(response.status);\n"
402             + "          log(response.statusText);\n"
403             + "          log(response.ok);\n"
404             + "         })\n"
405             + "        .catch(e => logEx(e));\n"
406             + "    </script>\n"
407             + "  </body>\n"
408             + "</html>";
409 
410         final String json = "{ \"Html\": \"Unit\" }";
411         getMockWebConnection().setResponse(URL_SECOND, json, MimeType.APPLICATION_JSON);
412 
413         final WebDriver driver = enableFetchPolyfill();
414         loadPage2(html);
415         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
416 
417         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
418         assertEquals(URL_SECOND, lastRequest.getUrl());
419 
420         assertEquals("x-test", lastRequest.getAdditionalHeader("X-Custom-Header"));
421     }
422 
423     /**
424      * @throws Exception if the test fails
425      */
426     @Test
427     @Alerts({"200", "OK", "true", "text/plain;charset=iso-8859-1", "x-tEsT"})
428     public void fetchGetCustomResponseHeader() throws Exception {
429         final String html = DOCTYPE_HTML
430             + "<html>\n"
431             + "  <body>\n"
432             + "    <script>\n"
433             + LOG_TITLE_FUNCTION_NORMALIZE
434             + "      fetch('" + URL_SECOND + "', {\n"
435             + "        method: 'GET',\n"
436             + "        headers: {\n"
437             + "          'Content-Type': 'application/json',\n"
438             + "          'X-Custom-Header': 'x-test'\n"
439             + "        }\n"
440             + "      })"
441             + "        .then(response => {\n"
442             + "          log(response.status);\n"
443             + "          log(response.statusText);\n"
444             + "          log(response.ok);\n"
445             + "          log(response.headers.get('content-type'));\n"
446             + "          log(response.headers.get('X-Custom-Header'));\n"
447             + "         })\n"
448             + "        .catch(e => logEx(e));\n"
449             + "    </script>\n"
450             + "  </body>\n"
451             + "</html>";
452 
453         final List<NameValuePair> headers = new ArrayList<>();
454         headers.add(new NameValuePair("X-Custom-Header", "x-tEsT"));
455         getMockWebConnection().setResponse(URL_SECOND, "HtmlUnit", 200, "ok", MimeType.TEXT_PLAIN, headers);
456 
457         final WebDriver driver = enableFetchPolyfill();
458         loadPage2(html);
459         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
460 
461         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
462         assertEquals(URL_SECOND, lastRequest.getUrl());
463 
464         assertEquals("x-test", lastRequest.getAdditionalHeader("X-Custom-Header"));
465     }
466 
467     /**
468      * @throws Exception if the test fails
469      */
470     @Test
471     @Alerts({"200", "OK", "true"})
472     public void fetchPostFormData() throws Exception {
473         final String html = DOCTYPE_HTML
474             + "<html>\n"
475             + "  <body>\n"
476 
477             + "    <form name='testForm'>\n"
478             + "      <input type='text' name='myText' value='HtmlUnit'>\n"
479             + "    </form>\n"
480 
481             + "    <script>\n"
482             + LOG_TITLE_FUNCTION_NORMALIZE
483             + "      let formData = new FormData(document.testForm);"
484 
485             + "      fetch('" + URL_SECOND + "', {\n"
486             + "        method: 'POST',\n"
487             + "        body: formData\n"
488             + "      })"
489             + "        .then(response => {\n"
490             + "          log(response.status);\n"
491             + "          log(response.statusText);\n"
492             + "          log(response.ok);\n"
493             + "         })\n"
494             + "        .catch(e => logEx(e));\n"
495             + "    </script>\n"
496             + "  </body>\n"
497             + "</html>";
498 
499         getMockWebConnection().setResponse(URL_SECOND, "HtmlUnit", MimeType.TEXT_PLAIN);
500 
501         final WebDriver driver = enableFetchPolyfill();
502         loadPage2(html);
503         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
504 
505         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
506         assertEquals(URL_SECOND, lastRequest.getUrl());
507 
508         assertTrue(lastRequest.getRequestBody(), lastRequest.getRequestBody()
509                                 .contains("Content-Disposition: form-data; name=\"myText\""));
510         assertTrue(lastRequest.getRequestBody(), lastRequest.getRequestBody()
511                 .contains("HtmlUnit"));
512     }
513 
514     /**
515      * @throws Exception if the test fails
516      */
517     @Test
518     @Alerts({"200", "OK", "true", "test0,Hello1\\nHello1,test1,Hello2\\nHello2"})
519     public void fetchMultipartFormData() throws Exception {
520         final String html = DOCTYPE_HTML
521                 + "<html>\n"
522                 + "  <body>\n"
523                 + "    <script>\n"
524                 + LOG_TITLE_FUNCTION_NORMALIZE
525                 + "      fetch('" + URL_SECOND + "')"
526                 + "        .then(response => {\n"
527                 + "          log(response.status);\n"
528                 + "          log(response.statusText);\n"
529                 + "          log(response.ok);\n"
530 
531                 + "          return response.formData();\n"
532                 + "        })\n"
533                 + "        .then(formData => {\n"
534                 + "            log(Array.from(formData.entries()));\n"
535                 + "        })\n"
536                 + "        .catch(e => log(e.message));\n"
537                 + "    </script>\n"
538                 + "  </body>\n"
539                 + "</html>";
540 
541         final String boundary = "0123456789";
542         final String content = "--" + boundary + "\r\n"
543                 + "Content-Disposition: form-data; name=\"test0\"\r\n"
544                 + "Content-Type: text/plain\r\n"
545                 + "\r\n"
546                 + "Hello1\nHello1\r\n"
547                 + "--" + boundary + "\r\n"
548                 + "Content-Disposition: form-data; name=\"test1\"\r\n"
549                 + "Content-Type: text/plain\r\n"
550                 + "\r\n"
551                 + "Hello2\nHello2\r\n"
552                 + "--" + boundary + "--";
553 
554         getMockWebConnection().setResponse(URL_SECOND, content, "multipart/form-data; boundary=" + boundary);
555 
556         final WebDriver driver = enableFetchPolyfill();
557         loadPage2(html);
558         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
559 
560         assertEquals(URL_SECOND, getMockWebConnection().getLastWebRequest().getUrl());
561     }
562 
563     /**
564      * @throws Exception if the test fails
565      */
566     @Test
567     @Alerts({"200", "OK", "true"})
568     public void fetchPostURLSearchParams() throws Exception {
569         final String html = DOCTYPE_HTML
570             + "<html>\n"
571             + "  <body>\n"
572             + "    <script>\n"
573             + LOG_TITLE_FUNCTION_NORMALIZE
574             + "      let searchParams = new URLSearchParams();\n"
575             + "      searchParams.append('q', 'HtmlUnit');\n"
576             + "      searchParams.append('page', '1');\n"
577             + "      fetch('" + URL_SECOND + "', {\n"
578             + "        method: 'POST',\n"
579             + "        body: searchParams\n"
580             + "      })"
581             + "        .then(response => {\n"
582             + "          log(response.status);\n"
583             + "          log(response.statusText);\n"
584             + "          log(response.ok);\n"
585             + "         })\n"
586             + "        .catch(e => logEx(e));\n"
587             + "    </script>\n"
588             + "  </body>\n"
589             + "</html>";
590 
591         getMockWebConnection().setResponse(URL_SECOND, "HtmlUnit", MimeType.TEXT_PLAIN);
592 
593         final WebDriver driver = enableFetchPolyfill();
594         loadPage2(html);
595         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
596 
597         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
598         assertEquals(URL_SECOND, lastRequest.getUrl());
599 
600         String headerContentType = lastRequest.getAdditionalHeaders().get(HttpHeader.CONTENT_TYPE);
601         headerContentType = headerContentType.split(";")[0];
602         assertEquals("application/x-www-form-urlencoded", headerContentType);
603 
604         final List<NameValuePair> params = lastRequest.getRequestParameters();
605         assertEquals(2, params.size());
606         assertEquals("q", params.get(0).getName());
607         assertEquals("HtmlUnit", params.get(0).getValue());
608         assertEquals("page", params.get(1).getName());
609         assertEquals("1", params.get(1).getValue());
610     }
611 
612     /**
613      * @throws Exception if the test fails
614      */
615     @Test
616     @Alerts({"200", "OK", "true"})
617     public void fetchPostJSON() throws Exception {
618         final String html = DOCTYPE_HTML
619             + "<html>\n"
620             + "  <body>\n"
621             + "    <script>\n"
622             + LOG_TITLE_FUNCTION_NORMALIZE
623             + "      let jsonData = {hello: 'world'};\n"
624             + "      fetch('" + URL_SECOND + "', {\n"
625             + "        method: 'POST',\n"
626             + "        headers: {\n"
627             + "          'Content-Type': 'application/json'\n"
628             + "        },\n"
629             + "        body: JSON.stringify(jsonData)\n"
630             + "      })"
631             + "        .then(response => {\n"
632             + "          log(response.status);\n"
633             + "          log(response.statusText);\n"
634             + "          log(response.ok);\n"
635             + "         })\n"
636             + "        .catch(e => logEx(e));\n"
637             + "    </script>\n"
638             + "  </body>\n"
639             + "</html>";
640 
641         getMockWebConnection().setResponse(URL_SECOND, "HtmlUnit", MimeType.TEXT_PLAIN);
642 
643         final WebDriver driver = enableFetchPolyfill();
644         loadPage2(html);
645         verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
646 
647         final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
648         assertEquals(URL_SECOND, lastRequest.getUrl());
649 
650         String headerContentType = lastRequest.getAdditionalHeaders().get(HttpHeader.CONTENT_TYPE);
651         headerContentType = headerContentType.split(";")[0];
652         assertEquals("application/json", headerContentType);
653 
654         assertEquals("{\"hello\":\"world\"}", lastRequest.getRequestBody());
655     }
656 
657 //    /**
658 //     * Tests fetch with credentials.
659 //     * @throws Exception if the test fails
660 //     */
661 //    @Test
662 //    @Alerts("ok")
663 //    public void fetchWithCredentials() throws Exception {
664 //        final String html = DOCTYPE_HTML
665 //            + "<html><head>\n"
666 //            + "<script>\n"
667 //            + LOG_TITLE_FUNCTION
668 //            + "function test() {\n"
669 //            + "  fetch('" + URL_SECOND + "', {\n"
670 //            + "    method: 'GET',\n"
671 //            + "    credentials: 'include'\n"
672 //            + "  })\n"
673 //            + "    .then(response => log(response.ok ? 'ok' : 'not ok'))\n"
674 //            + "    .catch(e => logEx(e));\n"
675 //            + "}\n"
676 //            + "</script>\n"
677 //            + "</head>\n"
678 //            + "<body onload='test()'></body></html>";
679 //
680 //        getMockWebConnection().setDefaultResponse("<response/>\n", MimeType.TEXT_XML);
681 //        final WebDriver driver = loadPage2(html);
682 //        verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
683 //    }
684 //
685 //    /**
686 //     * Tests fetch response cloning.
687 //     * @throws Exception if the test fails
688 //     */
689 //    @Test
690 //    @Alerts({"true", "text1", "text2"})
691 //    public void fetchResponseClone() throws Exception {
692 //        final String html = DOCTYPE_HTML
693 //            + "<html><head>\n"
694 //            + "<script>\n"
695 //            + LOG_TITLE_FUNCTION
696 //            + "function test() {\n"
697 //            + "  fetch('" + URL_SECOND + "')\n"
698 //            + "    .then(response => {\n"
699 //            + "      var cloned = response.clone();\n"
700 //            + "      log(cloned.ok);\n"
701 //            + "      return Promise.all([\n"
702 //            + "        response.text(),\n"
703 //            + "        cloned.text()\n"
704 //            + "      ]);\n"
705 //            + "    })\n"
706 //            + "    .then(texts => {\n"
707 //            + "      log('text' + (texts[0] === texts[1] ? '1' : '0'));\n"
708 //            + "      log('text2');\n"
709 //            + "    })\n"
710 //            + "    .catch(e => logEx(e));\n"
711 //            + "}\n"
712 //            + "</script>\n"
713 //            + "</head>\n"
714 //            + "<body onload='test()'></body></html>";
715 //
716 //        getMockWebConnection().setDefaultResponse("response body", MimeType.TEXT_PLAIN);
717 //        final WebDriver driver = loadPage2(html);
718 //        verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
719 //    }
720 //
721 //    /**
722 //     * Tests fetch with mode option.
723 //     * @throws Exception if the test fails
724 //     */
725 //    @Test
726 //    @Alerts("ok")
727 //    public void fetchWithMode() throws Exception {
728 //        final String html = DOCTYPE_HTML
729 //            + "<html><head>\n"
730 //            + "<script>\n"
731 //            + LOG_TITLE_FUNCTION
732 //            + "function test() {\n"
733 //            + "  fetch('" + URL_SECOND + "', {\n"
734 //            + "    method: 'GET',\n"
735 //            + "    mode: 'cors'\n"
736 //            + "  })\n"
737 //            + "    .then(response => log(response.ok ? 'ok' : 'not ok'))\n"
738 //            + "    .catch(e => logEx(e));\n"
739 //            + "}\n"
740 //            + "</script>\n"
741 //            + "</head>\n"
742 //            + "<body onload='test()'></body></html>";
743 //
744 //        getMockWebConnection().setDefaultResponse("<response/>\n", MimeType.TEXT_XML);
745 //        final WebDriver driver = loadPage2(html);
746 //        verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
747 //    }
748 //
749 //    /**
750 //     * Tests fetch with cache option.
751 //     * @throws Exception if the test fails
752 //     */
753 //    @Test
754 //    @Alerts("ok")
755 //    public void fetchWithCache() throws Exception {
756 //        final String html = DOCTYPE_HTML
757 //            + "<html><head>\n"
758 //            + "<script>\n"
759 //            + LOG_TITLE_FUNCTION
760 //            + "function test() {\n"
761 //            + "  fetch('" + URL_SECOND + "', {\n"
762 //            + "    method: 'GET',\n"
763 //            + "    cache: 'no-cache'\n"
764 //            + "  })\n"
765 //            + "    .then(response => log(response.ok ? 'ok' : 'not ok'))\n"
766 //            + "    .catch(e => logEx(e));\n"
767 //            + "}\n"
768 //            + "</script>\n"
769 //            + "</head>\n"
770 //            + "<body onload='test()'></body></html>";
771 //
772 //        getMockWebConnection().setDefaultResponse("<response/>\n", MimeType.TEXT_XML);
773 //        final WebDriver driver = loadPage2(html);
774 //        verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
775 //    }
776 //
777 //    /**
778 //     * Tests fetch response with text encoding.
779 //     * @throws Exception if the test fails
780 //     */
781 //    @Test
782 //    @Alerts("olé")
783 //    public void fetchResponseTextEncoding() throws Exception {
784 //        final String html = DOCTYPE_HTML
785 //            + "<html>\n"
786 //            + "  <head>\n"
787 //            + "    <script>\n"
788 //            + LOG_TITLE_FUNCTION
789 //            + "      function test() {\n"
790 //            + "        fetch('" + URL_SECOND + "')\n"
791 //            + "          .then(response => response.text())\n"
792 //            + "          .then(text => log(text))\n"
793 //            + "          .catch(e => logEx(e));\n"
794 //            + "      }\n"
795 //            + "    </script>\n"
796 //            + "  </head>\n"
797 //            + "  <body onload='test()'>\n"
798 //            + "  </body>\n"
799 //            + "</html>";
800 //
801 //        final String response = "olé";
802 //        final byte[] responseBytes = response.getBytes(UTF_8);
803 //
804 //        getMockWebConnection().setResponse(URL_SECOND, responseBytes, 200, "OK",
805 //            MimeType.TEXT_HTML, new ArrayList<>());
806 //        final WebDriver driver = loadPage2(html);
807 //        verifyTitle2(DEFAULT_WAIT_TIME, driver, getExpectedAlerts());
808 //    }
809 
810     private WebDriver enableFetchPolyfill() {
811         final WebDriver driver = getWebDriver();
812         if (driver instanceof HtmlUnitDriver) {
813             ((HtmlUnitDriver) driver).getWebClient().getOptions().setFetchPolyfillEnabled(true);
814         }
815         return driver;
816     }
817 }