com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest2Test.java Source code

Java tutorial

Introduction

Here is the source code for com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequest2Test.java

Source

/*
 * Copyright (c) 2002-2016 Gargoyle Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gargoylesoftware.htmlunit.javascript.host.xml;

import static com.gargoylesoftware.htmlunit.BrowserRunner.Browser.IE;

import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.gargoylesoftware.htmlunit.BrowserRunner;
import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts;
import com.gargoylesoftware.htmlunit.BrowserRunner.BuggyWebDriver;
import com.gargoylesoftware.htmlunit.BrowserRunner.NotYetImplemented;
import com.gargoylesoftware.htmlunit.WebDriverTestCase;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.javascript.host.xml.XMLHttpRequestTest.BasicAuthenticationServlet;
import com.gargoylesoftware.htmlunit.util.NameValuePair;

/**
 * Additional tests for {@link XMLHttpRequest} using already WebDriverTestCase.
 *
 * @author Marc Guillemot
 * @author Ahmed Ashour
 * @author Ronald Brill
 * @author Sebastian Cato
 * @author Frank Danek
 */
@RunWith(BrowserRunner.class)
public class XMLHttpRequest2Test extends WebDriverTestCase {
    static String XHRInstantiation_ = "(window.XMLHttpRequest ? "
            + "new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'))";

    /**
     * This produced a deadlock situation with HtmlUnit-2.6 and HttmlUnit-2.7-SNAPSHOT on 17.09.09.
     * The reason is that HtmlUnit has currently one "JS execution thread" per window, synchronizing on the
     * owning page BUT the XHR callback execution are synchronized on their "owning" page.
     * This test isn't really executed now to avoid the deadlock.
     * Strangely, this test seem to fail even without the implementation of the "/setStateXX" handling
     * on the "server side".
     * Strange thing.
     *
     * Update 28.01.2013:
     * no deadlock occur anymore (we use a single JS execution thread for a while). Activating the test as it may help.
     * Update 28.02.2013:
     * deadlock does occur (at least on the build server). Disabling the test again.
     *
     * @throws Exception if the test fails
     */
    @Test
    @Ignore
    public void deadlock() throws Exception {
        final String jsCallSynchXHR = "function callSynchXHR(url) {\n" + "  var xhr = " + XHRInstantiation_ + ";\n"
                + "  xhr.open('GET', url, false);\n" + "  xhr.send('');\n" + "}\n";
        final String jsCallASynchXHR = "function callASynchXHR(url) {\n" + "  var xhr = " + XHRInstantiation_
                + ";\n" + "  var handler = function() {\n" + "    if (xhr.readyState == 4)\n"
                + "      alert(xhr.responseText);\n" + "  }\n" + "  xhr.onreadystatechange = handler;\n"
                + "  xhr.open('GET', url, true);\n" + "  xhr.send('');\n" + "}\n";
        final String html = "<html><head><script>\n" + jsCallSynchXHR + jsCallASynchXHR + "function testMain() {\n"
                + "  // set state 1 and wait for state 2\n" + "  callSynchXHR('/setState1/setState3')\n"
                + "  // call function with XHR and handler in frame\n"
                + "  myFrame.contentWindow.callASynchXHR('/fooCalledFromFrameCalledFromMain');\n" + "}\n"
                + "</script></head>\n" + "<body onload='testMain()'>\n"
                + "<iframe id='myFrame' src='frame.html'></iframe>\n" + "</body></html>";

        final String frame = "<html><head><script>\n" + jsCallSynchXHR + jsCallASynchXHR
                + "function testFrame() {\n" + "  // set state 2\n" + "  callSynchXHR('/setState2')\n"
                + "  // call function with XHR and handler in parent\n"
                + "  parent.callASynchXHR('/fooCalledFromMainCalledFromFrame');\n" + "}\n"
                + "setTimeout(testFrame, 10);\n" + "</script></head>\n" + "<body></body></html>";

        getMockWebConnection().setResponse(new URL(getDefaultUrl(), "frame.html"), frame);
        getMockWebConnection().setDefaultResponse(""); // for all XHR

        // just to avoid unused variable warning when the next line is commented
        getMockWebConnection().setResponse(getDefaultUrl(), html);
        loadPage2(html);
    }

    /**
     * @throws Exception if an error occurs
     */
    @Test
    public void setRequestHeader() throws Exception {
        final String html = "<html><head><script>\n" + "  function test() {\n" + "    var xhr = "
                + XHRInstantiation_ + ";\n" + "    xhr.open('GET', 'second.html', false);\n"
                + "    xhr.setRequestHeader('Accept', 'text/javascript, application/javascript, */*');\n"
                + "    xhr.setRequestHeader('Accept-Language', 'ar-eg');\n" + "    xhr.send('');\n" + "  }\n"
                + "</script></head><body onload='test()'></body></html>";

        getMockWebConnection().setDefaultResponse("");
        loadPage2(html);

        final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
        final Map<String, String> headers = lastRequest.getAdditionalHeaders();
        assertEquals("text/javascript, application/javascript, */*", headers.get("Accept"));
        assertEquals("ar-eg", headers.get("Accept-Language"));
    }

    /**
     * Content-Length header is simply ignored by browsers as it
     * is the browser's responsibility to set it.
     * @throws Exception if an error occurs
     */
    @Test
    public void requestHeader_contentLength() throws Exception {
        requestHeader_contentLength("1234");
        requestHeader_contentLength("11");
        requestHeader_contentLength(null);
    }

    private void requestHeader_contentLength(final String headerValue) throws Exception {
        final String body = "hello world";
        final String setHeader = headerValue == null ? "" : "xhr.setRequestHeader('Content-length', 1234);\n";
        final String html = "<html><body><script>\n" + "var xhr = " + XHRInstantiation_ + ";\n"
                + "xhr.open('POST', 'second.html', false);\n" + "var body = '" + body + "';\n"
                + "xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n" + setHeader
                + "xhr.send(body);\n" + "</script></body></html>";

        getMockWebConnection().setDefaultResponse("");
        loadPage2(html);

        final WebRequest lastRequest = getMockWebConnection().getLastWebRequest();
        final Map<String, String> headers = lastRequest.getAdditionalHeaders();
        assertEquals("" + body.length(), headers.get("Content-Length"));
    }

    /**
     * XHR.open throws an exception if URL parameter is null or empty string.
     * @throws Exception if an error occurs
     */
    @Test
    @Alerts(DEFAULT = { "5", "pass", "pass", "pass", "pass" }, IE = { "1", "exception", "exception", "pass",
            "pass" })
    @NotYetImplemented(IE)
    // real IE11 invokes just one request and returns the other two responses from it's cache
    public void openThrowOnEmptyUrl() throws Exception {
        final String html = "<html><head>\n" + "<script>\n" + "var xhr = " + XHRInstantiation_ + ";\n"
                + "var values = [null, '', ' ', '  \\t  '];\n" + "for (var i = 0; i < values.length; i++) {\n"
                + "  try {\n" + "    xhr.open('GET', values[i], false);\n" + "    xhr.send('');\n"
                + "    alert('pass');\n" + "  } catch(e) { alert('exception') }\n" + "}\n" + "</script>\n"
                + "</head>\n" + "<body></body>\n</html>";

        final int expectedRequests = Integer.parseInt(getExpectedAlerts()[0]);
        setExpectedAlerts(Arrays.copyOfRange(getExpectedAlerts(), 1, getExpectedAlerts().length));

        loadPageWithAlerts2(html);

        assertEquals(expectedRequests, getMockWebConnection().getRequestCount());
    }

    /**
     * Test access to the XML DOM.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts({ "1", "bla", "someAttr", "someValue", "true", "foo", "2", "fi1" })
    // TODO [IE11]SINGLE-VS-BULK test runs when executed as single but breaks as bulk
    public void responseXML() throws Exception {
        testResponseXML("text/xml");
        testResponseXML(null);
    }

    /**
     * Test access to responseXML when the content type indicates that it is not XML.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts("null")
    // TODO [IE11]SINGLE-VS-BULK test runs when executed as single but breaks as bulk
    public void responseXML_badContentType() throws Exception {
        final String html = "<html><head>\n" + "<script>\n" + "function test() {\n" + "  var xhr = "
                + XHRInstantiation_ + ";\n" + "  xhr.open('GET', 'foo.xml', false);\n" + "  xhr.send('');\n"
                + "  alert(xhr.responseXML);\n" + "}\n" + "</script>\n" + "</head>\n"
                + "<body onload='test()'></body></html>";

        final URL urlFoo = new URL(URL_FIRST + "foo.xml");
        getMockWebConnection().setResponse(urlFoo,
                "<bla someAttr='someValue'><foo><fi id='fi1'/><fi/></foo></bla>\n", "text/plain");
        loadPageWithAlerts2(html);
    }

    private void testResponseXML(final String contentType) throws Exception {
        final String html = "<html><head>\n" + "<script>\n" + "function test() {\n" + "  var xhr = "
                + XHRInstantiation_ + ";\n" + "  xhr.open('GET', 'foo.xml', false);\n" + "  xhr.send('');\n"
                + "  var childNodes = xhr.responseXML.childNodes;\n" + "  alert(childNodes.length);\n"
                + "  var rootNode = childNodes[0];\n" + "  alert(rootNode.nodeName);\n"
                + "  alert(rootNode.attributes[0].nodeName);\n" + "  alert(rootNode.attributes[0].nodeValue);\n"
                + "  alert(rootNode.attributes['someAttr'] == rootNode.attributes[0]);\n"
                + "  alert(rootNode.firstChild.nodeName);\n" + "  alert(rootNode.firstChild.childNodes.length);\n"
                + "  alert(xhr.responseXML.getElementsByTagName('fi').item(0).attributes[0].nodeValue);\n" + "}\n"
                + "</script>\n" + "</head>\n" + "<body onload='test()'></body></html>";

        final URL urlFoo = new URL(URL_FIRST + "foo.xml");
        getMockWebConnection().setResponse(urlFoo,
                "<bla someAttr='someValue'><foo><fi id='fi1'/><fi/></foo></bla>\n", contentType);
        loadPageWithAlerts2(html);
    }

    /**
     * Test access to responseXML when the content type indicates that it is not XML.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts("null")
    public void responseXML_sendNotCalled() throws Exception {
        final String html = "<html><head>\n" + "<script>\n" + "function test() {\n" + "  var xhr = "
                + XHRInstantiation_ + ";\n" + "  xhr.open('GET', 'foo.xml', false);\n"
                + "  alert(xhr.responseXML);\n" + "}\n" + "</script>\n" + "</head>\n"
                + "<body onload='test()'></body></html>";

        final URL urlFoo = new URL(URL_FIRST + "foo.xml");
        getMockWebConnection().setResponse(urlFoo,
                "<bla someAttr='someValue'><foo><fi id='fi1'/><fi/></foo></bla>\n", "text/plain");
        loadPageWithAlerts2(html);
    }

    /**
     * Test for Bug 2891430: HtmlUnit should not violate the same-origin policy with FF3.
     * Note: FF3.5 doesn't enforce this same-origin policy.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts("ok")
    public void sameOriginPolicy() throws Exception {
        sameOriginPolicy(URL_THIRD.toString());
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    @Alerts(DEFAULT = "ok", IE = "exception")
    public void sameOriginPolicy_aboutBlank() throws Exception {
        sameOriginPolicy("about:blank");
    }

    private void sameOriginPolicy(final String url) throws Exception {
        final String html = "<html><head>\n" + "<script>\n" + "function test() {\n" + "  var xhr = "
                + XHRInstantiation_ + ";\n" + "  try {\n" + "    xhr.open('GET', '" + url + "', false);\n"
                + "    alert('ok');\n" + "  } catch(e) { alert('exception'); }\n" + "}\n" + "</script>\n"
                + "</head>\n" + "<body onload='test()'></body></html>";

        getMockWebConnection().setResponse(URL_THIRD, "<bla/>", "text/xml");
        loadPageWithAlerts2(html);
    }

    /**
     * @throws Exception if an error occurs
     */
    @Test
    public void put() throws Exception {
        final String html = "<html><head><script>\n" + "  function test() {\n" + "    var xhr = "
                + XHRInstantiation_ + ";\n" + "    xhr.open('PUT', 'second.html', false);\n"
                + "    xhr.send('Something');\n" + "  }\n" + "</script></head><body onload='test()'></body></html>";

        getMockWebConnection().setDefaultResponse("");
        loadPage2(html);

        final String requestBody = getMockWebConnection().getLastWebRequest().getRequestBody();
        assertEquals("Something", requestBody);
    }

    /**
     * Regression test for bug 2952333.
     * This test was causing a java.lang.ClassCastException:
     * com.gargoylesoftware.htmlunit.xml.XmlPage cannot be cast to com.gargoylesoftware.htmlunit.html.HtmlPage
     * @throws Exception if an error occurs
     */
    @Test
    @Alerts("[object XMLDocument]")
    public void iframeInResponse() throws Exception {
        final String html = "<html><head><script>\n" + "var xhr = " + XHRInstantiation_ + ";\n"
                + "xhr.open('GET', 'foo.xml', false);\n" + "xhr.send('');\n" + "alert(xhr.responseXML);\n"
                + "</script></head><body></body></html>";

        final String xml = "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
                + "<body><iframe></iframe></body></html>";
        getMockWebConnection().setDefaultResponse(xml, "text/xml");
        loadPageWithAlerts2(html);
    }

    /**
     * Ensures that XHR download is performed without altering other JS jobs.
     * Currently HtmlUnit doesn't behave correctly here because download and callback execution
     * are executed within the same synchronize block on the HtmlPage.
     * @throws Exception if an error occurs
     */
    @Test
    @Alerts({ "in timeout", "hello" })
    @NotYetImplemented
    // TODO [IE11]SINGLE-VS-BULK test runs when executed as single but breaks as bulk
    public void xhrDownloadInBackground() throws Exception {
        final String html = "<html><head><script>\n" + "var xhr = " + XHRInstantiation_ + ";\n"
                + "var handler = function() {\n" + "  if (xhr.readyState == 4)\n" + "    alert(xhr.responseText);\n"
                + "}\n" + "xhr.onreadystatechange = handler;\n" + "xhr.open('GET', '/delay200/foo.txt', true);\n"
                + "xhr.send('');\n" + "setTimeout(function(){ alert('in timeout');}, 5);\n"
                + "</script></head><body></body></html>";

        getMockWebConnection().setDefaultResponse("hello", "text/plain");
        loadPageWithAlerts2(html);
    }

    /**
     * Ensures that XHR callback is executed before a timeout, even if it is time
     * to execute this one.
     * @throws Exception if an error occurs
     */
    @Test
    @Alerts({ "hello", "in timeout" })
    @BuggyWebDriver(IE)
    // IEDriver catches "in timeout", "hello" but real IE11 gets the correct order
    public void xhrCallbackBeforeTimeout() throws Exception {
        final String html = "<html><head><script>\n" + "function wait() {\n" + "  var xhr = " + XHRInstantiation_
                + ";\n" + "  xhr.open('GET', '/delay200/foo.txt', false);\n" + "  xhr.send('');\n" + "}\n"
                + "function doTest() {\n" + "  setTimeout(function(){ alert('in timeout');}, 5);\n" + "  wait();\n"
                + "  var xhr2 = " + XHRInstantiation_ + ";\n" + "  var handler = function() {\n"
                + "    if (xhr2.readyState == 4)\n" + "      alert(xhr2.responseText);\n" + "  }\n"
                + "  xhr2.onreadystatechange = handler;\n" + "  xhr2.open('GET', '/foo.txt', true);\n"
                + "  xhr2.send('');\n" + "  wait();\n" + "}\n" + "setTimeout(doTest, 10);\n"
                + "</script></head><body></body></html>";

        getMockWebConnection().setDefaultResponse("hello", "text/plain");
        loadPageWithAlerts2(html, 2000);
    }

    /**
     * @throws Exception if an error occurs
     */
    @Test
    @Alerts("a=b,0")
    public void post() throws Exception {
        final String html = "<html><head><script>\n" + "function test() {\n" + "  var xhr = " + XHRInstantiation_
                + ";\n" + "  xhr.open('POST', '/test2?a=b', false);\n" + "  xhr.send('');\n"
                + "  alert(xhr.responseText);\n" + "}\n" + "</script></head><body onload='test()'></body></html>";

        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test2", PostServlet2.class);

        loadPageWithAlerts2(html, servlets);
    }

    /**
     * Servlet for {@link #post()}.
     */
    public static class PostServlet2 extends HttpServlet {

        @Override
        protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
                throws ServletException, IOException {
            final Writer writer = resp.getWriter();
            writer.write(req.getQueryString() + ',' + req.getContentLength());
            writer.close();
        }
    }

    /**
     * Firefox up to 3.6 does not call "onreadystatechange" handler if sync.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts("4")
    public void testOnreadystatechange_sync() throws Exception {
        final String html = "<html>\n" + "  <head>\n" + "    <title>XMLHttpRequest Test</title>\n"
                + "    <script>\n" + "      var xhr;\n" + "      function test() {\n" + "        xhr = "
                + XHRInstantiation_ + ";\n" + "        xhr.open('GET', '" + URL_SECOND + "', false);\n"
                + "        xhr.onreadystatechange = onStateChange;\n" + "        xhr.send('');\n" + "      }\n"
                + "      function onStateChange() {\n" + "        alert(xhr.readyState);\n" + "      }\n"
                + "    </script>\n" + "  </head>\n" + "  <body onload='test()'>\n" + "  </body>\n" + "</html>";

        final String xml = "<xml>\n" + "<content>blah</content>\n" + "<content>blah2</content>\n" + "</xml>";

        getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml");
        loadPageWithAlerts2(html);
    }

    /**
     * Firefox up to 3.6 does not call "onreadystatechange" handler if sync.
     * Firefox provides an event parameter.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts("[object Event]#[object XMLHttpRequest]")
    public void testOnreadystatechangeSyncWithParam() throws Exception {
        final String html = "<html>\n" + "  <head>\n" + "    <title>XMLHttpRequest Test</title>\n"
                + "    <script>\n" + "      var xhr;\n" + "      function test() {\n" + "        xhr = "
                + XHRInstantiation_ + ";\n" + "        xhr.open('GET', '" + URL_SECOND + "', false);\n"
                + "        xhr.onreadystatechange = onStateChange;\n" + "        xhr.send('');\n" + "      }\n"
                + "      function onStateChange(e) {\n" + "        if (xhr.readyState == 4) {\n"
                + "          if(e) alert(e + '#' + e.target);\n" + "          else alert('no param');\n"
                + "        }\n" + "      }\n" + "    </script>\n" + "  </head>\n" + "  <body onload='test()'>\n"
                + "  </body>\n" + "</html>";

        final String xml = "<xml>\n" + "<content>blah</content>\n" + "</xml>";

        getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml");
        loadPageWithAlerts2(html);
    }

    /**
     * Firefox up to 3.6 does not call "onreadystatechange" handler if sync.
     * Firefox provides an event parameter.
     * @throws Exception if the test fails
     */
    @Test
    @Alerts("[object Event]#[object XMLHttpRequest]")
    public void testOnreadystatechangeAsyncWithParam() throws Exception {
        final String html = "<html>\n" + "  <head>\n" + "    <title>XMLHttpRequest Test</title>\n"
                + "    <script>\n" + "      var xhr;\n" + "      function test() {\n" + "        xhr = "
                + XHRInstantiation_ + ";\n" + "        xhr.open('GET', '" + URL_SECOND + "', true);\n"
                + "        xhr.onreadystatechange = onStateChange;\n" + "        xhr.send('');\n" + "      }\n"
                + "      function onStateChange(e) {\n" + "        if (xhr.readyState == 4) {\n"
                + "          if(e) alert(e + '#' + e.target);\n" + "          else alert('no param');\n"
                + "        }\n" + "      }\n" + "    </script>\n" + "  </head>\n" + "  <body onload='test()'>\n"
                + "  </body>\n" + "</html>";

        final String xml = "<xml>\n" + "<content>blah</content>\n" + "</xml>";

        getMockWebConnection().setResponse(URL_SECOND, xml, "text/xml");
        loadPageWithAlerts2(html, 2000);
    }

    /**
     * Test the simplest CORS case. A cross-origin simple request,
     * server replies "allow *".
     * @throws Exception if the test fails.
     */
    @Test
    @Alerts({ "ok", "4" })
    public void sameOriginCorsSimple() throws Exception {
        final String html = "<html><head>\n" + "<script>\n" + "function test() {\n" + "  var xhr = "
                + XHRInstantiation_ + ";\n" + "  try {\n" + "    xhr.open('GET', '" + URL_CROSS_ORIGIN
                + "', false);\n" + "    alert('ok');\n" + "    xhr.send();\n" + "    alert(xhr.readyState)\n"
                + "  } catch(e) { alert('exception'); }\n" + "}\n" + "</script>\n" + "</head>\n"
                + "<body onload='test()'></body></html>";

        final List<NameValuePair> responseHeaders = new ArrayList<>();
        responseHeaders.add(new NameValuePair("access-control-allow-origin", "*"));
        getMockWebConnection().setResponse(URL_CROSS_ORIGIN, "<empty/>", 200, "OK", "text/xml", "utf-8",
                responseHeaders);
        loadPageWithAlerts2(html);
    }

    @Override
    protected boolean needThreeConnections() {
        return true;
    }

    /**
     * Test XMLHttpRequest with basic authentication.
     * @throws Exception on failure
     */
    @Test
    @Alerts("Basic:Zm9vOmJhcg==")
    public void basicAuthenticationRequest() throws Exception {
        final String html = "<html>\n" + "  <head>\n" + "    <title>XMLHttpRequest Test</title>\n"
                + "    <script>\n" + "      var request;\n" + "      function testBasicAuth() {\n"
                + "        var request = " + XHRInstantiation_ + ";\n"
                + "        request.open('GET', '/protected/token', false, 'foo', 'bar');\n"
                + "        request.send();\n" + "        alert(request.responseText);\n" + "      }\n"
                + "    </script>\n" + "  </head>\n" + "  <body onload='testBasicAuth()'>\n" + "  </body>\n"
                + "</html>";

        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/protected/token", BasicAuthenticationServlet.class);

        loadPageWithAlerts2(html, servlets);
    }

    /**
     * Test XMLHttpRequest with basic authentication.
     * @throws Exception on failure
     */
    @Test
    @Alerts("<xml></xml>")
    public void openNullUserIdNullPassword() throws Exception {
        final String html = "<html>\n" + "  <head>\n" + "    <title>XMLHttpRequest Test</title>\n"
                + "    <script>\n" + "      var request;\n" + "      function testBasicAuth() {\n"
                + "        var request = " + XHRInstantiation_ + ";\n" + "        request.open('GET', '"
                + URL_SECOND + "', false, null, null);\n" + "        request.send();\n"
                + "        alert(request.responseText);\n" + "      }\n" + "    </script>\n" + "  </head>\n"
                + "  <body onload='testBasicAuth()'>\n" + "  </body>\n" + "</html>";

        getMockWebConnection().setResponse(URL_SECOND, "<xml></xml>", "text/xml");

        loadPageWithAlerts2(html);
    }

    /**
     * @throws Exception if an error occurs
     */
    @Test
    @Alerts("PATCH|some body data")
    public void patch() throws Exception {
        final String html = "<html><head><script>\n" + "function test() {\n" + "  var xhr = " + XHRInstantiation_
                + ";\n" + "  xhr.open('PATCH', '/test2', false);\n" + "  xhr.send('some body data');\n"
                + "  alert(xhr.responseText);\n" + "}\n" + "</script></head><body onload='test()'></body></html>";

        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test2", PatchServlet2.class);

        loadPageWithAlerts2(html, servlets);
    }

    /**
     * Servlet for {@link #patch()}.
     */
    public static class PatchServlet2 extends HttpServlet {

        @Override
        protected void service(final HttpServletRequest req, final HttpServletResponse resp)
                throws ServletException, IOException {
            final Writer writer = resp.getWriter();
            writer.write(req.getMethod());
            writer.write('|');
            writer.write(IOUtils.toString(req.getReader()));
            writer.close();
        }
    }

}