diff --git a/tests/auto/shared/httpreqrep.cpp b/tests/auto/shared/httpreqrep.cpp
index eb2db68901ac4bb72cf2c9f45440c893c44b8a35..15a86631cd846a534932fc740b553cc738b73fd0 100644
--- a/tests/auto/shared/httpreqrep.cpp
+++ b/tests/auto/shared/httpreqrep.cpp
@@ -31,11 +31,14 @@ HttpReqRep::HttpReqRep(QTcpSocket *socket, QObject *parent)
     : QObject(parent), m_socket(socket)
 {
     m_socket->setParent(this);
-    connect(m_socket, &QIODevice::readyRead, this, &HttpReqRep::handleReadyRead);
+    connect(m_socket, &QTcpSocket::readyRead, this, &HttpReqRep::handleReadyRead);
+    connect(m_socket, &QTcpSocket::disconnected, this, &HttpReqRep::handleDisconnected);
 }
 
 void HttpReqRep::sendResponse()
 {
+    if (m_state != State::REQUEST_RECEIVED)
+        return;
     m_socket->write("HTTP/1.1 ");
     m_socket->write(QByteArray::number(m_responseStatusCode));
     m_socket->write(" OK?\r\n");
@@ -45,9 +48,21 @@ void HttpReqRep::sendResponse()
         m_socket->write(kv.second);
         m_socket->write("\r\n");
     }
+    m_socket->write("Connection: close\r\n");
     m_socket->write("\r\n");
     m_socket->write(m_responseBody);
-    m_socket->close();
+    m_state = State::DISCONNECTING;
+    m_socket->disconnectFromHost();
+    Q_EMIT responseSent();
+}
+
+void HttpReqRep::close()
+{
+    if (m_state != State::REQUEST_RECEIVED)
+        return;
+    m_state = State::DISCONNECTING;
+    m_socket->disconnectFromHost();
+    Q_EMIT error(QStringLiteral("missing response"));
 }
 
 QByteArray HttpReqRep::requestHeader(const QByteArray &key) const
@@ -60,32 +75,60 @@ QByteArray HttpReqRep::requestHeader(const QByteArray &key) const
 
 void HttpReqRep::handleReadyRead()
 {
-    const auto requestLine = m_socket->readLine();
-    const auto requestLineParts = requestLine.split(' ');
-    if (requestLineParts.size() != 3 || !requestLineParts[2].toUpper().startsWith("HTTP/")) {
-        qWarning("HttpReqRep: invalid request line");
-        Q_EMIT readFinished(false);
-        return;
-    }
-
-    decltype(m_requestHeaders) headers;
-    for (;;) {
-        const auto headerLine = m_socket->readLine();
-        if (headerLine == QByteArrayLiteral("\r\n"))
+    while (m_socket->canReadLine()) {
+        switch (m_state) {
+        case State::RECEIVING_REQUEST: {
+            const auto requestLine = m_socket->readLine();
+            const auto requestLineParts = requestLine.split(' ');
+            if (requestLineParts.size() != 3 || !requestLineParts[2].toUpper().startsWith("HTTP/")) {
+                m_state = State::DISCONNECTING;
+                m_socket->disconnectFromHost();
+                Q_EMIT error(QStringLiteral("invalid request line"));
+                return;
+            }
+            m_requestMethod = requestLineParts[0];
+            m_requestPath = requestLineParts[1];
+            m_state = State::RECEIVING_HEADERS;
+            break;
+        }
+        case State::RECEIVING_HEADERS: {
+            const auto headerLine = m_socket->readLine();
+            if (headerLine == QByteArrayLiteral("\r\n")) {
+                m_state = State::REQUEST_RECEIVED;
+                Q_EMIT requestReceived();
+                return;
+            }
+            int colonIndex = headerLine.indexOf(':');
+            if (colonIndex < 0) {
+                m_state = State::DISCONNECTING;
+                m_socket->disconnectFromHost();
+                Q_EMIT error(QStringLiteral("invalid header line"));
+                return;
+            }
+            auto headerKey = headerLine.left(colonIndex).trimmed().toLower();
+            auto headerValue = headerLine.mid(colonIndex + 1).trimmed().toLower();
+            m_requestHeaders.emplace(headerKey, headerValue);
             break;
-        int colonIndex = headerLine.indexOf(':');
-        if (colonIndex < 0) {
-            qWarning("HttpReqRep: invalid header line");
-            Q_EMIT readFinished(false);
+        }
+        default:
             return;
         }
-        auto headerKey = headerLine.left(colonIndex).trimmed().toLower();
-        auto headerValue = headerLine.mid(colonIndex + 1).trimmed().toLower();
-        headers.emplace(headerKey, headerValue);
     }
+}
 
-    m_requestMethod = requestLineParts[0];
-    m_requestPath = requestLineParts[1];
-    m_requestHeaders = headers;
-    Q_EMIT readFinished(true);
+void HttpReqRep::handleDisconnected()
+{
+    switch (m_state) {
+    case State::RECEIVING_REQUEST:
+    case State::RECEIVING_HEADERS:
+    case State::REQUEST_RECEIVED:
+        Q_EMIT error(QStringLiteral("unexpected disconnect"));
+        break;
+    case State::DISCONNECTING:
+        break;
+    case State::DISCONNECTED:
+        Q_UNREACHABLE();
+    }
+    m_state = State::DISCONNECTED;
+    Q_EMIT closed();
 }
diff --git a/tests/auto/shared/httpreqrep.h b/tests/auto/shared/httpreqrep.h
index 4e9f10dff3c5b56b3d1e2c9f3ba87c54938d35dd..bee8119eb7dbe9d6f3276d6e406b393bce2a6a23 100644
--- a/tests/auto/shared/httpreqrep.h
+++ b/tests/auto/shared/httpreqrep.h
@@ -30,6 +30,7 @@
 
 #include <QTcpSocket>
 
+#include <map>
 #include <utility>
 
 // Represents an HTTP request-response exchange.
@@ -37,11 +38,20 @@ class HttpReqRep : public QObject
 {
     Q_OBJECT
 public:
-    HttpReqRep(QTcpSocket *socket, QObject *parent = nullptr);
+    explicit HttpReqRep(QTcpSocket *socket, QObject *parent = nullptr);
+
     void sendResponse();
+    void close();
+
+    // Request parameters (only valid after requestReceived())
+
     QByteArray requestMethod() const { return m_requestMethod; }
     QByteArray requestPath() const { return m_requestPath; }
     QByteArray requestHeader(const QByteArray &key) const;
+
+    // Response parameters (can be set until sendResponse()/close()).
+
+    int responseStatus() const { return m_responseStatusCode; }
     void setResponseStatus(int statusCode)
     {
         m_responseStatusCode = statusCode;
@@ -50,6 +60,7 @@ public:
     {
         m_responseHeaders[key.toLower()] = std::move(value);
     }
+    QByteArray responseBody() const { return m_responseBody; }
     void setResponseBody(QByteArray content)
     {
         m_responseHeaders["content-length"] = QByteArray::number(content.size());
@@ -57,13 +68,34 @@ public:
     }
 
 Q_SIGNALS:
-    void readFinished(bool ok);
+    // Emitted when the request has been correctly parsed.
+    void requestReceived();
+    // Emitted on first call to sendResponse().
+    void responseSent();
+    // Emitted when something goes wrong.
+    void error(const QString &error);
+    // Emitted during or some time after sendResponse() or close().
+    void closed();
 
 private Q_SLOTS:
     void handleReadyRead();
+    void handleDisconnected();
 
 private:
+    enum class State {
+        // Waiting for first line of request.
+        RECEIVING_REQUEST,      // Next: RECEIVING_HEADERS or DISCONNECTING.
+        // Waiting for header lines.
+        RECEIVING_HEADERS,      // Next: REQUEST_RECEIVED or DISCONNECTING.
+        // Request parsing succeeded, waiting for sendResponse() or close().
+        REQUEST_RECEIVED,       // Next: DISCONNECTING.
+        // Waiting for network.
+        DISCONNECTING,          // Next: DISCONNECTED.
+        // Connection is dead.
+        DISCONNECTED,           // Next: -
+    };
     QTcpSocket *m_socket = nullptr;
+    State m_state = State::RECEIVING_REQUEST;
     QByteArray m_requestMethod;
     QByteArray m_requestPath;
     std::map<QByteArray, QByteArray> m_requestHeaders;
diff --git a/tests/auto/shared/httpserver.cpp b/tests/auto/shared/httpserver.cpp
index 6012379f2a68d63039f4808656d42db551166842..8d14c18ff6fcad409d2585a55b4735e9c8f858a1 100644
--- a/tests/auto/shared/httpserver.cpp
+++ b/tests/auto/shared/httpserver.cpp
@@ -27,44 +27,59 @@
 ****************************************************************************/
 #include "httpserver.h"
 
-#include "waitforsignal.h"
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(gHttpServerLog, "HttpServer")
 
 HttpServer::HttpServer(QObject *parent) : QObject(parent)
 {
     connect(&m_tcpServer, &QTcpServer::newConnection, this, &HttpServer::handleNewConnection);
-    if (!m_tcpServer.listen())
-        qWarning("HttpServer: listen() failed");
-    m_url = QStringLiteral("http://127.0.0.1:") + QString::number(m_tcpServer.serverPort());
 }
 
-QUrl HttpServer::url(const QString &path) const
+bool HttpServer::start()
 {
-    auto copy = m_url;
-    copy.setPath(path);
-    return copy;
+    m_error = false;
+
+    if (!m_tcpServer.listen()) {
+        qCWarning(gHttpServerLog).noquote() << m_tcpServer.errorString();
+        return false;
+    }
+
+    m_url.setScheme(QStringLiteral("http"));
+    m_url.setHost(QStringLiteral("127.0.0.1"));
+    m_url.setPort(m_tcpServer.serverPort());
+
+    return true;
 }
 
-void HttpServer::handleNewConnection()
+bool HttpServer::stop()
 {
-    auto reqRep = new HttpReqRep(m_tcpServer.nextPendingConnection(), this);
-    connect(reqRep, &HttpReqRep::readFinished, this, &HttpServer::handleReadFinished);
+    m_tcpServer.close();
+    return !m_error;
 }
 
-void HttpServer::handleReadFinished(bool ok)
+QUrl HttpServer::url(const QString &path) const
 {
-    auto reqRep = qobject_cast<HttpReqRep *>(sender());
-    if (ok)
-        Q_EMIT newRequest(reqRep);
-    else
-        reqRep->deleteLater();
+    auto copy = m_url;
+    copy.setPath(path);
+    return copy;
 }
 
-std::unique_ptr<HttpReqRep> waitForRequest(HttpServer *server)
+void HttpServer::handleNewConnection()
 {
-    std::unique_ptr<HttpReqRep> result;
-    waitForSignal(server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
-        rr->setParent(nullptr);
-        result.reset(rr);
+    auto rr = new HttpReqRep(m_tcpServer.nextPendingConnection(), this);
+    connect(rr, &HttpReqRep::requestReceived, [this, rr]() {
+        Q_EMIT newRequest(rr);
+        rr->close();
+    });
+    connect(rr, &HttpReqRep::responseSent, [this, rr]() {
+        qCInfo(gHttpServerLog).noquote() << rr->requestMethod() << rr->requestPath()
+                                         << rr->responseStatus() << rr->responseBody().size();
+    });
+    connect(rr, &HttpReqRep::error, [this, rr](const QString &error) {
+        qCWarning(gHttpServerLog).noquote() << rr->requestMethod() << rr->requestPath()
+                                            << error;
+        m_error = true;
     });
-    return result;
+    connect(rr, &HttpReqRep::closed, rr, &QObject::deleteLater);
 }
diff --git a/tests/auto/shared/httpserver.h b/tests/auto/shared/httpserver.h
index e45743b7b0e19aba95e7eca43d2f176182a746d9..ddbab433ccd0db45a5896f928617b2a78d693462 100644
--- a/tests/auto/shared/httpserver.h
+++ b/tests/auto/shared/httpserver.h
@@ -33,29 +33,55 @@
 #include <QTcpServer>
 #include <QUrl>
 
-#include <memory>
-
 // Listens on a TCP socket and creates HttpReqReps for each connection.
+//
+// Usage:
+//
+//     HttpServer server;
+//     connect(&server, &HttpServer::newRequest, [](HttpReqRep *rr) {
+//         if (rr->requestPath() == "/myPage.html") {
+//             rr->setResponseBody("<html><body>Hello, World!</body></html>");
+//             rr->sendResponse();
+//         }
+//     });
+//     QVERIFY(server.start());
+//     /* do stuff */
+//     QVERIFY(server.stop());
+//
+// HttpServer owns the HttpReqRep objects. The signal handler should not store
+// references to HttpReqRep objects.
+//
+// Only if a handler calls sendResponse() will a response be actually sent. This
+// means that multiple handlers can be connected to the signal, with different
+// handlers responsible for different paths.
 class HttpServer : public QObject
 {
     Q_OBJECT
+public:
+    explicit HttpServer(QObject *parent = nullptr);
 
-    QTcpServer m_tcpServer;
-    QUrl m_url;
+    // Must be called to start listening.
+    //
+    // Returns true if a TCP port has been successfully bound.
+    Q_REQUIRED_RESULT bool start();
 
-public:
-    HttpServer(QObject *parent = nullptr);
+    // Stops listening and performs final error checks.
+    Q_REQUIRED_RESULT bool stop();
+
+    // Full URL for given relative path
     QUrl url(const QString &path = QStringLiteral("/")) const;
 
 Q_SIGNALS:
+    // Emitted after a HTTP request has been successfully parsed.
     void newRequest(HttpReqRep *reqRep);
 
 private Q_SLOTS:
     void handleNewConnection();
-    void handleReadFinished(bool ok);
-};
 
-// Wait for an HTTP request to be received.
-std::unique_ptr<HttpReqRep> waitForRequest(HttpServer *server);
+private:
+    QTcpServer m_tcpServer;
+    QUrl m_url;
+    bool m_error = false;
+};
 
 #endif // !HTTPSERVER_H
diff --git a/tests/auto/shared/waitforsignal.h b/tests/auto/shared/waitforsignal.h
deleted file mode 100644
index 9b2daf7af03c5033df955227517de5773927c5f7..0000000000000000000000000000000000000000
--- a/tests/auto/shared/waitforsignal.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtWebEngine module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-#ifndef WAITFORSIGNAL_H
-#define WAITFORSIGNAL_H
-
-#include <QObject>
-#include <QTestEventLoop>
-
-#include <utility>
-
-// Implementation details of waitForSignal.
-namespace {
-    // Wraps a functor to set a flag and exit from event loop if called.
-    template <class SignalHandler>
-    struct waitForSignal_SignalHandlerWrapper {
-        waitForSignal_SignalHandlerWrapper(SignalHandler &&sh)
-            : signalHandler(std::forward<SignalHandler>(sh)) {}
-
-        template <class... Args>
-        void operator()(Args && ... args) {
-            signalHandler(std::forward<Args>(args)...);
-            hasBeenCalled = true;
-            loop.exitLoop();
-        }
-
-        SignalHandler &&signalHandler;
-        QTestEventLoop loop;
-        bool hasBeenCalled = false;
-    };
-
-    // No-op functor.
-    struct waitForSignal_DefaultSignalHandler {
-        template <class... Args>
-        void operator()(Args && ...) {}
-    };
-} // namespace
-
-// Wait for a signal to be emitted.
-//
-// The given functor is called with the signal arguments allowing the arguments
-// to be modified before returning from the signal handler (unlike QSignalSpy).
-template <class Sender, class Signal, class SignalHandler>
-bool waitForSignal(Sender &&sender, Signal &&signal, SignalHandler &&signalHandler, int timeout = 15000)
-{
-    waitForSignal_SignalHandlerWrapper<SignalHandler> signalHandlerWrapper(
-        std::forward<SignalHandler>(signalHandler));
-    auto connection = QObject::connect(
-        std::forward<Sender>(sender),
-        std::forward<Signal>(signal),
-        std::ref(signalHandlerWrapper));
-    signalHandlerWrapper.loop.enterLoopMSecs(timeout);
-    QObject::disconnect(connection);
-    return signalHandlerWrapper.hasBeenCalled;
-}
-
-template <class Sender, class Signal>
-bool waitForSignal(Sender &&sender, Signal &&signal, int timeout = 15000)
-{
-    return waitForSignal(std::forward<Sender>(sender),
-                         std::forward<Signal>(signal),
-                         waitForSignal_DefaultSignalHandler(),
-                         timeout);
-}
-
-#endif // !WAITFORSIGNAL_H
diff --git a/tests/auto/widgets/qwebenginedownloads/BLACKLIST b/tests/auto/widgets/qwebenginedownloads/BLACKLIST
deleted file mode 100644
index 1bf8c8b1f9d483c517a63faca9cabb7bad9e9288..0000000000000000000000000000000000000000
--- a/tests/auto/widgets/qwebenginedownloads/BLACKLIST
+++ /dev/null
@@ -1,4 +0,0 @@
-[downloadLink]
-osx
-[downloadTwoLinks]
-*
diff --git a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp
index e8ac9676fc1aef0842d8f4f3911c96c00534d673..4848038df58644251f857928e9408c043f9fc991 100644
--- a/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp
+++ b/tests/auto/widgets/qwebenginedownloads/tst_qwebenginedownloads.cpp
@@ -36,50 +36,150 @@
 #include <QWebEngineProfile>
 #include <QWebEngineView>
 #include <httpserver.h>
-#include <waitforsignal.h>
-
-static std::unique_ptr<HttpReqRep> waitForFaviconRequest(HttpServer *server)
-{
-    auto rr = waitForRequest(server);
-    if (!rr ||
-        rr->requestMethod() != QByteArrayLiteral("GET") ||
-        rr->requestPath() != QByteArrayLiteral("/favicon.ico"))
-        return nullptr;
-    rr->setResponseStatus(404);
-    rr->sendResponse();
-    return std::move(rr);
-}
 
 class tst_QWebEngineDownloads : public QObject
 {
     Q_OBJECT
+
+public:
+    enum UserAction {
+        SaveLink,
+        ClickLink,
+    };
+
+    enum FileAction {
+        FileIsDownloaded,
+        FileIsDisplayed,
+    };
+
 private Q_SLOTS:
+    void initTestCase();
+    void init();
+    void cleanup();
+    void cleanupTestCase();
+
     void downloadLink_data();
     void downloadLink();
+    void downloadTwoLinks_data();
     void downloadTwoLinks();
     void downloadPage_data();
     void downloadPage();
     void downloadViaSetUrl();
     void downloadFileNot1();
     void downloadFileNot2();
-};
 
-enum DownloadTestUserAction {
-    SaveLink,
-    Navigate,
+private:
+    void saveLink(QPoint linkPos);
+    void clickLink(QPoint linkPos);
+    void simulateUserAction(QPoint linkPos, UserAction action);
+
+    QWebEngineDownloadItem::DownloadType expectedDownloadType(
+        UserAction userAction,
+        const QByteArray &contentDisposition = QByteArray());
+
+    HttpServer *m_server;
+    QWebEngineProfile *m_profile;
+    QWebEnginePage *m_page;
+    QWebEngineView *m_view;
+    QSet<QWebEngineDownloadItem *> m_downloads;
 };
 
-enum DownloadTestFileAction {
-    FileIsDownloaded,
-    FileIsDisplayed,
+class ScopedConnection {
+public:
+    ScopedConnection(QMetaObject::Connection connection) : m_connection(std::move(connection)) {}
+    ~ScopedConnection() { QObject::disconnect(m_connection); }
+private:
+    QMetaObject::Connection m_connection;
 };
 
-Q_DECLARE_METATYPE(DownloadTestUserAction);
-Q_DECLARE_METATYPE(DownloadTestFileAction);
+Q_DECLARE_METATYPE(tst_QWebEngineDownloads::UserAction)
+Q_DECLARE_METATYPE(tst_QWebEngineDownloads::FileAction)
+
+void tst_QWebEngineDownloads::initTestCase()
+{
+    m_server = new HttpServer();
+    m_profile = new QWebEngineProfile;
+    m_profile->setHttpCacheType(QWebEngineProfile::NoCache);
+    connect(m_profile, &QWebEngineProfile::downloadRequested, [this](QWebEngineDownloadItem *item) {
+        m_downloads.insert(item);
+        connect(item, &QWebEngineDownloadItem::destroyed, [this, item](){
+            m_downloads.remove(item);
+        });
+        connect(item, &QWebEngineDownloadItem::finished, [this, item](){
+            m_downloads.remove(item);
+        });
+    });
+    m_page = new QWebEnginePage(m_profile);
+    m_view = new QWebEngineView;
+    m_view->setPage(m_page);
+    m_view->show();
+}
+
+void tst_QWebEngineDownloads::init()
+{
+    QVERIFY(m_server->start());
+}
+
+void tst_QWebEngineDownloads::cleanup()
+{
+    QCOMPARE(m_downloads.count(), 0);
+    QVERIFY(m_server->stop());
+}
+
+void tst_QWebEngineDownloads::cleanupTestCase()
+{
+    delete m_view;
+    delete m_page;
+    delete m_profile;
+    delete m_server;
+}
+
+void tst_QWebEngineDownloads::saveLink(QPoint linkPos)
+{
+    // Simulate right-clicking on link and choosing "save link as" from menu.
+    QSignalSpy menuSpy(m_view, &QWebEngineView::customContextMenuRequested);
+    m_view->setContextMenuPolicy(Qt::CustomContextMenu);
+    auto event1 = new QContextMenuEvent(QContextMenuEvent::Mouse, linkPos);
+    auto event2 = new QMouseEvent(QEvent::MouseButtonPress, linkPos, Qt::RightButton, {}, {});
+    auto event3 = new QMouseEvent(QEvent::MouseButtonRelease, linkPos, Qt::RightButton, {}, {});
+    QTRY_VERIFY(m_view->focusWidget());
+    QWidget *renderWidget = m_view->focusWidget();
+    QCoreApplication::postEvent(renderWidget, event1);
+    QCoreApplication::postEvent(renderWidget, event2);
+    QCoreApplication::postEvent(renderWidget, event3);
+    QTRY_COMPARE(menuSpy.count(), 1);
+    m_page->triggerAction(QWebEnginePage::DownloadLinkToDisk);
+}
+
+void tst_QWebEngineDownloads::clickLink(QPoint linkPos)
+{
+    // Simulate left-clicking on link.
+    QTRY_VERIFY(m_view->focusWidget());
+    QWidget *renderWidget = m_view->focusWidget();
+    QTest::mouseClick(renderWidget, Qt::LeftButton, {}, linkPos);
+}
+
+void tst_QWebEngineDownloads::simulateUserAction(QPoint linkPos, UserAction action)
+{
+    switch (action) {
+    case SaveLink: return saveLink(linkPos);
+    case ClickLink: return clickLink(linkPos);
+    }
+}
+
+QWebEngineDownloadItem::DownloadType tst_QWebEngineDownloads::expectedDownloadType(
+    UserAction userAction, const QByteArray &contentDisposition)
+{
+    if (userAction == SaveLink)
+        return QWebEngineDownloadItem::UserRequested;
+    if (contentDisposition == QByteArrayLiteral("attachment"))
+        return QWebEngineDownloadItem::Attachment;
+    return QWebEngineDownloadItem::DownloadAttribute;
+}
 
 void tst_QWebEngineDownloads::downloadLink_data()
 {
-    QTest::addColumn<DownloadTestUserAction>("userAction");
+    QTest::addColumn<UserAction>("userAction");
     QTest::addColumn<bool>("anchorHasDownloadAttribute");
     QTest::addColumn<QByteArray>("fileName");
     QTest::addColumn<QByteArray>("fileContents");
@@ -87,7 +187,7 @@ void tst_QWebEngineDownloads::downloadLink_data()
     QTest::addColumn<QByteArray>("fileMimeTypeDetected");
     QTest::addColumn<QByteArray>("fileDisposition");
     QTest::addColumn<bool>("fileHasReferer");
-    QTest::addColumn<DownloadTestFileAction>("fileAction");
+    QTest::addColumn<FileAction>("fileAction");
     QTest::addColumn<QWebEngineDownloadItem::DownloadType>("downloadType");
 
     // SaveLink should always trigger a download, even for empty files.
@@ -100,8 +200,7 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // SaveLink should always trigger a download, also for text files.
     QTest::newRow("save link to text file")
@@ -113,8 +212,7 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // ... adding the "download" attribute should have no effect.
     QTest::newRow("save link to text file (attribute)")
@@ -126,8 +224,7 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // ... adding the "attachment" content disposition should also have no effect.
     QTest::newRow("save link to text file (attachment)")
@@ -139,8 +236,7 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("attachment")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // ... even adding both should have no effect.
     QTest::newRow("save link to text file (attribute+attachment)")
@@ -152,12 +248,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("attachment")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // Navigating to an empty file should show an empty page.
     QTest::newRow("navigate to empty file")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.txt")
         /* fileContents               */ << QByteArrayLiteral("")
@@ -165,12 +260,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDisplayed
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDisplayed;
 
     // Navigating to a text file should show the text file.
     QTest::newRow("navigate to text file")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.txt")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -178,12 +272,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDisplayed
-        /* downloadType               */ << QWebEngineDownloadItem::UserRequested;
+        /* fileAction                 */ << FileIsDisplayed;
 
     // ... unless the link has the "download" attribute: then the file should be downloaded.
     QTest::newRow("navigate to text file (attribute)")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << true
         /* fileName                   */ << QByteArrayLiteral("foo.txt")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -191,12 +284,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // ... same with the content disposition header save for the download type.
     QTest::newRow("navigate to text file (attachment)")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.txt")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -204,12 +296,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("attachment")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::Attachment;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // ... and both.
     QTest::newRow("navigate to text file (attribute+attachment)")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << true
         /* fileName                   */ << QByteArrayLiteral("foo.txt")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -217,12 +308,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("text/plain")
         /* fileDisposition            */ << QByteArrayLiteral("attachment")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::Attachment;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // The file's extension has no effect.
     QTest::newRow("navigate to supposed zip file")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.zip")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -230,12 +320,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDisplayed
-        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+        /* fileAction                 */ << FileIsDisplayed;
 
     // ... the file's mime type however does.
     QTest::newRow("navigate to supposed zip file (application/zip)")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.zip")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -243,12 +332,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/zip")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // ... but we're not very picky about the exact type.
     QTest::newRow("navigate to supposed zip file (application/octet-stream)")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.zip")
         /* fileContents               */ << QByteArrayLiteral("bar")
@@ -256,15 +344,14 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/octet-stream")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // empty zip file (consisting only of "end of central directory record")
     QByteArray zipFile = QByteArrayLiteral("PK\x05\x06") + QByteArray(18, 0);
 
     // The mime type is guessed automatically if not provided by the server.
     QTest::newRow("navigate to actual zip file")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.zip")
         /* fileContents               */ << zipFile
@@ -272,12 +359,11 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/octet-stream")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+        /* fileAction                 */ << FileIsDownloaded;
 
     // The mime type is not guessed automatically if provided by the server.
     QTest::newRow("navigate to actual zip file (application/zip)")
-        /* userAction                 */ << Navigate
+        /* userAction                 */ << ClickLink
         /* anchorHasDownloadAttribute */ << false
         /* fileName                   */ << QByteArrayLiteral("foo.zip")
         /* fileContents               */ << zipFile
@@ -285,13 +371,12 @@ void tst_QWebEngineDownloads::downloadLink_data()
         /* fileMimeTypeDetected       */ << QByteArrayLiteral("application/zip")
         /* fileDisposition            */ << QByteArrayLiteral("")
         /* fileHasReferer             */ << true
-        /* fileAction                 */ << FileIsDownloaded
-        /* downloadType               */ << QWebEngineDownloadItem::DownloadAttribute;
+        /* fileAction                 */ << FileIsDownloaded;
 }
 
 void tst_QWebEngineDownloads::downloadLink()
 {
-    QFETCH(DownloadTestUserAction, userAction);
+    QFETCH(UserAction, userAction);
     QFETCH(bool, anchorHasDownloadAttribute);
     QFETCH(QByteArray, fileName);
     QFETCH(QByteArray, fileContents);
@@ -299,245 +384,225 @@ void tst_QWebEngineDownloads::downloadLink()
     QFETCH(QByteArray, fileMimeTypeDetected);
     QFETCH(QByteArray, fileDisposition);
     QFETCH(bool, fileHasReferer);
-    QFETCH(DownloadTestFileAction, fileAction);
-    QFETCH(QWebEngineDownloadItem::DownloadType, downloadType);
-
-    HttpServer server;
-    QWebEngineProfile profile;
-    profile.setHttpCacheType(QWebEngineProfile::NoCache);
-    QWebEnginePage page(&profile);
-    QWebEngineView view;
-    view.setPage(&page);
-
-    // 1. Load an HTML page with a link
-    //
-    // The only variation being whether the <a> element has a "download"
-    // attribute or not.
-    view.load(server.url());
-    view.show();
-    auto indexRR = waitForRequest(&server);
-    QVERIFY(indexRR);
-    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
-    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
-    QByteArray html;
-    html += QByteArrayLiteral("<html><body><a href=\"");
-    html += fileName;
-    html += QByteArrayLiteral("\" ");
-    if (anchorHasDownloadAttribute)
-        html += QByteArrayLiteral("download");
-    html += QByteArrayLiteral(">Link</a></body></html>");
-    indexRR->setResponseBody(html);
-    indexRR->sendResponse();
-    bool loadOk = false;
-    QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok) { loadOk = ok; }));
-    QVERIFY(loadOk);
-
-    // 1.1. Ignore favicon request
-    auto favIconRR = waitForFaviconRequest(&server);
-    QVERIFY(favIconRR);
-
-    // 2. Simulate user action
-    //
-    // - Navigate: user left-clicks on link
-    // - SaveLink: user right-clicks on link and chooses "save link as" from menu
-    QTRY_VERIFY(view.focusWidget());
-    QWidget *renderWidget = view.focusWidget();
-    QPoint linkPos(10, 10);
-    if (userAction == SaveLink) {
-        view.setContextMenuPolicy(Qt::CustomContextMenu);
-        auto event1 = new QContextMenuEvent(QContextMenuEvent::Mouse, linkPos);
-        auto event2 = new QMouseEvent(QEvent::MouseButtonPress, linkPos, Qt::RightButton, {}, {});
-        auto event3 = new QMouseEvent(QEvent::MouseButtonRelease, linkPos, Qt::RightButton, {}, {});
-        QCoreApplication::postEvent(renderWidget, event1);
-        QCoreApplication::postEvent(renderWidget, event2);
-        QCoreApplication::postEvent(renderWidget, event3);
-        QVERIFY(waitForSignal(&view, &QWidget::customContextMenuRequested));
-        page.triggerAction(QWebEnginePage::DownloadLinkToDisk);
-    } else
-        QTest::mouseClick(renderWidget, Qt::LeftButton, {}, linkPos);
-
-    // 3. Deliver requested file
-    //
-    // Request/response headers vary.
-    auto fileRR = waitForRequest(&server);
-    QVERIFY(fileRR);
-    QCOMPARE(fileRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(fileRR->requestPath(), QByteArrayLiteral("/") + fileName);
-    if (fileHasReferer)
-        QCOMPARE(fileRR->requestHeader(QByteArrayLiteral("referer")), server.url().toEncoded());
-    else
-        QCOMPARE(fileRR->requestHeader(QByteArrayLiteral("referer")), QByteArray());
-    if (!fileDisposition.isEmpty())
-        fileRR->setResponseHeader(QByteArrayLiteral("content-disposition"), fileDisposition);
-    if (!fileMimeTypeDeclared.isEmpty())
-        fileRR->setResponseHeader(QByteArrayLiteral("content-type"), fileMimeTypeDeclared);
-    fileRR->setResponseBody(fileContents);
-    fileRR->sendResponse();
-
-    // 4a. File is displayed and not downloaded - end test
-    if (fileAction == FileIsDisplayed) {
-        QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok) { loadOk = ok; }));
-        QVERIFY(loadOk);
-        return;
-    }
+    QFETCH(FileAction, fileAction);
+
+    // Set up HTTP server
+    int indexRequestCount = 0;
+    int fileRequestCount = 0;
+    QByteArray fileRequestReferer;
+    ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        if (rr->requestMethod() == "GET" && rr->requestPath() == "/") {
+            indexRequestCount++;
+
+            rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+            QByteArray html;
+            html += QByteArrayLiteral("<html><body><a href=\"");
+            html += fileName;
+            html += QByteArrayLiteral("\" ");
+            if (anchorHasDownloadAttribute)
+                html += QByteArrayLiteral("download");
+            html += QByteArrayLiteral(">Link</a></body></html>");
+            rr->setResponseBody(html);
+            rr->sendResponse();
+        } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/" + fileName) {
+            fileRequestCount++;
+            fileRequestReferer = rr->requestHeader(QByteArrayLiteral("referer"));
+
+            if (!fileDisposition.isEmpty())
+                rr->setResponseHeader(QByteArrayLiteral("content-disposition"), fileDisposition);
+            if (!fileMimeTypeDeclared.isEmpty())
+                rr->setResponseHeader(QByteArrayLiteral("content-type"), fileMimeTypeDeclared);
+            rr->setResponseBody(fileContents);
+            rr->sendResponse();
+        } else {
+            rr->setResponseStatus(404);
+            rr->sendResponse();
+        }
+    });
 
-    // 4b. File is downloaded - check QWebEngineDownloadItem attributes
+    // Set up profile and download handler
     QTemporaryDir tmpDir;
     QVERIFY(tmpDir.isValid());
     QByteArray slashFileName = QByteArrayLiteral("/") + fileName;
     QString suggestedPath =
         QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + slashFileName;
     QString downloadPath = tmpDir.path() + slashFileName;
-    QUrl downloadUrl = server.url(slashFileName);
-    QWebEngineDownloadItem *downloadItem = nullptr;
-    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
-                          [&](QWebEngineDownloadItem *item) {
+    QUrl downloadUrl = m_server->url(slashFileName);
+    int acceptedCount = 0;
+    int finishedCount = 0;
+    ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
         QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
         QCOMPARE(item->isFinished(), false);
         QCOMPARE(item->totalBytes(), -1);
         QCOMPARE(item->receivedBytes(), 0);
         QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
-        QCOMPARE(item->type(), downloadType);
+        QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition));
         QCOMPARE(item->isSavePageDownload(), false);
         QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected));
         QCOMPARE(item->path(), suggestedPath);
         QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
         QCOMPARE(item->url(), downloadUrl);
+
+        connect(item, &QWebEngineDownloadItem::finished, [&, item]() {
+            QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted);
+            QCOMPARE(item->isFinished(), true);
+            QCOMPARE(item->totalBytes(), fileContents.size());
+            QCOMPARE(item->receivedBytes(), fileContents.size());
+            QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+            QCOMPARE(item->type(), expectedDownloadType(userAction, fileDisposition));
+            QCOMPARE(item->isSavePageDownload(), false);
+            QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected));
+            QCOMPARE(item->path(), downloadPath);
+            QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
+            QCOMPARE(item->url(), downloadUrl);
+
+            finishedCount++;
+        });
         item->setPath(downloadPath);
         item->accept();
-        downloadItem = item;
-    }));
-    QVERIFY(downloadItem);
-    bool finishOk = false;
-    QVERIFY(waitForSignal(downloadItem, &QWebEngineDownloadItem::finished, [&]() {
-        auto item = downloadItem;
-        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted);
-        QCOMPARE(item->isFinished(), true);
-        QCOMPARE(item->totalBytes(), fileContents.size());
-        QCOMPARE(item->receivedBytes(), fileContents.size());
-        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
-        QCOMPARE(item->type(), downloadType);
-        QCOMPARE(item->isSavePageDownload(), false);
-        QCOMPARE(item->mimeType(), QString(fileMimeTypeDetected));
-        QCOMPARE(item->path(), downloadPath);
-        QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
-        QCOMPARE(item->url(), downloadUrl);
-        finishOk = true;
-    }));
-    QVERIFY(finishOk);
 
-    // 5. Check actual file contents
+        acceptedCount++;
+    });
+
+    // Load an HTML page with a link
+    //
+    // The only variation being whether the <a> element has a "download"
+    // attribute or not.
+    QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished);
+    m_view->load(m_server->url());
+    QTRY_COMPARE(loadSpy.count(), 1);
+    QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true);
+    QCOMPARE(indexRequestCount, 1);
+
+    simulateUserAction(QPoint(10, 10), userAction);
+
+    // If file is expected to be displayed and not downloaded then end test
+    if (fileAction == FileIsDisplayed) {
+        QTRY_COMPARE(loadSpy.count(), 1);
+        QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true);
+        QCOMPARE(acceptedCount, 0);
+        return;
+    }
+
+    // Otherwise file is downloaded
+    QTRY_COMPARE(acceptedCount, 1);
+    QTRY_COMPARE(finishedCount, 1);
+    QCOMPARE(fileRequestCount, 1);
+    if (fileHasReferer)
+        QCOMPARE(fileRequestReferer, m_server->url().toEncoded());
+    else
+        QCOMPARE(fileRequestReferer, QByteArray());
     QFile file(downloadPath);
     QVERIFY(file.open(QIODevice::ReadOnly));
     QCOMPARE(file.readAll(), fileContents);
 }
 
+void tst_QWebEngineDownloads::downloadTwoLinks_data()
+{
+    QTest::addColumn<UserAction>("action1");
+    QTest::addColumn<UserAction>("action2");
+    QTest::newRow("Save+Save") << SaveLink << SaveLink;
+    QTest::newRow("Save+Click") << SaveLink << ClickLink;
+    QTest::newRow("Click+Save") << ClickLink << SaveLink;
+    QTest::newRow("Click+Click") << ClickLink << ClickLink;
+}
+
 void tst_QWebEngineDownloads::downloadTwoLinks()
 {
-    HttpServer server;
-    QSignalSpy requestSpy(&server, &HttpServer::newRequest);
-    QList<HttpReqRep*> results;
-    connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
-        rr->setParent(nullptr);
-        results.append(rr);
+    QFETCH(UserAction, action1);
+    QFETCH(UserAction, action2);
+
+    // Set up HTTP server
+    int file1RequestCount = 0;
+    int file2RequestCount = 0;
+    ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        if (rr->requestMethod() == "GET" && rr->requestPath() == "/") {
+            rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+            rr->setResponseBody(QByteArrayLiteral("<html><body><a href=\"file1\" download>Link1</a><br/><a href=\"file2\">Link2</a></body></html>"));
+            rr->sendResponse();
+        } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file1") {
+            file1RequestCount++;
+            rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain"));
+            rr->setResponseBody(QByteArrayLiteral("file1"));
+            rr->sendResponse();
+        } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file2") {
+            file2RequestCount++;
+            rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain"));
+            rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment"));
+            rr->setResponseBody(QByteArrayLiteral("file2"));
+            rr->sendResponse();
+        } else {
+            rr->setResponseStatus(404);
+            rr->sendResponse();
+        }
     });
 
+    // Set up profile and download handler
     QTemporaryDir tmpDir;
     QVERIFY(tmpDir.isValid());
     QString standardDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
-
-    QWebEngineProfile profile;
-    profile.setHttpCacheType(QWebEngineProfile::NoCache);
-    QList<QPointer<QWebEngineDownloadItem>> downloadItems;
-    connect(&profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
+    int acceptedCount = 0;
+    int finishedCount = 0;
+    ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
         QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
         QCOMPARE(item->isFinished(), false);
         QCOMPARE(item->totalBytes(), -1);
         QCOMPARE(item->receivedBytes(), 0);
         QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+        QCOMPARE(item->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
+        QCOMPARE(item->mimeType(), QStringLiteral("text/plain"));
         QString filePart = QChar('/') + item->url().fileName();
         QCOMPARE(item->path(), standardDir + filePart);
+
+        // type() is broken due to race condition in DownloadManagerDelegateQt
+        if (action1 == ClickLink && action2 == ClickLink) {
+            if (filePart == QStringLiteral("/file1"))
+                QCOMPARE(item->type(), expectedDownloadType(action1));
+            else if (filePart == QStringLiteral("/file2"))
+                QCOMPARE(item->type(), expectedDownloadType(action2, QByteArrayLiteral("attachment")));
+            else
+                QFAIL(qPrintable("Unexpected file name: " + filePart));
+        }
+
+        connect(item, &QWebEngineDownloadItem::finished, [&]() {
+            finishedCount++;
+        });
         item->setPath(tmpDir.path() + filePart);
         item->accept();
-        downloadItems.append(item);
+
+        acceptedCount++;
     });
 
-    QWebEnginePage page(&profile);
-    QWebEngineView view;
-    view.setPage(&page);
-
-    view.load(server.url());
-    view.show();
-    QTRY_COMPARE(requestSpy.count(), 1);
-    std::unique_ptr<HttpReqRep> indexRR(results.takeFirst());
-    QVERIFY(indexRR);
-    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
-    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
-    indexRR->setResponseBody(QByteArrayLiteral("<html><body><a href=\"file1\" download>Link1</a><br/><a href=\"file2\">Link2</a></body></html>"));
-    indexRR->sendResponse();
-    bool loadOk = false;
-    QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok){ loadOk = ok; }));
-    QVERIFY(loadOk);
-
-    QTRY_COMPARE(requestSpy.count(), 2);
-    std::unique_ptr<HttpReqRep> favIconRR(results.takeFirst());
-    QVERIFY(favIconRR);
-    favIconRR->setResponseStatus(404);
-    favIconRR->sendResponse();
-
-    QTRY_VERIFY(view.focusWidget());
-    QWidget *renderWidget = view.focusWidget();
-    QTest::mouseClick(renderWidget, Qt::LeftButton, {}, QPoint(10, 10));
-    QTest::mouseClick(renderWidget, Qt::LeftButton, {}, QPoint(10, 30));
-
-    QTRY_VERIFY(requestSpy.count() >= 3);
-    std::unique_ptr<HttpReqRep> file1RR(results.takeFirst());
-    QVERIFY(file1RR);
-    QCOMPARE(file1RR->requestMethod(), QByteArrayLiteral("GET"));
-    QTRY_COMPARE(requestSpy.count(), 4);
-    std::unique_ptr<HttpReqRep> file2RR(results.takeFirst());
-    QVERIFY(file2RR);
-    QCOMPARE(file2RR->requestMethod(), QByteArrayLiteral("GET"));
-
-    // Handle one request overtaking the other
-    if (file1RR->requestPath() == QByteArrayLiteral("/file2"))
-        std::swap(file1RR, file2RR);
-
-    QCOMPARE(file1RR->requestPath(), QByteArrayLiteral("/file1"));
-    QCOMPARE(file2RR->requestPath(), QByteArrayLiteral("/file2"));
-
-    file1RR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain"));
-    file1RR->setResponseBody(QByteArrayLiteral("file1"));
-    file1RR->sendResponse();
-    file2RR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/plain"));
-    file2RR->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment"));
-    file2RR->setResponseBody(QByteArrayLiteral("file2"));
-    file2RR->sendResponse();
-
-    // Now wait for downloadRequested signals:
-    QTRY_VERIFY(downloadItems.count() >= 2);
-    QScopedPointer<QWebEngineDownloadItem> item1(downloadItems.takeFirst());
-    QScopedPointer<QWebEngineDownloadItem> item2(downloadItems.takeFirst());
-    QVERIFY(item1);
-    QVERIFY(item2);
-
-    // Handle one request overtaking the other
-    if (item1->url().fileName() == QByteArrayLiteral("file2"))
-        qSwap(item1, item2);
-
-    QCOMPARE(item1->type(), QWebEngineDownloadItem::DownloadAttribute);
-    QCOMPARE(item1->mimeType(), QStringLiteral("text/plain"));
-    QCOMPARE(item1->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
-    QCOMPARE(item1->url(), server.url(QByteArrayLiteral("/file1")));
-    QTRY_COMPARE(item1->state(), QWebEngineDownloadItem::DownloadCompleted);
-
-    QCOMPARE(item2->type(), QWebEngineDownloadItem::Attachment);
-    QCOMPARE(item2->mimeType(), QStringLiteral("text/plain"));
-    QCOMPARE(item2->savePageFormat(), QWebEngineDownloadItem::UnknownSaveFormat);
-    QCOMPARE(item2->url(), server.url(QByteArrayLiteral("/file2")));
-    QTRY_COMPARE(item2->state(), QWebEngineDownloadItem::DownloadCompleted);
+    QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished);
+    m_view->load(m_server->url());
+    QTRY_COMPARE(loadSpy.count(), 1);
+    QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true);
+
+    // Trigger downloads
+    simulateUserAction(QPoint(10, 10), action1);
+    simulateUserAction(QPoint(10, 30), action2);
+
+    // Wait for downloads
+    if (action1 == action2 && action1 == ClickLink) {
+        // With two clicks, sometimes both files get downloaded, sometimes only
+        // the second file, depending on the timing. This is expected and
+        // follows Chromium's behavior. We check here only that the second file
+        // is downloaded correctly (and that we do not crash).
+        //
+        // The first download may be aborted before or after the HTTP request is
+        // made. In the latter case we will have both a file1 and a file2
+        // request, but still only one accepted download.
+        QTRY_COMPARE(file2RequestCount, 1);
+        QTRY_VERIFY(acceptedCount >= 1);
+        QTRY_VERIFY(finishedCount >= 1);
+        QTRY_COMPARE(m_downloads.count(), 0);
+    } else {
+        // Otherwise both files should always be downloaded correctly.
+        QTRY_COMPARE(file1RequestCount, 1);
+        QTRY_COMPARE(file2RequestCount, 1);
+        QTRY_COMPARE(acceptedCount, 2);
+        QTRY_COMPARE(finishedCount, 2);
+    }
 }
 
 void tst_QWebEngineDownloads::downloadPage_data()
@@ -552,37 +617,28 @@ void tst_QWebEngineDownloads::downloadPage()
 {
     QFETCH(QWebEngineDownloadItem::SavePageFormat, savePageFormat);
 
-    HttpServer server;
-    QWebEngineProfile profile;
-    QWebEnginePage page(&profile);
-    QWebEngineView view;
-    view.setPage(&page);
-
-    view.load(server.url());
-    view.show();
-    auto indexRR = waitForRequest(&server);
-    QVERIFY(indexRR);
-    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
-    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
-    indexRR->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>"));
-    indexRR->sendResponse();
-    bool loadOk = false;
-    QVERIFY(waitForSignal(&page, &QWebEnginePage::loadFinished, [&](bool ok){ loadOk = ok; }));
-    QVERIFY(loadOk);
-
-    auto favIconRR = waitForFaviconRequest(&server);
-    QVERIFY(favIconRR);
+    // Set up HTTP server
+    int indexRequestCount = 0;
+    ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        if (rr->requestMethod() == "GET" && rr->requestPath() == "/") {
+            indexRequestCount++;
+            rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+            rr->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>"));
+            rr->sendResponse();
+        } else {
+            rr->setResponseStatus(404);
+            rr->sendResponse();
+        }
+    });
 
+    // Set up profile and download handler
     QTemporaryDir tmpDir;
     QVERIFY(tmpDir.isValid());
     QString downloadPath = tmpDir.path() + QStringLiteral("/test.html");
-    page.save(downloadPath, savePageFormat);
-
-    QWebEngineDownloadItem *downloadItem = nullptr;
-    QUrl downloadUrl = server.url("/");
-    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
-                        [&](QWebEngineDownloadItem *item) {
+    QUrl downloadUrl = m_server->url("/");
+    int acceptedCount = 0;
+    int finishedCount = 0;
+    ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
         QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadInProgress);
         QCOMPARE(item->isFinished(), false);
         QCOMPARE(item->totalBytes(), -1);
@@ -590,33 +646,43 @@ void tst_QWebEngineDownloads::downloadPage()
         QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
         QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage);
         QCOMPARE(item->isSavePageDownload(), true);
-        // FIXME why is mimeType always the same?
+        // FIXME(juvaldma): why is mimeType always the same?
         QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive"));
         QCOMPARE(item->path(), downloadPath);
         QCOMPARE(item->savePageFormat(), savePageFormat);
         QCOMPARE(item->url(), downloadUrl);
         // no need to call item->accept()
-        downloadItem = item;
-    }));
-    QVERIFY(downloadItem);
-    bool finishOk = false;
-    QVERIFY(waitForSignal(downloadItem, &QWebEngineDownloadItem::finished, [&]() {
-        auto item = downloadItem;
-        QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted);
-        QCOMPARE(item->isFinished(), true);
-        QCOMPARE(item->totalBytes(), item->receivedBytes());
-        QVERIFY(item->receivedBytes() > 0);
-        QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
-        QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage);
-        QCOMPARE(item->isSavePageDownload(), true);
-        QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive"));
-        QCOMPARE(item->path(), downloadPath);
-        QCOMPARE(item->savePageFormat(), savePageFormat);
-        QCOMPARE(item->url(), downloadUrl);
-        finishOk = true;
-    }));
-    QVERIFY(finishOk);
 
+        connect(item, &QWebEngineDownloadItem::finished, [&, item]() {
+            QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadCompleted);
+            QCOMPARE(item->isFinished(), true);
+            QCOMPARE(item->totalBytes(), item->receivedBytes());
+            QVERIFY(item->receivedBytes() > 0);
+            QCOMPARE(item->interruptReason(), QWebEngineDownloadItem::NoReason);
+            QCOMPARE(item->type(), QWebEngineDownloadItem::SavePage);
+            QCOMPARE(item->isSavePageDownload(), true);
+            QCOMPARE(item->mimeType(), QStringLiteral("application/x-mimearchive"));
+            QCOMPARE(item->path(), downloadPath);
+            QCOMPARE(item->savePageFormat(), savePageFormat);
+            QCOMPARE(item->url(), downloadUrl);
+
+            finishedCount++;
+        });
+
+        acceptedCount++;
+    });
+
+    // Load some HTML
+    QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished);
+    m_page->load(m_server->url());
+    QTRY_COMPARE(loadSpy.count(), 1);
+    QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true);
+    QCOMPARE(indexRequestCount, 1);
+
+    // Save some HTML
+    m_page->save(downloadPath, savePageFormat);
+    QTRY_COMPARE(acceptedCount, 1);
+    QTRY_COMPARE(finishedCount, 1);
     QFile file(downloadPath);
     QVERIFY(file.exists());
 }
@@ -626,31 +692,33 @@ void tst_QWebEngineDownloads::downloadViaSetUrl()
     // Reproduce the scenario described in QTBUG-63388 by triggering downloads
     // of the same file multiple times via QWebEnginePage::setUrl
 
-    HttpServer server;
-    QWebEngineProfile profile;
-    QWebEnginePage page(&profile);
-    QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
-    QSignalSpy urlSpy(&page, &QWebEnginePage::urlChanged);
-    const QUrl indexUrl = server.url();
-    const QUrl fileUrl = server.url(QByteArrayLiteral("/file"));
-
-    // Set up the test scenario by trying to load some unrelated HTML.
-
-    page.setUrl(indexUrl);
-
-    auto indexRR = waitForRequest(&server);
-    QVERIFY(indexRR);
-    QCOMPARE(indexRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(indexRR->requestPath(), QByteArrayLiteral("/"));
-    indexRR->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
-    indexRR->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>"));
-    indexRR->sendResponse();
+    // Set up HTTP server
+    ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        if (rr->requestMethod() == "GET" && rr->requestPath() == "/") {
+            rr->setResponseHeader(QByteArrayLiteral("content-type"), QByteArrayLiteral("text/html"));
+            rr->setResponseBody(QByteArrayLiteral("<html><body>Hello</body></html>"));
+            rr->sendResponse();
+        } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/file") {
+            rr->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment"));
+            rr->setResponseBody(QByteArrayLiteral("redacted"));
+            rr->sendResponse();
+        } else {
+            rr->setResponseStatus(404);
+            rr->sendResponse();
+        }
+    });
 
-    auto indexFavRR = waitForFaviconRequest(&server);
-    QVERIFY(indexFavRR);
-    indexRR.reset();
-    indexFavRR.reset();
+    // Set up profile and download handler
+    QVector<QUrl> downloadUrls;
+    ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
+        downloadUrls.append(item->url());
+    });
 
+    // Set up the test scenario by trying to load some unrelated HTML.
+    QSignalSpy loadSpy(m_page, &QWebEnginePage::loadFinished);
+    QSignalSpy urlSpy(m_page, &QWebEnginePage::urlChanged);
+    const QUrl indexUrl = m_server->url();
+    m_page->setUrl(indexUrl);
     QTRY_COMPARE(loadSpy.count(), 1);
     QTRY_COMPARE(urlSpy.count(), 1);
     QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true);
@@ -658,37 +726,18 @@ void tst_QWebEngineDownloads::downloadViaSetUrl()
 
     // Download files via setUrl. With QTBUG-63388 after the first iteration the
     // downloads would be triggered for indexUrl and not fileUrl.
-
-    QVector<QUrl> downloadUrls;
-    QObject::connect(&profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
-        downloadUrls.append(item->url());
-    });
-
+    const QUrl fileUrl = m_server->url(QByteArrayLiteral("/file"));
     for (int i = 0; i != 3; ++i) {
-        page.setUrl(fileUrl);
-        QCOMPARE(page.url(), fileUrl);
-
-        auto fileRR = waitForRequest(&server);
-        QVERIFY(fileRR);
-        QCOMPARE(fileRR->requestMethod(), QByteArrayLiteral("GET"));
-        QCOMPARE(fileRR->requestPath(), QByteArrayLiteral("/file"));
-        fileRR->setResponseHeader(QByteArrayLiteral("content-disposition"), QByteArrayLiteral("attachment"));
-        fileRR->setResponseBody(QByteArrayLiteral("redacted"));
-        fileRR->sendResponse();
-
-// Since 63 we no longer get favicon requests here:
-//        auto fileFavRR = waitForFaviconRequest(&server);
-//        QVERIFY(fileFavRR);
-
+        m_page->setUrl(fileUrl);
+        QCOMPARE(m_page->url(), fileUrl);
         QTRY_COMPARE(loadSpy.count(), 1);
         QTRY_COMPARE(urlSpy.count(), 2);
         QTRY_COMPARE(downloadUrls.count(), 1);
-        fileRR.reset();
         QCOMPARE(loadSpy.takeFirst().value(0).toBool(), false);
         QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), fileUrl);
         QCOMPARE(urlSpy.takeFirst().value(0).toUrl(), indexUrl);
         QCOMPARE(downloadUrls.takeFirst(), fileUrl);
-        QCOMPARE(page.url(), indexUrl);
+        QCOMPARE(m_page->url(), indexUrl);
     }
 }
 
@@ -696,26 +745,22 @@ void tst_QWebEngineDownloads::downloadFileNot1()
 {
     // Trigger file download via download() but don't accept().
 
-    HttpServer server;
-    QWebEngineProfile profile;
-    QWebEnginePage page(&profile);
-    const auto filePath = QByteArrayLiteral("/file");
-    const auto fileUrl = server.url(filePath);
-
-    page.download(fileUrl);
-    auto fileRR = waitForRequest(&server);
-    QVERIFY(fileRR);
-    QCOMPARE(fileRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(fileRR->requestPath(), filePath);
-    fileRR->sendResponse();
+    ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        rr->setResponseStatus(404);
+        rr->sendResponse();
+    });
 
     QPointer<QWebEngineDownloadItem> downloadItem;
-    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
-                          [&](QWebEngineDownloadItem *item) {
+    int downloadCount = 0;
+    ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
         QVERIFY(item);
         QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
         downloadItem = item;
-    }));
+        downloadCount++;
+    });
+
+    m_page->download(m_server->url(QByteArrayLiteral("/file")));
+    QTRY_COMPARE(downloadCount, 1);
     QVERIFY(!downloadItem);
 }
 
@@ -723,27 +768,23 @@ void tst_QWebEngineDownloads::downloadFileNot2()
 {
     // Trigger file download via download() but call cancel() instead of accept().
 
-    HttpServer server;
-    QWebEngineProfile profile;
-    QWebEnginePage page(&profile);
-    const auto filePath = QByteArrayLiteral("/file");
-    const auto fileUrl = server.url(filePath);
-
-    page.download(fileUrl);
-    auto fileRR = waitForRequest(&server);
-    QVERIFY(fileRR);
-    QCOMPARE(fileRR->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(fileRR->requestPath(), filePath);
-    fileRR->sendResponse();
+    ScopedConnection sc1 = connect(m_server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        rr->setResponseStatus(404);
+        rr->sendResponse();
+    });
 
     QPointer<QWebEngineDownloadItem> downloadItem;
-    QVERIFY(waitForSignal(&profile, &QWebEngineProfile::downloadRequested,
-                          [&](QWebEngineDownloadItem *item) {
+    int downloadCount = 0;
+    ScopedConnection sc2 = connect(m_profile, &QWebEngineProfile::downloadRequested, [&](QWebEngineDownloadItem *item) {
         QVERIFY(item);
         QCOMPARE(item->state(), QWebEngineDownloadItem::DownloadRequested);
         item->cancel();
         downloadItem = item;
-    }));
+        downloadCount++;
+    });
+
+    m_page->download(m_server->url(QByteArrayLiteral("/file")));
+    QTRY_COMPARE(downloadCount, 1);
     QVERIFY(downloadItem);
     QCOMPARE(downloadItem->state(), QWebEngineDownloadItem::DownloadCancelled);
 }
diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
index 6b729d8f214acfae0036f5dfa53047a01aec9718..81877657b2e74d259080692b8208edabcd0841d3 100644
--- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
+++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
@@ -4247,21 +4247,26 @@ void tst_QWebEnginePage::registerProtocolHandler()
     QFETCH(bool, permission);
 
     HttpServer server;
+    int mailRequestCount = 0;
+    connect(&server, &HttpServer::newRequest, [&](HttpReqRep *rr) {
+        if (rr->requestMethod() == "GET" && rr->requestPath() == "/") {
+            rr->setResponseBody(QByteArrayLiteral("<html><body><a id=\"link\" href=\"mailto:foo@bar.com\">some text here</a></body></html>"));
+            rr->sendResponse();
+        } else if (rr->requestMethod() == "GET" && rr->requestPath() == "/mail?uri=mailto%3Afoo%40bar.com") {
+            mailRequestCount++;
+            rr->sendResponse();
+        } else {
+            rr->setResponseStatus(404);
+            rr->sendResponse();
+        }
+    });
+    QVERIFY(server.start());
+
     QWebEnginePage page;
     QSignalSpy loadSpy(&page, &QWebEnginePage::loadFinished);
     QSignalSpy permissionSpy(&page, &QWebEnginePage::registerProtocolHandlerPermissionRequested);
 
     page.setUrl(server.url("/"));
-    auto rr1 = waitForRequest(&server);
-    QVERIFY(rr1);
-    rr1->setResponseBody(QByteArrayLiteral("<html><body><a id=\"link\" href=\"mailto:foo@bar.com\">some text here</a></body></html>"));
-    rr1->sendResponse();
-    auto rr2 = waitForRequest(&server);
-    QVERIFY(rr2);
-    QCOMPARE(rr2->requestMethod(), QByteArrayLiteral("GET"));
-    QCOMPARE(rr2->requestPath(), QByteArrayLiteral("/favicon.ico"));
-    rr2->setResponseStatus(404);
-    rr2->sendResponse();
     QTRY_COMPARE(loadSpy.count(), 1);
     QCOMPARE(loadSpy.takeFirst().value(0).toBool(), true);
 
@@ -4283,17 +4288,10 @@ void tst_QWebEnginePage::registerProtocolHandler()
 
     page.runJavaScript(QStringLiteral("document.getElementById(\"link\").click()"));
 
-    std::unique_ptr<HttpReqRep> rr3;
-    if (permission) {
-        rr3 = waitForRequest(&server);
-        QVERIFY(rr3);
-        QCOMPARE(rr3->requestMethod(), QByteArrayLiteral("GET"));
-        QCOMPARE(rr3->requestPath(), QByteArrayLiteral("/mail?uri=mailto%3Afoo%40bar.com"));
-        rr3->sendResponse();
-    }
-
     QTRY_COMPARE(loadSpy.count(), 1);
     QCOMPARE(loadSpy.takeFirst().value(0).toBool(), permission);
+    QCOMPARE(mailRequestCount, permission ? 1 : 0);
+    QVERIFY(server.stop());
 }
 
 void tst_QWebEnginePage::dataURLFragment()