diff options
author | Michal Klocek <michal.klocek@qt.io> | 2021-02-11 10:03:24 +0100 |
---|---|---|
committer | Michal Klocek <michal.klocek@qt.io> | 2021-05-22 14:10:10 +0200 |
commit | 97dcbd4019456b9a1c567faddb0521b7505d80fc (patch) | |
tree | 9c77c5640b1563d853c79898cd64d87252fe8c41 /tests/auto/util | |
parent | dd523573f2981cc58d4da0ec6e2b061a6172a8eb (diff) |
Add tests to the cmake build
Use QT_TESTCASE_SOURCEDIR instead of TESTS_SOURCE_DIR.
Introduce Test::HttpServer and Test::Util targets.
Query shared data location from server.
Clean up "shared" resources.
Note QT_TESTCASE_SOURCEDIR must be turned into the canonical form
since the user can call on windows:
"cmake \path\to\foo" instead of "cmake c:\path\to\foo" which
will break all file:// urls.
Note this patch breaks qmake builds.
Task-number: QTBUG-91760
Change-Id: Ibc1f904ac9acd375d1ff70ff80f0c533497e3f20
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'tests/auto/util')
-rw-r--r-- | tests/auto/util/CMakeLists.txt | 7 | ||||
-rw-r--r-- | tests/auto/util/qt_webengine_quicktest.h | 53 | ||||
-rw-r--r-- | tests/auto/util/quickutil.h | 191 | ||||
-rw-r--r-- | tests/auto/util/testwindow.h | 68 | ||||
-rw-r--r-- | tests/auto/util/util.cmake | 5 | ||||
-rw-r--r-- | tests/auto/util/util.h | 238 |
6 files changed, 562 insertions, 0 deletions
diff --git a/tests/auto/util/CMakeLists.txt b/tests/auto/util/CMakeLists.txt new file mode 100644 index 000000000..fa2f84cec --- /dev/null +++ b/tests/auto/util/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.18) +project(minimal LANGUAGES CXX) + +find_package(Qt6 COMPONENTS Core) +find_package(Qt6 COMPONENTS Network) + +include(util.cmake) diff --git a/tests/auto/util/qt_webengine_quicktest.h b/tests/auto/util/qt_webengine_quicktest.h new file mode 100644 index 000000000..55ccd9f33 --- /dev/null +++ b/tests/auto/util/qt_webengine_quicktest.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 QT_WEBENGINE_QUICKTEST_H +#define QT_WEBENGINE_QUICKTEST_H + +#include <QtQuickTest/quicktestglobal.h> + +#ifdef QT_WIDGETS_LIB +#include <QtWidgets/QApplication> +#else +#include <QtGui/QGuiApplication> +#endif + +#include "qopenglcontext.h" +#include <qtwebengineglobal.h> + +QT_BEGIN_NAMESPACE + +#ifdef QT_WIDGETS_LIB +#define Application QApplication +#else +#define Application QGuiApplication +#endif + +QT_END_NAMESPACE + +#endif // QT_WEBENGINE_QUICKTEST_H diff --git a/tests/auto/util/quickutil.h b/tests/auto/util/quickutil.h new file mode 100644 index 000000000..34afbbb45 --- /dev/null +++ b/tests/auto/util/quickutil.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 UTIL_H +#define UTIL_H + +#include <QEventLoop> +#include <QQmlEngine> +#include <QSignalSpy> +#include <QTimer> +#include <QtTest/QtTest> +#include <QtWebEngineQuick/private/qquickwebengineview_p.h> +#include <QtWebEngineCore/QWebEngineLoadRequest> +#include <QtWebEngineQuick/private/qquickwebengineview_p.h> +#include <QGuiApplication> + +#if !defined(TESTS_SOURCE_DIR) +#define TESTS_SOURCE_DIR "" +#endif + +class LoadSpy : public QEventLoop { + Q_OBJECT + +public: + LoadSpy(QQuickWebEngineView *webEngineView) + { + connect(webEngineView, &QQuickWebEngineView::loadingChanged, this, &LoadSpy::onLoadingChanged); + } + + ~LoadSpy() { } + +Q_SIGNALS: + void loadSucceeded(); + void loadFailed(); + +private Q_SLOTS: + void onLoadingChanged(const QWebEngineLoadRequest &request) + { + if (request.status() == QWebEngineLoadRequest::LoadSucceededStatus) + emit loadSucceeded(); + else if (request.status() == QWebEngineLoadRequest::LoadFailedStatus) + emit loadFailed(); + } +}; + +class LoadStartedCatcher : public QObject { + Q_OBJECT + +public: + LoadStartedCatcher(QQuickWebEngineView *webEngineView) + : m_webEngineView(webEngineView) + { + connect(m_webEngineView, &QQuickWebEngineView::loadingChanged, this, &LoadStartedCatcher::onLoadingChanged); + } + + virtual ~LoadStartedCatcher() { } + +public Q_SLOTS: + void onLoadingChanged(const QWebEngineLoadRequest &request) + { + if (request.status() == QWebEngineLoadRequest::LoadStartedStatus) + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); + } + +Q_SIGNALS: + void finished(); + +private: + QQuickWebEngineView *m_webEngineView; +}; + +inline bool waitForLoadSucceeded(QQuickWebEngineView *webEngineView, int timeout = 10000) +{ + LoadSpy loadSpy(webEngineView); + QSignalSpy spy(&loadSpy, &LoadSpy::loadSucceeded); + return spy.wait(timeout); +} + +inline bool waitForLoadFailed(QQuickWebEngineView *webEngineView, int timeout = 20000) +{ + LoadSpy loadSpy(webEngineView); + QSignalSpy spy(&loadSpy, &LoadSpy::loadFailed); + return spy.wait(timeout); +} + +inline QVariant evaluateJavaScriptSync(QQuickWebEngineView *view, const QString &script) +{ + QQmlEngine *engine = qmlEngine(view); + engine->globalObject().setProperty("called", false); + engine->globalObject().setProperty("result", QJSValue()); + QJSValue callback = engine->evaluate( + "(function callback(r) {" + " called = true;" + " result = r;" + "})" + ); + view->runJavaScript(script, callback); + QTRY_LOOP_IMPL(engine->globalObject().property("called").toBool(), 5000, 50); + if (!engine->globalObject().property("called").toBool()) { + qWarning("JavaScript wasn't evaluated"); + return QVariant(); + } + + return engine->globalObject().property("result").toVariant(); +} + +inline QPoint elementCenter(QQuickWebEngineView *view, const QString &id) +{ + const QString jsCode( + "(function(){" + " var elem = document.getElementById('" + id + "');" + " var rect = elem.getBoundingClientRect();" + " return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" + "})()"); + QVariantList rectList = evaluateJavaScriptSync(view, jsCode).toList(); + + if (rectList.count() != 2) { + qWarning("elementCenter failed."); + return QPoint(); + } + + return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); +} + +inline QString activeElementId(QQuickWebEngineView *webEngineView) +{ + qRegisterMetaType<QQuickWebEngineView::JavaScriptConsoleMessageLevel>("JavaScriptConsoleMessageLevel"); + QSignalSpy consoleMessageSpy(webEngineView, &QQuickWebEngineView::javaScriptConsoleMessage); + + webEngineView->runJavaScript( + "if (document.activeElement == null)" + " console.log('');" + "else" + " console.log(document.activeElement.id);" + ); + + if (!consoleMessageSpy.wait()) + return QString(); + + QList<QVariant> arguments = consoleMessageSpy.takeFirst(); + if (static_cast<QQuickWebEngineView::JavaScriptConsoleMessageLevel>(arguments.at(0).toInt()) != QQuickWebEngineView::InfoMessageLevel) + return QString(); + + return arguments.at(1).toString(); +} + +#define W_QTEST_MAIN(TestObject, params) \ +int main(int argc, char *argv[]) \ +{ \ + QtWebEngine::initialize(); \ + \ + QList<const char *> w_argv(argc); \ + for (int i = 0; i < argc; ++i) \ + w_argv[i] = argv[i]; \ + for (int i = 0; i < params.size(); ++i) \ + w_argv.append(params[i].data()); \ + int w_argc = w_argv.size(); \ + \ + QGuiApplication app(w_argc, const_cast<char **>(w_argv.data())); \ + app.setAttribute(Qt::AA_Use96Dpi, true); \ + TestObject tc; \ + QTEST_SET_MAIN_SOURCE_PATH \ + return QTest::qExec(&tc, argc, argv); \ +} +#endif /* UTIL_H */ + diff --git a/tests/auto/util/testwindow.h b/tests/auto/util/testwindow.h new file mode 100644 index 000000000..b57443c69 --- /dev/null +++ b/tests/auto/util/testwindow.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 TESTWINDOW_H +#define TESTWINDOW_H + +#if 0 +#pragma qt_no_master_include +#endif + +#include <QResizeEvent> +#include <QScopedPointer> +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickview.h> + +// TestWindow: Utility class to ignore QQuickView details. +class TestWindow : public QQuickView { +public: + inline TestWindow(QQuickItem *webEngineView); + QScopedPointer<QQuickItem> webEngineView; + +protected: + inline void resizeEvent(QResizeEvent*); +}; + +inline TestWindow::TestWindow(QQuickItem *webEngineView) + : webEngineView(webEngineView) +{ + Q_ASSERT(webEngineView); + webEngineView->setParentItem(contentItem()); + resize(300, 400); +} + +inline void TestWindow::resizeEvent(QResizeEvent *event) +{ + QQuickView::resizeEvent(event); + webEngineView->setX(0); + webEngineView->setY(0); + webEngineView->setWidth(event->size().width()); + webEngineView->setHeight(event->size().height()); +} + +#endif /* TESTWINDOW_H */ diff --git a/tests/auto/util/util.cmake b/tests/auto/util/util.cmake new file mode 100644 index 000000000..84d7f593f --- /dev/null +++ b/tests/auto/util/util.cmake @@ -0,0 +1,5 @@ +if (NOT TARGET Test::Util) + add_library(qtestutil INTERFACE) + target_include_directories(qtestutil INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + add_library(Test::Util ALIAS qtestutil) +endif() diff --git a/tests/auto/util/util.h b/tests/auto/util/util.h new file mode 100644 index 000000000..537b9212b --- /dev/null +++ b/tests/auto/util/util.h @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +// Functions and macros that really need to be in QTestLib + +#if 0 +#pragma qt_no_master_include +#endif + +#include <QEventLoop> +#include <QSignalSpy> +#include <QTimer> +#include <qwebenginepage.h> +#include <qwebengineview.h> + +// Disconnect signal on destruction. +class ScopedConnection +{ +public: + ScopedConnection(QMetaObject::Connection connection) : m_connection(std::move(connection)) { } + ~ScopedConnection() { QObject::disconnect(m_connection); } + +private: + QMetaObject::Connection m_connection; +}; + +/** + * Just like QSignalSpy but facilitates sync and async + * signal emission. For example if you want to verify that + * page->foo() emitted a signal, it could be that the + * implementation decides to emit the signal asynchronously + * - in which case we want to spin a local event loop until + * emission - or that the call to foo() emits it right away. + */ +class SignalBarrier : private QSignalSpy +{ +public: + SignalBarrier(const QObject* obj, const char* aSignal) + : QSignalSpy(obj, aSignal) + { } + + bool ensureSignalEmitted() + { + bool result = count() > 0; + if (!result) + result = wait(); + clear(); + return result; + } +}; + +template<typename T, typename R> +struct CallbackWrapper { + QPointer<R> p; + void operator()(const T& result) { + if (p) + (*p)(result); + } +}; + +template<typename T> +class CallbackSpy: public QObject { +public: + CallbackSpy() : called(false) { + timeoutTimer.setSingleShot(true); + QObject::connect(&timeoutTimer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); + } + + T waitForResult(int timeout = 20000) { + const int step = 1000; + int elapsed = 0; + while (elapsed < timeout && !called) { + timeoutTimer.start(step); + eventLoop.exec(); + elapsed += step; + } + return result; + } + + bool wasCalled() const { + return called; + } + + void operator()(const T &result) { + this->result = result; + called = true; + eventLoop.quit(); + } + + CallbackWrapper<T, CallbackSpy<T> > ref() + { + CallbackWrapper<T, CallbackSpy<T> > wrapper = {this}; + return wrapper; + } + +private: + Q_DISABLE_COPY(CallbackSpy) + bool called; + QTimer timeoutTimer; + QEventLoop eventLoop; + T result; +}; + +static inline QString toPlainTextSync(QWebEnginePage *page) +{ + CallbackSpy<QString> spy; + page->toPlainText(spy.ref()); + return spy.waitForResult(); +} + +static inline QString toHtmlSync(QWebEnginePage *page) +{ + CallbackSpy<QString> spy; + page->toHtml(spy.ref()); + return spy.waitForResult(); +} + +static inline bool findTextSync(QWebEnginePage *page, const QString &subString) +{ + CallbackSpy<bool> spy; + page->findText(subString, {}, spy.ref()); + return spy.waitForResult(); +} + +static inline QVariant evaluateJavaScriptSync(QWebEnginePage *page, const QString &script) +{ + CallbackSpy<QVariant> spy; + page->runJavaScript(script, spy.ref()); + return spy.waitForResult(); +} + +static inline QVariant evaluateJavaScriptSyncInWorld(QWebEnginePage *page, const QString &script, int worldId) +{ + CallbackSpy<QVariant> spy; + page->runJavaScript(script, worldId, spy.ref()); + return spy.waitForResult(); +} + +static inline QUrl baseUrlSync(QWebEnginePage *page) +{ + CallbackSpy<QVariant> spy; + page->runJavaScript("document.baseURI", spy.ref()); + return spy.waitForResult().toUrl(); +} + +static inline bool loadSync(QWebEnginePage *page, const QUrl &url, bool ok = true) +{ + QSignalSpy spy(page, &QWebEnginePage::loadFinished); + page->load(url); + return (!spy.empty() || spy.wait(20000)) && (spy.front().value(0).toBool() == ok); +} + +static inline bool loadSync(QWebEngineView *view, const QUrl &url, bool ok = true) +{ + return loadSync(view->page(), url, ok); +} + +static inline QPoint elementCenter(QWebEnginePage *page, const QString &id) +{ + const QString jsCode( + "(function(){" + " var elem = document.getElementById('" + id + "');" + " var rect = elem.getBoundingClientRect();" + " return [(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2];" + "})()"); + QVariantList rectList = evaluateJavaScriptSync(page, jsCode).toList(); + + if (rectList.count() != 2) { + qWarning("elementCenter failed."); + return QPoint(); + } + + return QPoint(rectList.at(0).toInt(), rectList.at(1).toInt()); +} + +static inline QRect elementGeometry(QWebEnginePage *page, const QString &id) +{ + const QString jsCode( + "(function() {" + " var elem = document.getElementById('" + id + "');" + " var rect = elem.getBoundingClientRect();" + " return [rect.left, rect.top, rect.right, rect.bottom];" + "})()"); + QVariantList coords = evaluateJavaScriptSync(page, jsCode).toList(); + + if (coords.count() != 4) { + qWarning("elementGeometry faield."); + return QRect(); + } + + return QRect(coords[0].toInt(), coords[1].toInt(), coords[2].toInt(), coords[3].toInt()); +} + + +#define W_QSKIP(a, b) QSKIP(a) + +#define W_QTEST_MAIN(TestObject, params) \ +int main(int argc, char *argv[]) \ +{ \ + QList<const char *> w_argv(argc); \ + for (int i = 0; i < argc; ++i) \ + w_argv[i] = argv[i]; \ + for (int i = 0; i < params.size(); ++i) \ + w_argv.append(params[i].data()); \ + int w_argc = w_argv.size(); \ + \ + QApplication app(w_argc, const_cast<char **>(w_argv.data())); \ + app.setAttribute(Qt::AA_Use96Dpi, true); \ + QTEST_DISABLE_KEYPAD_NAVIGATION \ + TestObject tc; \ + QTEST_SET_MAIN_SOURCE_PATH \ + return QTest::qExec(&tc, argc, argv); \ +} |