summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2018-06-04 16:11:50 +0200
committerQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2018-06-04 16:11:50 +0200
commit6f979c0e259c55f76bcd04748e3f7080ed35045f (patch)
tree7a6eb91b83ffcd7a2bdb30e8deff1b48d60b26f9
parentc8c54c868204fd6159bcc8e6c8e71d76907f8c31 (diff)
parent6e768c55e605008398168d1524cb02045e047c3d (diff)
Merge remote-tracking branch 'origin/5.11' into dev
Conflicts: .qmake.conf Change-Id: Ia44a20a78bc327ce199f02e1c1d2fb6c246d6675
-rw-r--r--src/plugins/platforms/webgl/qwebglmain.cpp4
-rw-r--r--src/plugins/platforms/webgl/webqt.jsx88
-rw-r--r--tests/global/global.cfg5
-rw-r--r--tests/plugins/platforms/platforms.pro4
-rw-r--r--tests/plugins/platforms/webgl/BLACKLIST1
-rw-r--r--tests/plugins/platforms/webgl/basic_scene.qml3
-rw-r--r--tests/plugins/platforms/webgl/parameters.h114
-rw-r--r--tests/plugins/platforms/webgl/tst_webgl.cpp286
-rw-r--r--tests/plugins/platforms/webgl/webgl.pro19
-rw-r--r--tests/plugins/plugins.pro4
-rw-r--r--tests/tests.pro2
11 files changed, 485 insertions, 45 deletions
diff --git a/src/plugins/platforms/webgl/qwebglmain.cpp b/src/plugins/platforms/webgl/qwebglmain.cpp
index dd14bec..6c9c324 100644
--- a/src/plugins/platforms/webgl/qwebglmain.cpp
+++ b/src/plugins/platforms/webgl/qwebglmain.cpp
@@ -52,13 +52,13 @@ QPlatformIntegration* QWebGLIntegrationPlugin::create(const QString& system,
const QStringList parts = parameter.split('=');
if (parts.first() == QStringLiteral("port")) {
if (parts.size() != 2) {
- qCCritical(lcWebGL, "QWebGLIntegrationPlugin::create: No port specified");
+ qCCritical(lcWebGL, "Port parameter specified with no value");
return nullptr;
}
bool ok;
port = parts.last().toUShort(&ok);
if (!ok) {
- qCCritical(lcWebGL, "QWebGLIntegrationPlugin::create: Invalid port number");
+ qCCritical(lcWebGL, "Invalid port number");
return nullptr;
}
}
diff --git a/src/plugins/platforms/webgl/webqt.jsx b/src/plugins/platforms/webgl/webqt.jsx
index d2c5b0b..cf0bae4 100644
--- a/src/plugins/platforms/webgl/webqt.jsx
+++ b/src/plugins/platforms/webgl/webqt.jsx
@@ -274,29 +274,30 @@ window.onload = function () {
canvas.addEventListener('DOMMouseScroll', handleMouseWheel, { passive: true });
function handleTouch(event) {
- var object = {};
- object["type"] = "touch";
- object["name"] = name;
- object["time"] = new Date().getTime();
- object["event"] = event.type;
- object["changedTouches"] = [];
- object["stationaryTouches"] = [];
-
+ var object = {
+ "type" : "touch",
+ "name" : name,
+ "time" : new Date().getTime(),
+ "event" : event.type,
+ "changedTouches" : [],
+ "stationaryTouches" : [],
+ };
var addTouch = function(changedTouch, isChanged) {
- var touch = {};
- touch["clientX"] = changedTouch.clientX;
- touch["clientY"] = changedTouch.clientY;
- touch["force"] = changedTouch.force;
- touch["identifier"] = changedTouch.identifier;
- touch["pageX"] = changedTouch.pageX;
- touch["pageY"] = changedTouch.pageY;
- touch["radiousX"] = changedTouch.radiousX;
- touch["radiousY"] = changedTouch.radiousY;
- touch["rotatingAngle"] = changedTouch.rotatingAngle;
- touch["screenX"] = changedTouch.screenX;
- touch["screenY"] = changedTouch.screenY;
- touch["normalPositionX"] = changedTouch.screenX / screen.width;
- touch["normalPositionY"] = changedTouch.screenY / screen.height;
+ var touch = {
+ "clientX" : changedTouch.clientX,
+ "clientY" : changedTouch.clientY,
+ "force" : changedTouch.force,
+ "identifier" : changedTouch.identifier,
+ "pageX" : changedTouch.pageX,
+ "pageY" : changedTouch.pageY,
+ "radiousX" : changedTouch.radiousX,
+ "radiousY" : changedTouch.radiousY,
+ "rotatingAngle" : changedTouch.rotatingAngle,
+ "screenX" : changedTouch.screenX,
+ "screenY" : changedTouch.screenY,
+ "normalPositionX" : changedTouch.screenX / screen.width,
+ "normalPositionY" : changedTouch.screenY / screen.height,
+ };
if (isChanged)
object.changedTouches.push(touch);
else
@@ -786,14 +787,15 @@ window.onload = function () {
d.drawArrayBuf = gl.createBuffer();
gl._bindBuffer(gl.ARRAY_BUFFER, d.drawArrayBuf);
for (var i = 4; i < arguments.length; i += 6) {
- var subData = {};
- subData["index"] = arguments[i + 0];
- subData["size"] = arguments[i + 1];
- subData["type"] = arguments[i + 2];
- subData["normalized"] = arguments[i + 3];
- subData["stride"] = arguments[i + 4];
- subData["offset"] = 0;
- subData["data"] = arguments[i + 5];
+ var subData = {
+ "index" : arguments[i + 0],
+ "size" : arguments[i + 1],
+ "type" : arguments[i + 2],
+ "normalized" : arguments[i + 3],
+ "stride" : arguments[i + 4],
+ "offset" : 0,
+ "data" : arguments[i + 5],
+ };
subDataParts.push(subData);
bufferSize += subData.data.length;
}
@@ -921,20 +923,20 @@ window.onload = function () {
var view = new DataView(event.data);
var offset = 0;
var obj = { "parameters" : [] };
- obj["function"] = supportedFunctions[view.getUint8(offset)];
+ obj.function = supportedFunctions[view.getUint8(offset)];
offset += 1;
if (obj.function in commandsNeedingResponse) {
- obj["id"] = view.getUint32(offset);
+ obj.id = view.getUint32(offset);
offset += 4;
}
- if (obj["function"] === "makeCurrent")
- obj["parameterCount"] = 4;
- else if (obj["function"] === "swapBuffers")
- obj["parameterCount"] = 0;
- else if (obj["function"] == "drawArrays")
- obj["parameterCount"] = null; // glDrawArrays has a variable number of arguments
+ if (obj.function === "makeCurrent")
+ obj.parameterCount = 4;
+ else if (obj.function === "swapBuffers")
+ obj.parameterCount = 0;
+ else if (obj.function == "drawArrays")
+ obj.parameterCount = null; // glDrawArrays has a variable number of arguments
else
- obj["parameterCount"] = gl[obj["function"]].length;
+ obj.parameterCount = gl[obj.function].length;
function deserialize(container, count) {
for (var i = 0; count != null ? i < count : offset + 4 < event.data.byteLength; ++i) {
var character = view.getUint8(offset);
@@ -1120,11 +1122,11 @@ window.onload = function () {
} else if (obj.type === "change_title") {
document.title = obj.text;
} else if (obj.type === "connect") {
- supportedFunctions = obj["supportedFunctions"];
- var sysinfo = obj["sysinfo"];
- if (obj["debug"])
+ supportedFunctions = obj.supportedFunctions;
+ var sysinfo = obj.sysinfo;
+ if (obj.debug)
DEBUG = 1;
- if (obj["loadingScreen"] === "0")
+ if (obj.loadingScreen === "0")
LOADINGSCREEN = 0;
console.log(sysinfo);
} else {
diff --git a/tests/global/global.cfg b/tests/global/global.cfg
new file mode 100644
index 0000000..903a388
--- /dev/null
+++ b/tests/global/global.cfg
@@ -0,0 +1,5 @@
+<config>
+<modules>
+<module name="QtWebGLStreaming" qtname="webgl"/>
+</modules>
+</config>
diff --git a/tests/plugins/platforms/platforms.pro b/tests/plugins/platforms/platforms.pro
new file mode 100644
index 0000000..836b27b
--- /dev/null
+++ b/tests/plugins/platforms/platforms.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ webgl
diff --git a/tests/plugins/platforms/webgl/BLACKLIST b/tests/plugins/platforms/webgl/BLACKLIST
new file mode 100644
index 0000000..fbe6866
--- /dev/null
+++ b/tests/plugins/platforms/webgl/BLACKLIST
@@ -0,0 +1 @@
+windows 32bit ci
diff --git a/tests/plugins/platforms/webgl/basic_scene.qml b/tests/plugins/platforms/webgl/basic_scene.qml
new file mode 100644
index 0000000..617bdaa
--- /dev/null
+++ b/tests/plugins/platforms/webgl/basic_scene.qml
@@ -0,0 +1,3 @@
+import QtQuick 2.0
+
+Item {}
diff --git a/tests/plugins/platforms/webgl/parameters.h b/tests/plugins/platforms/webgl/parameters.h
new file mode 100644
index 0000000..5f07e9e
--- /dev/null
+++ b/tests/plugins/platforms/webgl/parameters.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt WebGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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$
+**
+****************************************************************************/
+
+#include <QtCore/qdatastream.h>
+#include <QtCore/qvariant.h>
+
+#ifndef PARAMETERS_H
+#define PARAMETERS_H
+
+QT_BEGIN_NAMESPACE
+
+namespace Parameters
+{
+
+QVariantList read(const QByteArray &data, QDataStream &stream, quint32 &offset, int count);
+
+template<typename T>
+T readNext(QDataStream &stream, quint32 &offset)
+{
+ T value;
+ stream >> value;
+ offset += sizeof(T);
+ return value;
+}
+
+template<>
+QString readNext(QDataStream &stream, quint32 &offset)
+{
+ QString value;
+ stream >> value;
+ offset += quint32(int(sizeof(qint32)) + value.size());
+ return value;
+}
+
+template<>
+QByteArray readNext(QDataStream &stream, quint32 &offset)
+{
+ QByteArray data;
+ stream >> data;
+ offset += quint32(int(sizeof(qint32)) + data.size());
+ return data;
+}
+
+QVariantList readNextArray(const QByteArray &data, QDataStream &stream, quint32 &offset)
+{
+ quint8 count;
+ stream >> count;
+ offset += sizeof(count);
+ return read(data, stream, offset, count);
+}
+
+QVariant readNext(const QByteArray &data, QDataStream &stream, quint32 &offset)
+{
+ char type;
+ offset += quint32(stream.readRawData(&type, 1));
+ switch (type) {
+ case 'i': return readNext<qint32>(stream, offset);
+ case 'u': return readNext<quint32>(stream, offset);
+ case 'd': return readNext<double>(stream, offset);
+ case 'b': return readNext<quint8>(stream, offset);
+ case 's': return readNext<QString>(stream, offset);
+ case 'x': return readNext<QByteArray>(stream, offset);
+ case 'a': return readNextArray(data, stream, offset);
+ }
+ return QVariant();
+}
+
+QVariantList read(const QByteArray &data, QDataStream &stream, quint32 &offset)
+{
+ QVariantList parameters;
+ for (const auto size = data.size(); int(offset + 4) < size;)
+ parameters.append(readNext(data, stream, offset));
+ return parameters;
+}
+
+QVariantList read(const QByteArray &data, QDataStream &stream, quint32 &offset, int count)
+{
+ QVariantList parameters;
+ for (int i = 0; i < count; ++i)
+ parameters.append(readNext(data, stream, offset));
+ return parameters;
+}
+
+}
+
+QT_END_NAMESPACE
+
+#endif // PARAMETERS_H
diff --git a/tests/plugins/platforms/webgl/tst_webgl.cpp b/tests/plugins/platforms/webgl/tst_webgl.cpp
new file mode 100644
index 0000000..306d6fd
--- /dev/null
+++ b/tests/plugins/platforms/webgl/tst_webgl.cpp
@@ -0,0 +1,286 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt WebGL module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** 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$
+**
+****************************************************************************/
+
+#include <QtTest/qtest.h>
+#include <QtTest/qsignalspy.h>
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qregularexpression.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qopengl.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkreply.h>
+#include <QtWebSockets/qwebsocket.h>
+
+#include "parameters.h"
+
+#include <memory>
+
+#define PORT 8080
+#define PORTSTRING QT_STRINGIFY(PORT)
+
+class tst_WebGL : public QObject
+{
+ Q_OBJECT
+
+ QNetworkAccessManager manager;
+ QWebSocket webSocket;
+ QStringList functions;
+ QProcess process;
+
+signals:
+ void command(const QString &name, const QVariantList &parameters);
+ void queryCommand(const QString &name, int id, const QVariantList &parameters);
+
+public slots:
+ void parseTextMessage(const QString &text);
+ void parseBinaryMessage(const QByteArray &data);
+
+private slots:
+ void initTestCase();
+
+ void init();
+ void cleanup();
+
+ void checkFunctionCount_data();
+ void checkFunctionCount();
+
+ void waitForSwapBuffers_data();
+ void waitForSwapBuffers();
+};
+
+void tst_WebGL::parseTextMessage(const QString &text)
+{
+ const auto document = QJsonDocument::fromJson(text.toUtf8());
+ if (document["type"].toString() == "connect") {
+ const auto supportedFunctions = document["supportedFunctions"].toArray();
+ functions.clear();
+ for (const auto &function : supportedFunctions)
+ functions.append(function.toString());
+ } else if (document["type"] == "create_canvas") {
+ const QJsonDocument defaultValuesMessage {
+ QJsonObject {
+ { QLatin1String("type"), QLatin1String("default_context_parameters") },
+ { QString::number(GL_EXTENSIONS),
+ QLatin1String("GL_OES_element_index_uint "
+ "GL_OES_standard_derivatives "
+ "GL_OES_depth_texture GL_OES_packed_depth_stencil") },
+ { QString::number(GL_BLEND), false },
+ { QString::number(GL_DEPTH_TEST), false },
+ { QString::number(GL_MAX_TEXTURE_SIZE), 512 },
+ { QString::number(GL_MAX_VERTEX_ATTRIBS), 16},
+ { QString::number(GL_RENDERER), "Test WebGL"},
+ { QString::number(GL_SCISSOR_TEST), false },
+ { QString::number(GL_STENCIL_TEST), false },
+ { QString::number(GL_UNPACK_ALIGNMENT), 4 },
+ { QString::number(GL_VENDOR), "Qt" },
+ { QString::number(GL_VIEWPORT), QJsonArray{ 0, 0, 640, 480 } },
+ { QLatin1String("name"), document["winId"] }
+ },
+ };
+ webSocket.sendTextMessage(defaultValuesMessage.toJson());
+ }
+}
+
+void tst_WebGL::parseBinaryMessage(const QByteArray &data)
+{
+ const QSet<QString> commandsNeedingResponse {
+ QLatin1String("swapBuffers"),
+ QLatin1String("checkFramebufferStatus"),
+ QLatin1String("createProgram"),
+ QLatin1String("createShader"),
+ QLatin1String("genBuffers"),
+ QLatin1String("genFramebuffers"),
+ QLatin1String("genRenderbuffers"),
+ QLatin1String("genTextures"),
+ QLatin1String("getAttachedShaders"),
+ QLatin1String("getAttribLocation"),
+ QLatin1String("getBooleanv"),
+ QLatin1String("getError"),
+ QLatin1String("getFramebufferAttachmentParameteriv"),
+ QLatin1String("getIntegerv"),
+ QLatin1String("getParameter"),
+ QLatin1String("getProgramInfoLog"),
+ QLatin1String("getProgramiv"),
+ QLatin1String("getRenderbufferParameteriv"),
+ QLatin1String("getShaderiv"),
+ QLatin1String("getShaderPrecisionFormat"),
+ QLatin1String("getString"),
+ QLatin1String("getTexParameterfv"),
+ QLatin1String("getTexParameteriv"),
+ QLatin1String("getUniformfv"),
+ QLatin1String("getUniformLocation"),
+ QLatin1String("getUniformiv"),
+ QLatin1String("getVertexAttribfv"),
+ QLatin1String("getVertexAttribiv"),
+ QLatin1String("getShaderSource"),
+ QLatin1String("getShaderInfoLog"),
+ QLatin1String("isRenderbuffer")
+ };
+
+ quint32 offset = 0;
+ QString function;
+ int id = -1;
+ QDataStream stream(data);
+ {
+ quint8 functionIndex;
+ stream >> functionIndex;
+ offset += sizeof(functionIndex);
+ function = functions[functionIndex];
+ if (commandsNeedingResponse.contains(function)) {
+ stream >> id;
+ offset += sizeof(id);
+ }
+ }
+ const auto parameters = Parameters::read(data, stream, offset);
+ {
+ quint32 magic = 0;
+ stream >> magic;
+ offset += sizeof(magic);
+ QCOMPARE(magic, 0xbaadf00d);
+ }
+ QCOMPARE(int(offset), data.size());
+ if (id == -1)
+ emit command(function, parameters);
+ else
+ emit queryCommand(function, id, parameters);
+}
+
+void tst_WebGL::initTestCase()
+{
+ connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &tst_WebGL::parseBinaryMessage);
+ connect(&webSocket, &QWebSocket::textMessageReceived, this, &tst_WebGL::parseTextMessage);
+}
+
+void tst_WebGL::init()
+{
+ QFETCH(QString, scene);
+ const auto tryToConnect = [=](quint16 port = PORT) {
+ QTcpSocket socket;
+ socket.connectToHost("localhost", port);
+ QTRY_LOOP_IMPL(socket.state() == QTcpSocket::ConnectedState ||
+ socket.state() == QTcpSocket::UnconnectedState, 1000, 50);
+ return socket.state() == QTcpSocket::ConnectedState;
+ };
+
+ QVERIFY2(!tryToConnect(), "An application is listening on port " PORTSTRING);
+
+ QString executableName = QLatin1String("qmlscene");
+#if defined(Q_OS_WIN)
+ executableName += QString::fromLatin1(".exe");
+#endif
+
+ process.setProcessChannelMode(QProcess::MergedChannels);
+ process.setProgram(QLibraryInfo::location(QLibraryInfo::BinariesPath) + QChar('/')
+ + executableName);
+ process.setArguments(QStringList { QDir::toNativeSeparators(scene) });
+ process.setEnvironment(QProcess::systemEnvironment()
+ << "QT_QPA_PLATFORM=webgl:port=" PORTSTRING
+ << "QT_LOGGING_RULES=qt.qpa.webgl.*=true");
+ process.start();
+ process.waitForStarted();
+ QVERIFY(process.isOpen());
+ connect(&process, &QProcess::readyReadStandardOutput, [=]() {
+ while (process.bytesAvailable())
+ qDebug() << process.pid() << process.readLine();
+ });
+ QTRY_VERIFY(tryToConnect());
+ const QJsonDocument connectMessage {
+ QJsonObject {
+ { QLatin1String("type"), QLatin1String("connect") },
+ { QLatin1String("width"), 1920 },
+ { QLatin1String("height"), 1080 },
+ { QLatin1String("physicalWidth"), 531.3 },
+ { QLatin1String("physicalHeight"), 298.9 }
+ }
+ };
+
+ auto reply = manager.get(QNetworkRequest(QUrl("http://localhost:" PORTSTRING "/webqt.js")));
+ QSignalSpy replyFinishedSpy(reply, &QNetworkReply::finished);
+ QTRY_VERIFY(!replyFinishedSpy.isEmpty());
+ reply->readLine();
+ const auto portString = reply->readLine().trimmed();
+ QVERIFY(portString.size());
+ const QRegularExpression rx("var port = (\\d+);");
+ const auto match = rx.match(portString);
+ QVERIFY(!match.captured(1).isEmpty());
+ QVERIFY(match.captured(1).toUInt() <= std::numeric_limits<quint16>::max());
+ const auto websocketPort = quint16(match.captured(1).toUInt());
+
+ QTRY_VERIFY(tryToConnect(websocketPort));
+ QSignalSpy connected(&webSocket, &QWebSocket::connected);
+ webSocket.open(QUrl(QString::fromLatin1("ws://localhost:%1").arg(websocketPort)));
+ QTRY_VERIFY(!connected.isEmpty());
+ webSocket.sendTextMessage(connectMessage.toJson());
+ QVERIFY(webSocket.state() == QAbstractSocket::ConnectedState);
+}
+
+void tst_WebGL::cleanup()
+{
+ webSocket.close();
+
+ process.kill();
+ process.waitForFinished();
+}
+
+void tst_WebGL::checkFunctionCount_data()
+{
+ QTest::addColumn<QString>("scene"); // Fetched in tst_WebGL::init
+ QTest::newRow("Basic scene") << QFINDTESTDATA("basic_scene.qml");
+}
+
+void tst_WebGL::checkFunctionCount()
+{
+ QCOMPARE(functions.size(), 147);
+}
+
+void tst_WebGL::waitForSwapBuffers_data()
+{
+ QTest::addColumn<QString>("scene"); // Fetched in tst_WebGL::init
+ QTest::newRow("Basic scene") << QFINDTESTDATA("basic_scene.qml");
+}
+
+void tst_WebGL::waitForSwapBuffers()
+{
+ QSignalSpy spy(this, &tst_WebGL::queryCommand);
+ QTRY_VERIFY(std::find_if(spy.cbegin(), spy.cend(), [](const QList<QVariant> &list) {
+ // Our connect message changed the scene's size, forcing a swapBuffers() call.
+ return list.first() == QLatin1String("swapBuffers");
+ }) != spy.cend());
+}
+
+QTEST_MAIN(tst_WebGL)
+
+#include "tst_webgl.moc"
diff --git a/tests/plugins/platforms/webgl/webgl.pro b/tests/plugins/platforms/webgl/webgl.pro
new file mode 100644
index 0000000..add4016
--- /dev/null
+++ b/tests/plugins/platforms/webgl/webgl.pro
@@ -0,0 +1,19 @@
+CONFIG += testcase
+
+QT += \
+ testlib \
+ quick \
+ websockets
+
+TARGET = tst_webgl
+
+HEADERS += \
+ parameters.h
+
+SOURCES += \
+ tst_webgl.cpp
+
+TESTDATA = *.qml
+
+OTHER_FILES += \
+ basic_scene.qml
diff --git a/tests/plugins/plugins.pro b/tests/plugins/plugins.pro
new file mode 100644
index 0000000..2616dc4
--- /dev/null
+++ b/tests/plugins/plugins.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+
+SUBDIRS += \
+ platforms
diff --git a/tests/tests.pro b/tests/tests.pro
index 566e172..22508cc 100644
--- a/tests/tests.pro
+++ b/tests/tests.pro
@@ -1,2 +1,4 @@
TEMPLATE = subdirs
+SUBDIRS += \
+ plugins