From 1df3ace8bf9c553b980e2f1d3b934564a30d2f62 Mon Sep 17 00:00:00 2001 From: Sebastian Feustel Date: Thu, 29 Feb 2024 15:23:51 +0100 Subject: [PATCH 1/6] Add ScreenshotRemote Function this create a screenshot like the normal screenshot, except that it outputs the image as Base64 String --- lib/CMakeLists.txt | 2 ++ lib/include/Spix/TestServer.h | 1 + lib/src/AnyRpcServer.cpp | 4 ++++ lib/src/Commands/ScreenshotRemote.cpp | 26 +++++++++++++++++++++++ lib/src/Commands/ScreenshotRemote.h | 23 ++++++++++++++++++++ lib/src/Scene/Mock/MockScene.cpp | 5 +++++ lib/src/Scene/Mock/MockScene.h | 2 +- lib/src/Scene/Qt/QtScene.cpp | 30 +++++++++++++++++++++++++++ lib/src/Scene/Qt/QtScene.h | 2 ++ lib/src/Scene/Scene.h | 1 + lib/src/TestServer.cpp | 11 ++++++++++ 11 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 lib/src/Commands/ScreenshotRemote.cpp create mode 100644 lib/src/Commands/ScreenshotRemote.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7a270d3..8962b9d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -60,6 +60,8 @@ set(SOURCES src/Commands/Quit.h src/Commands/Screenshot.cpp src/Commands/Screenshot.h + src/Commands/ScreenshotRemote.cpp + src/Commands/ScreenshotRemote.h src/Commands/SetProperty.cpp src/Commands/SetProperty.h src/Commands/Wait.cpp diff --git a/lib/include/Spix/TestServer.h b/lib/include/Spix/TestServer.h index f3da289..fd9f98f 100644 --- a/lib/include/Spix/TestServer.h +++ b/lib/include/Spix/TestServer.h @@ -64,6 +64,7 @@ class SPIX_EXPORT TestServer { std::vector getErrors(); void takeScreenshot(ItemPath targetItem, std::string filePath); + std::string takeScreenshotRemote(ItemPath targetItem); void quit(); protected: diff --git a/lib/src/AnyRpcServer.cpp b/lib/src/AnyRpcServer.cpp index 4551daf..00f936d 100644 --- a/lib/src/AnyRpcServer.cpp +++ b/lib/src/AnyRpcServer.cpp @@ -99,6 +99,10 @@ AnyRpcServer::AnyRpcServer(int anyrpcPort) return takeScreenshot(std::move(targetItem), std::move(filePath)); }); + utils::AddFunctionToAnyRpc(methodManager, "takeScreenshotRemote", + "Take a screenshot of the object and send as base64 string | takeScreenshotRemote(string pathToTargetedItem)", + [this](std::string targetItem) { return takeScreenshotRemote(std::move(targetItem)); }); + utils::AddFunctionToAnyRpc(methodManager, "quit", "Close the app | quit()", [this] { quit(); }); utils::AddFunctionToAnyRpc(methodManager, "command", diff --git a/lib/src/Commands/ScreenshotRemote.cpp b/lib/src/Commands/ScreenshotRemote.cpp new file mode 100644 index 0000000..a802215 --- /dev/null +++ b/lib/src/Commands/ScreenshotRemote.cpp @@ -0,0 +1,26 @@ +/*** + * Copyright (C) Falko Axmann. All rights reserved. + * Licensed under the MIT license. + * See LICENSE.txt file in the project root for full license information. + ****/ + +#include "ScreenshotRemote.h" + +#include +namespace spix { +namespace cmd { + +ScreenshotRemote::ScreenshotRemote(ItemPath targetItemPath, std::promise promise) +: m_itemPath {std::move(targetItemPath)} +, m_promise(std::move(promise)) +{ +} + +void ScreenshotRemote::execute(CommandEnvironment& env) +{ + auto value = env.scene().takeScreenshotRemote(m_itemPath); + m_promise.set_value(value); +} + +} // namespace cmd +} // namespace spix \ No newline at end of file diff --git a/lib/src/Commands/ScreenshotRemote.h b/lib/src/Commands/ScreenshotRemote.h new file mode 100644 index 0000000..f34d20a --- /dev/null +++ b/lib/src/Commands/ScreenshotRemote.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Command.h" +#include + +#include + +namespace spix { +namespace cmd { + +class ScreenshotRemote : public Command { +public: + ScreenshotRemote(ItemPath targetItemPath, std::promise promise); + + void execute(CommandEnvironment& env) override; + +private: + ItemPath m_itemPath; + std::promise m_promise; +}; + +} // namespace cmd +} // namespace spix diff --git a/lib/src/Scene/Mock/MockScene.cpp b/lib/src/Scene/Mock/MockScene.cpp index c9a2d87..ac081e1 100644 --- a/lib/src/Scene/Mock/MockScene.cpp +++ b/lib/src/Scene/Mock/MockScene.cpp @@ -28,6 +28,11 @@ void MockScene::takeScreenshot(const ItemPath&, const std::string&) { } +std::string MockScene::takeScreenshotRemote(const ItemPath&) +{ + return "Base64 String"; +} + void MockScene::addItemAtPath(MockItem item, const ItemPath& path) { m_items.emplace(std::make_pair(path.string(), std::move(item))); diff --git a/lib/src/Scene/Mock/MockScene.h b/lib/src/Scene/Mock/MockScene.h index 17a9bdb..5f16da0 100644 --- a/lib/src/Scene/Mock/MockScene.h +++ b/lib/src/Scene/Mock/MockScene.h @@ -25,7 +25,7 @@ class SPIX_EXPORT MockScene : public Scene { // Tasks void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) override; - + std::string takeScreenshotRemote(const ItemPath& targetItem); // Mock stuff void addItemAtPath(MockItem item, const ItemPath& path); MockEvents& mockEvents(); diff --git a/lib/src/Scene/Qt/QtScene.cpp b/lib/src/Scene/Qt/QtScene.cpp index 75aa5b0..d442123 100644 --- a/lib/src/Scene/Qt/QtScene.cpp +++ b/lib/src/Scene/Qt/QtScene.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include #include @@ -139,4 +141,32 @@ void QtScene::takeScreenshot(const ItemPath& targetItem, const std::string& file image.save(QString::fromStdString(filePath)); } +std::string QtScene::takeScreenshotRemote(const ItemPath& targetItem) +{ + auto item = getQQuickItemAtPath(targetItem); + if (!item) { + return ""; + } + + // take screenshot of the full window + auto windowImage = item->window()->grabWindow(); + + // get the rect of the item in window space in pixels, account for the device pixel ratio + QRectF imageCropRectItemSpace {0, 0, item->width(), item->height()}; + auto imageCropRectF = item->mapRectToScene(imageCropRectItemSpace); + QRect imageCropRect(imageCropRectF.x() * windowImage.devicePixelRatio(), + imageCropRectF.y() * windowImage.devicePixelRatio(), imageCropRectF.width() * windowImage.devicePixelRatio(), + imageCropRectF.height() * windowImage.devicePixelRatio()); + + // crop the window image to the item rect + auto image = windowImage.copy(imageCropRect); + QByteArray byteArray; + QBuffer buffer(&byteArray); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "PNG"); + buffer.close(); + + return byteArray.toBase64().toStdString(); +} + } // namespace spix diff --git a/lib/src/Scene/Qt/QtScene.h b/lib/src/Scene/Qt/QtScene.h index c166af3..8ff9cf2 100644 --- a/lib/src/Scene/Qt/QtScene.h +++ b/lib/src/Scene/Qt/QtScene.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include @@ -28,6 +29,7 @@ class QtScene : public Scene { // Tasks void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) override; + std::string takeScreenshotRemote(const ItemPath& targetItem); private: QtEvents m_events; diff --git a/lib/src/Scene/Scene.h b/lib/src/Scene/Scene.h index a5f4bc2..9ea4739 100644 --- a/lib/src/Scene/Scene.h +++ b/lib/src/Scene/Scene.h @@ -37,6 +37,7 @@ class Scene { // Tasks virtual void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) = 0; + virtual std::string takeScreenshotRemote(const ItemPath& targetItem) = 0; }; } // namespace spix diff --git a/lib/src/TestServer.cpp b/lib/src/TestServer.cpp index 45eee1e..fe5736c 100644 --- a/lib/src/TestServer.cpp +++ b/lib/src/TestServer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -165,6 +166,16 @@ void TestServer::takeScreenshot(ItemPath targetItem, std::string filePath) m_cmdExec->enqueueCommand(targetItem, std::move(filePath)); } +std::string TestServer::takeScreenshotRemote(ItemPath targetItem) +{ + std::promise promise; + auto result = promise.get_future(); + auto cmd = std::make_unique(targetItem, std::move(promise)); + m_cmdExec->enqueueCommand(std::move(cmd)); + + return result.get(); +} + void TestServer::quit() { m_cmdExec->enqueueCommand(); From c4745158b147b8c740a5785c7ccb431060c7b73d Mon Sep 17 00:00:00 2001 From: Sebastian Feustel <48125666+53845714nF@users.noreply.github.com> Date: Sun, 12 May 2024 09:54:38 +0200 Subject: [PATCH 2/6] Fixing CI (#113) * MacOS 14 only supports Arm64 but Qt5 not, so I pin it to macOS 13 on Qt5. * Bump Checkout Action from 3 to 4, because it uses an old Node Version. * Adding an extra test case for macOS, because on macOS 14 with Qt6 the -platform minimal is not supported. --------- Co-authored-by: Sebastian Feustel Co-authored-by: Falko Axmann <51922941+faaxm@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- .github/workflows/clang-format.yml | 2 +- .github/workflows/run-examples.yml | 20 ++++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34f6066..f891d60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: # Qt v5.15.2 {name: 'qt-5.15.2 ubuntu', os: ubuntu-latest, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'ON'}, {name: 'qt-5.15.2 windows', os: windows-latest, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'ON', cmake_flags: '"-DAnyRPC_ROOT=C:/Program Files (x86)/AnyRPC" "-DGTest_ROOT=C:/Program Files (x86)/googletest-distribution"'}, - {name: 'qt-5.15.2 macos', os: macos-latest, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'ON'}, + {name: 'qt-5.15.2 macos', os: macos-13, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'ON'}, # Qt v6.2.3 {name: 'qt-6.2.3 ubuntu', os: ubuntu-latest, build_type: Debug, qt_version: [6, 2, 3], shared_libs: 'OFF', tests: 'ON'}, {name: 'qt-6.2.3 windows', os: windows-latest, build_type: Debug, qt_version: [6, 2, 3], shared_libs: 'OFF', tests: 'ON', cmake_flags: '"-DAnyRPC_ROOT=C:/Program Files (x86)/AnyRPC" "-DGTest_ROOT=C:/Program Files (x86)/googletest-distribution"'}, @@ -36,7 +36,7 @@ jobs: ] steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Qt uses: jurplel/install-qt-action@v3 diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 360bd19..37e643b 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: DoozyX/clang-format-lint-action@v0.14 with: source: '.' diff --git a/.github/workflows/run-examples.yml b/.github/workflows/run-examples.yml index 5b32425..89d1664 100644 --- a/.github/workflows/run-examples.yml +++ b/.github/workflows/run-examples.yml @@ -11,17 +11,15 @@ jobs: # Qt v5.15.2 {name: 'qt-5.15.2 ubuntu', os: ubuntu-latest, build_type: Debug,qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'OFF'}, {name: 'qt-5.15.2 windows', os: windows-latest, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'OFF', cmake_flags: '"-DAnyRPC_ROOT=C:/Program Files (x86)/AnyRPC" "-DGTest_ROOT=C:/Program Files (x86)/googletest-distribution"'}, - {name: 'qt-5.15.2 macos', os: macos-latest, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'OFF'}, + {name: 'qt-5.15.2 macos', os: macos-13, build_type: Debug, qt_version: [5, 15, 2], shared_libs: 'OFF', tests: 'OFF'}, # Qt v6.2.3 {name: 'qt-6.2.3 ubuntu', os: ubuntu-latest, build_type: Debug, qt_version: [6, 2, 3], shared_libs: 'OFF', tests: 'OFF'}, {name: 'qt-6.2.3 windows', os: windows-latest, build_type: Debug, qt_version: [6, 2, 3], shared_libs: 'OFF', tests: 'OFF', cmake_flags: '"-DAnyRPC_ROOT=C:/Program Files (x86)/AnyRPC" "-DGTest_ROOT=C:/Program Files (x86)/googletest-distribution"'}, - - # Qt6 on GitHub Actions MacOS is currently bugged: https://bugreports.qt.io/browse/QTIFW-1592 - # {name: 'qt-6.2.3 macos', os: macos-latest, build_type: Debug, qt_version: [6, 2, 3], shared_libs: 'OFF', tests: 'OFF'}, + {name: 'qt-6.2.3 macos', os: macos-latest, build_type: Debug, qt_version: [6, 2, 3], shared_libs: 'OFF', tests: 'OFF'}, ] steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Qt uses: jurplel/install-qt-action@v3 @@ -42,9 +40,9 @@ jobs: - name: "Build" run: cmake --build build --config ${{ matrix.base.build_type }} - - - name: "Test GTest Examples (*nix)" - if: ${{ !contains(matrix.base.os, 'windows') }} + + - name: "Test GTest Examples (Linux)" + if: ${{ contains(matrix.base.os, 'ubuntu') }} run: | build/examples/GTest/SpixGTestExample -platform minimal build/examples/RepeaterLoader/SpixRepeaterLoaderExampleGTest -platform minimal @@ -54,3 +52,9 @@ jobs: run: | .\build\examples\GTest\${{ matrix.base.build_type }}\SpixGTestExample.exe -platform minimal .\build\examples\RepeaterLoader\${{ matrix.base.build_type }}\SpixRepeaterLoaderExampleGTest.exe -platform minimal + + - name: "Test GTest Examples (mac)" + if: ${{ contains(matrix.base.os, 'mac') }} + run: | + build/examples/GTest/SpixGTestExample + build/examples/RepeaterLoader/SpixRepeaterLoaderExampleGTest From bfb6ee3da4b06dfc6c574ec19c3f8c6f039eb0e2 Mon Sep 17 00:00:00 2001 From: Sebastian Feustel <48125666+53845714nF@users.noreply.github.com> Date: Sun, 12 May 2024 10:31:54 +0200 Subject: [PATCH 3/6] Extension of the mouseClick function (add proportion and offset) (#103) * Add new parameters (proportion and offset) to the mouse Click. It will be possible to click e.g. sliderarea at certain positions. * Create a new GTest for Mouse Click Position * Update examples/GTestMouseClickPosition/main.cpp --------- Co-authored-by: Sebastian Feustel Co-authored-by: Falko Axmann <51922941+faaxm@users.noreply.github.com> --- examples/CMakeLists.txt | 1 + .../GTestMouseClickPosition/CMakeLists.txt | 14 ++ .../GTestMouseClickPosition/ResultsView.qml | 43 +++++ examples/GTestMouseClickPosition/main.cpp | 153 ++++++++++++++++++ examples/GTestMouseClickPosition/main.qml | 99 ++++++++++++ examples/GTestMouseClickPosition/qml.qrc | 6 + lib/include/Spix/TestServer.h | 2 + lib/src/AnyRpcServer.cpp | 18 +++ lib/src/TestServer.cpp | 12 ++ lib/src/Utils/AnyRpcFunction.h | 9 ++ 10 files changed, 357 insertions(+) create mode 100644 examples/GTestMouseClickPosition/CMakeLists.txt create mode 100644 examples/GTestMouseClickPosition/ResultsView.qml create mode 100644 examples/GTestMouseClickPosition/main.cpp create mode 100644 examples/GTestMouseClickPosition/main.qml create mode 100644 examples/GTestMouseClickPosition/qml.qrc diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b5da580..37ba736 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(Basic) add_subdirectory(GTest) +add_subdirectory(GTestMouseClickPosition) add_subdirectory(ListGridView) add_subdirectory(RemoteCtrl) add_subdirectory(RepeaterLoader) diff --git a/examples/GTestMouseClickPosition/CMakeLists.txt b/examples/GTestMouseClickPosition/CMakeLists.txt new file mode 100644 index 0000000..7fa7459 --- /dev/null +++ b/examples/GTestMouseClickPosition/CMakeLists.txt @@ -0,0 +1,14 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(SPIX_QT_MAJOR "6" CACHE STRING "Major Qt version to build Spix against") + +find_package(Qt${SPIX_QT_MAJOR} COMPONENTS Core Quick REQUIRED) +find_package(GTest REQUIRED) + +add_executable(SpixGTestMouseClickPosition "main.cpp" "qml.qrc") +target_compile_definitions(SpixGTestMouseClickPosition PRIVATE $<$,$>:QT_QML_DEBUG>) +target_link_libraries(SpixGTestMouseClickPosition PRIVATE Qt${SPIX_QT_MAJOR}::Core Qt${SPIX_QT_MAJOR}::Quick GTest::GTest Spix) diff --git a/examples/GTestMouseClickPosition/ResultsView.qml b/examples/GTestMouseClickPosition/ResultsView.qml new file mode 100644 index 0000000..f46b913 --- /dev/null +++ b/examples/GTestMouseClickPosition/ResultsView.qml @@ -0,0 +1,43 @@ +import QtQuick 2.11 +import QtQuick.Controls 2.4 + +Rectangle { + id: resultsView + color: "#222222" + + function appendText(text) { + resultsArea.append(text); + } + + Text { + id: resultsTitle + text: "Result: " + color: "white" + font.bold: true + + anchors { + top: resultsView.top + left: resultsView.left + right: resultsView.right + + topMargin: 5 + leftMargin: 2 + bottomMargin: 2 + } + } + TextEdit { + id: resultsArea + objectName: "results" + color: "white" + anchors { + top: resultsTitle.bottom + left: resultsView.left + right: resultsView.right + bottom: resultsView.bottom + + leftMargin: 2 + rightMargin: 2 + bottomMargin: 2 + } + } +} diff --git a/examples/GTestMouseClickPosition/main.cpp b/examples/GTestMouseClickPosition/main.cpp new file mode 100644 index 0000000..de799db --- /dev/null +++ b/examples/GTestMouseClickPosition/main.cpp @@ -0,0 +1,153 @@ +/*** + * Copyright (C) Falko Axmann. All rights reserved. + * Licensed under the MIT license. + * See LICENSE.txt file in the project root for full license information. + ****/ + +/** + * This is a very basic example to demonstrate how to run your UI tests + * using GTest. It can be useful when you have to make sure that your + * UI tests work well in an existing, GTest based environment. + * + * Keep in mind that GTest is not designed for UI testing and that the + * order of the test execution is not guaranteed. Thus, you should only + * have one test per executable. + */ + +#include +#include +#include +#include + +#include +#include + +class SpixGTest; +static SpixGTest* srv; + +class SpixGTest : public spix::TestServer { +public: + SpixGTest(int argc, char* argv[]) + { + m_argc = argc; + m_argv = argv; + } + + int testResult() { return m_result.load(); } + +protected: + int m_argc; + char** m_argv; + std::atomic m_result {0}; + + void executeTest() override + { + srv = this; + ::testing::InitGoogleTest(&m_argc, m_argv); + auto testResult = RUN_ALL_TESTS(); + m_result.store(testResult); + } +}; + +TEST(GTestExample, ProportionAllButtons) +{ + const auto upperLeft = spix::Point(0.25, 0.25); + const auto upperRigth = spix::Point(0.75, 0.25); + const auto lowerLeft = spix::Point(0.25, 0.75); + const auto lowerRigth = spix::Point(0.75, 0.75); + + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), upperLeft); + srv->wait(std::chrono::milliseconds(500)); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), upperRigth); + srv->wait(std::chrono::milliseconds(500)); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), lowerLeft); + srv->wait(std::chrono::milliseconds(500)); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), lowerRigth); + srv->wait(std::chrono::milliseconds(500)); + auto result = srv->getStringProperty("mainWindow/results", "text"); + + auto expected_result = R"RSLT(Button 1 clicked +Button 2 clicked +Button 3 clicked +Button 4 clicked)RSLT"; + + EXPECT_EQ(result, expected_result); +} + +TEST(GTestExample, TooMuchProportion) +{ + // This Test click 200% under the Button 1 => under the Button 1 is Button 3 which will trigger. + const auto upperLeft = spix::Point(0.25, 2); + + srv->setStringProperty("mainWindow/results", "text", ""); + srv->mouseClick(spix::ItemPath("mainWindow/Button_1"), upperLeft); + srv->wait(std::chrono::milliseconds(500)); + + auto result = srv->getStringProperty("mainWindow/results", "text"); + + auto expected_result = R"RSLT(Button 3 clicked)RSLT"; + + EXPECT_EQ(result, expected_result); +} + +TEST(GTestExample, OffsetAllButtons) +{ + const auto upperLeft = spix::Point(100, 100); + const auto upperRigth = spix::Point(400, 100); + const auto lowerLeft = spix::Point(100, 200); + const auto lowerRigth = spix::Point(400, 200); + + srv->setStringProperty("mainWindow/results", "text", ""); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), spix::Point(0, 0), upperLeft); + srv->wait(std::chrono::milliseconds(500)); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), spix::Point(0, 0), upperRigth); + srv->wait(std::chrono::milliseconds(500)); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), spix::Point(0, 0), lowerLeft); + srv->wait(std::chrono::milliseconds(500)); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), spix::Point(0, 0), lowerRigth); + srv->wait(std::chrono::milliseconds(500)); + + auto result = srv->getStringProperty("mainWindow/results", "text"); + + auto expected_result = R"RSLT(Button 1 clicked +Button 2 clicked +Button 3 clicked +Button 4 clicked)RSLT"; + + EXPECT_EQ(result, expected_result); +} + +TEST(GTestExample, OffsetOnPositionWithNoButton) +{ + const auto centre = spix::Point(320, 120); + + srv->setStringProperty("mainWindow/results", "text", "No Click"); + srv->mouseClick(spix::ItemPath("mainWindow/Grid_1"), spix::Point(0, 0), centre); + srv->wait(std::chrono::milliseconds(500)); + + auto result = srv->getStringProperty("mainWindow/results", "text"); + + auto expected_result = R"RSLT(No Click)RSLT"; + + EXPECT_EQ(result, expected_result); + + srv->quit(); +} + +int main(int argc, char* argv[]) +{ + // Init Qt Qml Application + QGuiApplication app(argc, argv); + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + // Instantiate and run tests + SpixGTest tests(argc, argv); + auto bot = new spix::QtQmlBot(); + bot->runTestServer(tests); + + app.exec(); + return tests.testResult(); +} diff --git a/examples/GTestMouseClickPosition/main.qml b/examples/GTestMouseClickPosition/main.qml new file mode 100644 index 0000000..fa90945 --- /dev/null +++ b/examples/GTestMouseClickPosition/main.qml @@ -0,0 +1,99 @@ +import QtQuick 2.11 +import QtQuick.Window 2.11 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.11 + +Window { + objectName: "mainWindow" + visible: true + width: 640 + height: 480 + title: qsTr("Spix Example") + color: "#111111" + + + GridLayout { + objectName: "Grid_1" + rowSpacing: 20 + columnSpacing: 20 + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: parent.verticalCenter + } + columns: 2 + + Button { + objectName: "Button_1" + text: "Press Me" + Layout.fillHeight: true + Layout.fillWidth: true + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.AllButtons + + onClicked: + { + resultsView.appendText("Button 1 clicked") + } + } + } + Button { + objectName: "Button_2" + text: "Press Me" + Layout.fillHeight: true + Layout.fillWidth: true + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.AllButtons + + onClicked: + { + resultsView.appendText("Button 2 clicked") + } + } + } + Button { + objectName: "Button_3" + text: "Press Me" + Layout.fillHeight: true + Layout.fillWidth: true + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.AllButtons + + onClicked: + { + resultsView.appendText("Button 3 clicked") + } + } + } + Button { + objectName: "Button_4" + text: "Press Me" + Layout.fillHeight: true + Layout.fillWidth: true + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.AllButtons + + onClicked: + { + resultsView.appendText("Button 4 clicked") + } + } + } + } + + + ResultsView { + id: resultsView + anchors { + top: parent.verticalCenter + left: parent.left + right: parent.right + bottom: parent.bottom + } + } +} diff --git a/examples/GTestMouseClickPosition/qml.qrc b/examples/GTestMouseClickPosition/qml.qrc new file mode 100644 index 0000000..7a9de6b --- /dev/null +++ b/examples/GTestMouseClickPosition/qml.qrc @@ -0,0 +1,6 @@ + + + main.qml + ResultsView.qml + + diff --git a/lib/include/Spix/TestServer.h b/lib/include/Spix/TestServer.h index fd9f98f..925a59f 100644 --- a/lib/include/Spix/TestServer.h +++ b/lib/include/Spix/TestServer.h @@ -48,6 +48,8 @@ class SPIX_EXPORT TestServer { void wait(std::chrono::milliseconds waitTime); void mouseClick(ItemPath path); void mouseClick(ItemPath path, MouseButton mouseButton); + void mouseClick(ItemPath path, Point proportion); + void mouseClick(ItemPath path, Point proportion, Point offset); void mouseBeginDrag(ItemPath path); void mouseEndDrag(ItemPath path); void mouseDropUrls(ItemPath path, const std::vector& urls); diff --git a/lib/src/AnyRpcServer.cpp b/lib/src/AnyRpcServer.cpp index 00f936d..fe3f60c 100644 --- a/lib/src/AnyRpcServer.cpp +++ b/lib/src/AnyRpcServer.cpp @@ -37,6 +37,24 @@ AnyRpcServer::AnyRpcServer(int anyrpcPort) "mouseButton)", [this](std::string path, int mouseButton) { mouseClick(std::move(path), mouseButton); }); + utils::AddFunctionToAnyRpc(methodManager, "mouseClickWithOffset", + "Click on the object at the given path with the given offset" + "(absolute pixel) | mouseClickWithOffset(string path, float " + "offsetX, float offsetY)", + [this](std::string path, double offsetX, double offsetY) { + auto proportion = Point(0, 0); + auto offset = Point(offsetX, offsetY); + mouseClick(std::move(path), proportion, offset); + }); + + utils::AddFunctionToAnyRpc(methodManager, "mouseClickWithProportion", + "Click on the object at the given path with the given proportion (In percent)" + "| mouseClickWithProportion(string path, float proportionX, float proportionY)", + [this](std::string path, double proportionX, double proportionY) { + auto proportion = Point(proportionX, proportionY); + mouseClick(std::move(path), proportion); + }); + utils::AddFunctionToAnyRpc(methodManager, "mouseBeginDrag", "Begin a drag with the mouse | mouseBeginDrag(string path)", [this](std::string path) { mouseBeginDrag(std::move(path)); }); diff --git a/lib/src/TestServer.cpp b/lib/src/TestServer.cpp index fe5736c..8b44481 100644 --- a/lib/src/TestServer.cpp +++ b/lib/src/TestServer.cpp @@ -70,6 +70,18 @@ void TestServer::mouseClick(ItemPath path) m_cmdExec->enqueueCommand(path, spix::MouseButtons::Left); } +void TestServer::mouseClick(ItemPath path, Point proportion) +{ + auto pathWithProportion = ItemPosition(path.string(), proportion); + m_cmdExec->enqueueCommand(pathWithProportion, spix::MouseButtons::Left); +} + +void TestServer::mouseClick(ItemPath path, Point proportion, Point offset) +{ + auto pathWithOffset = ItemPosition(path.string(), proportion, offset); + m_cmdExec->enqueueCommand(pathWithOffset, spix::MouseButtons::Left); +} + void TestServer::mouseClick(ItemPath path, MouseButton mouseButton) { m_cmdExec->enqueueCommand(path, mouseButton); diff --git a/lib/src/Utils/AnyRpcFunction.h b/lib/src/Utils/AnyRpcFunction.h index e3870be..40a055b 100644 --- a/lib/src/Utils/AnyRpcFunction.h +++ b/lib/src/Utils/AnyRpcFunction.h @@ -69,6 +69,15 @@ int unpackAnyRpcParam(anyrpc::Value& value) return value.GetInt(); } +template <> +double unpackAnyRpcParam(anyrpc::Value& value) +{ + if (!value.IsNumber()) { + throw anyrpc::AnyRpcException(anyrpc::AnyRpcErrorInvalidParams, "Invalid parameters. Expected double."); + } + return value.GetDouble(); +} + template <> unsigned unpackAnyRpcParam(anyrpc::Value& value) { From a81d3e6a88e6ba3ee7911f07fb9359b8a12534fa Mon Sep 17 00:00:00 2001 From: Sebastian Feustel <48125666+53845714nF@users.noreply.github.com> Date: Tue, 14 May 2024 17:46:00 +0200 Subject: [PATCH 4/6] Add Documentation for command (#106) Co-authored-by: Sebastian Feustel --- README.md | 21 +++++++++++++++++++++ lib/src/AnyRpcServer.cpp | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5221325..207dac6 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,27 @@ s.invokeMethod("root/item", "test", [34]) s.invokeMethod("root/item", "test", [{}]) ``` +### Using generic/custom command +You can register your own commands in your C++ Application. +It could be useful for Example to reset your hole Application. + +Register the Commands in your C++ Code: +```C++ + ... + spix::AnyRpcServer server; + server.setGenericCommandHandler([](std::string command, std::string payload) { + // do whatever needs to be done + }); + ... +``` +Now you have all capabilities that the Application has. +The Payload handling must be done by your own. + +You can call this in Python like this: +```python +s.command('reset', 'now') +``` + ## Two modes of operation In general, Spix can be used in two ways, which are different in how events are generated and sent to your application: diff --git a/lib/src/AnyRpcServer.cpp b/lib/src/AnyRpcServer.cpp index fe3f60c..44be6dc 100644 --- a/lib/src/AnyRpcServer.cpp +++ b/lib/src/AnyRpcServer.cpp @@ -124,7 +124,7 @@ AnyRpcServer::AnyRpcServer(int anyrpcPort) utils::AddFunctionToAnyRpc(methodManager, "quit", "Close the app | quit()", [this] { quit(); }); utils::AddFunctionToAnyRpc(methodManager, "command", - "Executes a generic command | command(string command, string payload)", + "Executes a generic/custom command | command(string command, string payload)", [this](std::string command, std::string payload) { genericCommand(command, payload); }); m_pimpl->server->BindAndListen(anyrpcPort); From e500bd93e1c2cea34405abd82ddd81cd09147150 Mon Sep 17 00:00:00 2001 From: 53845714nF Date: Thu, 23 May 2024 15:15:39 +0200 Subject: [PATCH 5/6] rename function to takeScreenshotAsBase64 --- lib/CMakeLists.txt | 4 ++-- lib/include/Spix/TestServer.h | 2 +- lib/src/AnyRpcServer.cpp | 6 +++--- .../{ScreenshotRemote.cpp => ScreenshotBase64.cpp} | 8 ++++---- .../Commands/{ScreenshotRemote.h => ScreenshotBase64.h} | 4 ++-- lib/src/Scene/Mock/MockScene.cpp | 2 +- lib/src/Scene/Mock/MockScene.h | 2 +- lib/src/Scene/Qt/QtScene.cpp | 2 +- lib/src/Scene/Qt/QtScene.h | 2 +- lib/src/Scene/Scene.h | 2 +- lib/src/TestServer.cpp | 6 +++--- 11 files changed, 20 insertions(+), 20 deletions(-) rename lib/src/Commands/{ScreenshotRemote.cpp => ScreenshotBase64.cpp} (60%) rename lib/src/Commands/{ScreenshotRemote.h => ScreenshotBase64.h} (70%) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 8962b9d..29fbac4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -60,8 +60,8 @@ set(SOURCES src/Commands/Quit.h src/Commands/Screenshot.cpp src/Commands/Screenshot.h - src/Commands/ScreenshotRemote.cpp - src/Commands/ScreenshotRemote.h + src/Commands/ScreenshotBase64.cpp + src/Commands/ScreenshotBase64.h src/Commands/SetProperty.cpp src/Commands/SetProperty.h src/Commands/Wait.cpp diff --git a/lib/include/Spix/TestServer.h b/lib/include/Spix/TestServer.h index 925a59f..92ccce2 100644 --- a/lib/include/Spix/TestServer.h +++ b/lib/include/Spix/TestServer.h @@ -66,7 +66,7 @@ class SPIX_EXPORT TestServer { std::vector getErrors(); void takeScreenshot(ItemPath targetItem, std::string filePath); - std::string takeScreenshotRemote(ItemPath targetItem); + std::string takeScreenshotAsBase64(ItemPath targetItem); void quit(); protected: diff --git a/lib/src/AnyRpcServer.cpp b/lib/src/AnyRpcServer.cpp index 44be6dc..fb6c3c5 100644 --- a/lib/src/AnyRpcServer.cpp +++ b/lib/src/AnyRpcServer.cpp @@ -117,9 +117,9 @@ AnyRpcServer::AnyRpcServer(int anyrpcPort) return takeScreenshot(std::move(targetItem), std::move(filePath)); }); - utils::AddFunctionToAnyRpc(methodManager, "takeScreenshotRemote", - "Take a screenshot of the object and send as base64 string | takeScreenshotRemote(string pathToTargetedItem)", - [this](std::string targetItem) { return takeScreenshotRemote(std::move(targetItem)); }); + utils::AddFunctionToAnyRpc(methodManager, "takeScreenshotAsBase64", + "Take a screenshot of the object and send as base64 string | takeScreenshotAsBase64(string pathToTargetedItem)", + [this](std::string targetItem) { return takeScreenshotAsBase64(std::move(targetItem)); }); utils::AddFunctionToAnyRpc(methodManager, "quit", "Close the app | quit()", [this] { quit(); }); diff --git a/lib/src/Commands/ScreenshotRemote.cpp b/lib/src/Commands/ScreenshotBase64.cpp similarity index 60% rename from lib/src/Commands/ScreenshotRemote.cpp rename to lib/src/Commands/ScreenshotBase64.cpp index a802215..9ed93e7 100644 --- a/lib/src/Commands/ScreenshotRemote.cpp +++ b/lib/src/Commands/ScreenshotBase64.cpp @@ -4,21 +4,21 @@ * See LICENSE.txt file in the project root for full license information. ****/ -#include "ScreenshotRemote.h" +#include "ScreenshotBase64.h" #include namespace spix { namespace cmd { -ScreenshotRemote::ScreenshotRemote(ItemPath targetItemPath, std::promise promise) +ScreenshotAsBase64::ScreenshotAsBase64(ItemPath targetItemPath, std::promise promise) : m_itemPath {std::move(targetItemPath)} , m_promise(std::move(promise)) { } -void ScreenshotRemote::execute(CommandEnvironment& env) +void ScreenshotAsBase64::execute(CommandEnvironment& env) { - auto value = env.scene().takeScreenshotRemote(m_itemPath); + auto value = env.scene().takeScreenshotAsBase64(m_itemPath); m_promise.set_value(value); } diff --git a/lib/src/Commands/ScreenshotRemote.h b/lib/src/Commands/ScreenshotBase64.h similarity index 70% rename from lib/src/Commands/ScreenshotRemote.h rename to lib/src/Commands/ScreenshotBase64.h index f34d20a..477bf17 100644 --- a/lib/src/Commands/ScreenshotRemote.h +++ b/lib/src/Commands/ScreenshotBase64.h @@ -8,9 +8,9 @@ namespace spix { namespace cmd { -class ScreenshotRemote : public Command { +class ScreenshotAsBase64 : public Command { public: - ScreenshotRemote(ItemPath targetItemPath, std::promise promise); + ScreenshotAsBase64(ItemPath targetItemPath, std::promise promise); void execute(CommandEnvironment& env) override; diff --git a/lib/src/Scene/Mock/MockScene.cpp b/lib/src/Scene/Mock/MockScene.cpp index ac081e1..586eac9 100644 --- a/lib/src/Scene/Mock/MockScene.cpp +++ b/lib/src/Scene/Mock/MockScene.cpp @@ -28,7 +28,7 @@ void MockScene::takeScreenshot(const ItemPath&, const std::string&) { } -std::string MockScene::takeScreenshotRemote(const ItemPath&) +std::string MockScene::takeScreenshotAsBase64(const ItemPath&) { return "Base64 String"; } diff --git a/lib/src/Scene/Mock/MockScene.h b/lib/src/Scene/Mock/MockScene.h index 5f16da0..8a9b757 100644 --- a/lib/src/Scene/Mock/MockScene.h +++ b/lib/src/Scene/Mock/MockScene.h @@ -25,7 +25,7 @@ class SPIX_EXPORT MockScene : public Scene { // Tasks void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) override; - std::string takeScreenshotRemote(const ItemPath& targetItem); + std::string takeScreenshotAsBase64(const ItemPath& targetItem); // Mock stuff void addItemAtPath(MockItem item, const ItemPath& path); MockEvents& mockEvents(); diff --git a/lib/src/Scene/Qt/QtScene.cpp b/lib/src/Scene/Qt/QtScene.cpp index d442123..d320e7d 100644 --- a/lib/src/Scene/Qt/QtScene.cpp +++ b/lib/src/Scene/Qt/QtScene.cpp @@ -141,7 +141,7 @@ void QtScene::takeScreenshot(const ItemPath& targetItem, const std::string& file image.save(QString::fromStdString(filePath)); } -std::string QtScene::takeScreenshotRemote(const ItemPath& targetItem) +std::string QtScene::takeScreenshotAsBase64(const ItemPath& targetItem) { auto item = getQQuickItemAtPath(targetItem); if (!item) { diff --git a/lib/src/Scene/Qt/QtScene.h b/lib/src/Scene/Qt/QtScene.h index 8ff9cf2..1897845 100644 --- a/lib/src/Scene/Qt/QtScene.h +++ b/lib/src/Scene/Qt/QtScene.h @@ -29,7 +29,7 @@ class QtScene : public Scene { // Tasks void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) override; - std::string takeScreenshotRemote(const ItemPath& targetItem); + std::string takeScreenshotAsBase64(const ItemPath& targetItem); private: QtEvents m_events; diff --git a/lib/src/Scene/Scene.h b/lib/src/Scene/Scene.h index 9ea4739..91e53cc 100644 --- a/lib/src/Scene/Scene.h +++ b/lib/src/Scene/Scene.h @@ -37,7 +37,7 @@ class Scene { // Tasks virtual void takeScreenshot(const ItemPath& targetItem, const std::string& filePath) = 0; - virtual std::string takeScreenshotRemote(const ItemPath& targetItem) = 0; + virtual std::string takeScreenshotAsBase64(const ItemPath& targetItem) = 0; }; } // namespace spix diff --git a/lib/src/TestServer.cpp b/lib/src/TestServer.cpp index 8b44481..157cc71 100644 --- a/lib/src/TestServer.cpp +++ b/lib/src/TestServer.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include @@ -178,11 +178,11 @@ void TestServer::takeScreenshot(ItemPath targetItem, std::string filePath) m_cmdExec->enqueueCommand(targetItem, std::move(filePath)); } -std::string TestServer::takeScreenshotRemote(ItemPath targetItem) +std::string TestServer::takeScreenshotAsBase64(ItemPath targetItem) { std::promise promise; auto result = promise.get_future(); - auto cmd = std::make_unique(targetItem, std::move(promise)); + auto cmd = std::make_unique(targetItem, std::move(promise)); m_cmdExec->enqueueCommand(std::move(cmd)); return result.get(); From 588a5ee06bf5f0459c01c7870ba78c4aa38d2a08 Mon Sep 17 00:00:00 2001 From: Falko Axmann <51922941+faaxm@users.noreply.github.com> Date: Wed, 29 May 2024 08:38:09 +0200 Subject: [PATCH 6/6] add a line at end of file --- lib/src/Commands/ScreenshotBase64.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/Commands/ScreenshotBase64.cpp b/lib/src/Commands/ScreenshotBase64.cpp index 9ed93e7..fa126da 100644 --- a/lib/src/Commands/ScreenshotBase64.cpp +++ b/lib/src/Commands/ScreenshotBase64.cpp @@ -23,4 +23,4 @@ void ScreenshotAsBase64::execute(CommandEnvironment& env) } } // namespace cmd -} // namespace spix \ No newline at end of file +} // namespace spix