{"id":20655006,"url":"https://github.com/faaxm/spix","last_synced_at":"2025-04-06T16:15:40.758Z","repository":{"id":39262341,"uuid":"193724941","full_name":"faaxm/spix","owner":"faaxm","description":"UI test automation library for QtQuick/QML Apps","archived":false,"fork":false,"pushed_at":"2025-03-25T08:29:53.000Z","size":148,"stargazers_count":196,"open_issues_count":15,"forks_count":53,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-03-30T15:08:24.277Z","etag":null,"topics":["automated-testing","automation","qml","qml-applications","qt","qt-qml","qt-quick","qt5","qtquick"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faaxm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"faaxm","custom":"https://www.paypal.me/faaxm"}},"created_at":"2019-06-25T14:34:02.000Z","updated_at":"2025-03-25T08:22:47.000Z","dependencies_parsed_at":"2023-02-10T10:05:22.044Z","dependency_job_id":"da706595-a6c9-4a8f-b728-29371f148491","html_url":"https://github.com/faaxm/spix","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faaxm%2Fspix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faaxm%2Fspix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faaxm%2Fspix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faaxm%2Fspix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faaxm","download_url":"https://codeload.github.com/faaxm/spix/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247509238,"owners_count":20950232,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automated-testing","automation","qml","qml-applications","qt","qt-qml","qt-quick","qt5","qtquick"],"created_at":"2024-11-16T18:08:15.915Z","updated_at":"2025-04-06T16:15:40.734Z","avatar_url":"https://github.com/faaxm.png","language":"C++","funding_links":["https://github.com/sponsors/faaxm","https://www.paypal.me/faaxm"],"categories":[],"sub_categories":[],"readme":"[![Build and Test](https://github.com/faaxm/spix/actions/workflows/build.yml/badge.svg)](https://github.com/faaxm/spix/actions/workflows/build.yml)\n\n\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"Spix Logo\" src=\"https://www.f-ax.de/ext_files/spix_logo.png\" height=\"110\"\u003e\n\u003c/p\u003e\n\n# Spix\nSpix is a minimally invasive UI testing library that enables your\nQt/QML app's UI to be controlled either via c++ code, or through an http RPC\ninterface.\n\nUI elements are referenced through names and paths which are robust against\ndesign changes. To click on a button you write\n```cpp\nmouseClick(\"mainWindow/ok_button\");\n```\n\nTo provide an RPC test interface to your app,\nonly add these three lines to your `main(...)` function:\n```cpp\nspix::AnyRpcServer server;\nauto bot = new spix::QtQmlBot();\nbot-\u003erunTestServer(server);\n```\n\nAnd a test script in python could look like this:\n```python\nimport xmlrpc.client\n\ns = xmlrpc.client.ServerProxy('http://localhost:9000')\ns.mouseClick(\"mainWindow/Button_1\")\ns.wait(200)\ns.mouseClick(\"mainWindow/Button_2\")\nresultText = s.getStringProperty(\"mainWindow/results\", \"text\")\ns.quit()\n```\n\nYou can also use [PyAutoGUI](https://pyautogui.readthedocs.io) in combination with\nSpix. Have a look at the [example script](examples/RemoteCtrl/script/autogui.py).\n\n## What are the applications of Spix?\nThe main use for Spix is to automatically test the UI of your Qt/QML application\nand make sure that it behaves as you expect. However, you can also use Spix as\nan easy way to remote control existing Qt/QML applications or to automatically\ngenerate and update screenshots for your documentation.\n\n# Requirements\n* Qt (both 5 and 6 supported)\n* [AnyRPC](https://github.com/sgieseking/anyrpc)\n\n# Current Features\n* Send mouse events (click, move, drag/drop)\n* Drop mime data from external apps\n* Enter text\n* Check existence and visibility of items\n* Get property values of items (text, position, color, ...)\n* Invoke a method on an object\n* Take and save a screenshot\n* Quit the app\n* Remote control, also of embedded devices / iOS\n\n# Setting Up Spix\n\n## Installing AnyRPC\n\nSpix requires [AnyRPC](https://github.com/sgieseking/anyrpc) be installed on the local machine before building. For *nix machines, AnyRPC can be built/installed using CMake:\n```sh\n# in a temporary directory\ngit clone https://github.com/sgieseking/anyrpc.git\ncd anyrpc\nmkdir build\ncd build\ncmake -DBUILD_EXAMPLES=OFF -DBUILD_WITH_LOG4CPLUS=OFF -DBUILD_PROTOCOL_MESSAGEPACK=OFF ..\ncmake --build .\nsudo cmake --install .\n```\n\nFor non-*nix machines, checkout the [CI install script](ci/install-deps.sh).\n\n## Installing Spix\n\nSpix uses cmake and can be build with the standard cmake commands once cloned:\n```sh\ngit clone https://github.com/faaxm/spix\ncd spix\nmkdir build \u0026\u0026 cd build\ncmake -DSPIX_QT_MAJOR=6 ..\ncmake --build .\nsudo cmake --install .\n```\n\u003e Change SPIX_QT_MAJOR to 5 to build against Qt5 instead of Qt6.\n\nIf you installed the dependencies (like AnyRPC) in a non-standard directory you can point cmake to it by setting `CMAKE_PREFIX_PATH`, so\ninstead of `cmake ..` you run:\n```sh\ncmake -DCMAKE_PREFIX_PATH=/path/to/libs ..\n```\n\n## Including Spix in your Qt project\n\nIf using qmake, add the following to your Qt `.pro` file:\n```\nQT += quick\nINCLUDEPATH += /usr/local/include\nLIBS += -lSpix -lanyrpc\n```\nIf using CMake, add the following to your `CMakeLists.txt`:\n```\nfind_package(Spix REQUIRED)\n```\n\nUpdate your `main(...)` to start the Spix RPC server:\n```C++\n#include \u003cSpix/AnyRpcServer.h\u003e\n#include \u003cSpix/QtQmlBot.h\u003e\n\nint main(...) {\n    ...\n    spix::AnyRpcServer server;\n    auto bot = new spix::QtQmlBot();\n    bot-\u003erunTestServer(server);\n    ...\n}\n```\nFinally, if you're using a `QQuickView` as your root window, you'll need to give it an object name in your `main` (otherwise the root window object name will be defined in your QML):\n```C++\nint main(...) {\n    QQuickView view;\n    view.setObjectName(\"root\")\n    ...\n}\n```\n\n# Using Spix\n\nThe easiest method of interacting with Spix is using the [XMLRPC client built into python](https://docs.python.org/3/library/xmlrpc.client.html#module-xmlrpc.client):\n```python\nimport xmlrpc.client\n\ns = xmlrpc.client.ServerProxy('http://localhost:9000') # default port is 9000\ns.method(\u003cpath\u003e, \u003coptions\u003e)\n# for example:\ns.mouseClick(\"root/Button_2\")\nresultText = s.getStringProperty(\"root/results\", \"text\")\n```\n\nYou can also use the XMLRPC client to list the available methods. The complete list of methods are also available in the [source](lib/src/AnyRpcServer.cpp).\n```python\nprint(s.system.listMethods())\n# ['command', 'enterKey', 'existsAndVisible', 'getBoundingBox', 'getErrors', 'getStringProperty', 'inputText', 'invokeMethod', 'mouseBeginDrag', 'mouseClick', 'mouseDropUrls', 'mouseEndDrag', 'quit', 'setStringProperty', 'system.listMethods', 'system.methodHelp', 'takeScreenshot', 'wait']\nprint(s.system.methodHelp('mouseClick'))\n# Click on the object at the given path\n```\n\nSpix uses a slash-separated path format to select Qt objects. Selectors match against `objectName` or `id` if no object name is defined.\n```\n\u003croot\u003e/\u003cchild0\u003e(/\u003cchildN\u003e...)\n```\nSpix matches children recursivley, allowing as much flexibility as needed:\n```\n# matches any `button` that is a descendant of `root` (even subchildren)\n'root/button'\n# matches any `button` that is a descendant of `numberpad` which is in turn a descendant of `root`.\n'root/numberpad/button'\n# and so on\n```\n\nMore specifically, Spix's matching processes works as follows:\n* `\u003croot\u003e` matches a top-level [`QQuickWindow`](https://doc-snapshots.qt.io/qt6-dev/qquickwindow.html) whose `objectName` (or `id` if `objectName` is empty) matches the specified string. Top-level windows are enumerated by [`QGuiApplication::topLevelWindows`](https://doc.qt.io/qt-6/qguiapplication.html#topLevelWindows).\n* `\u003cchild\u003e` matches the first child object whose `objectName` (or `id` if `objectName` is empty) matches the specified string using a recursive search of all children and subchildren of the root. This process repeats for every subsequent child path entry.\n\n### Invoking QML methods\n\nSpix can directly invoke both internal and custom methods in QML objects: this can be a handy way to automate interactions that Spix doesn't support normally. For example, we can control the cursor in a `TextArea` by calling [`TextArea.select`](https://doc-snapshots.qt.io/qt6-6.2/qml-qtquick-textedit.html#select-method):\n```qml\nTextArea {\n    id: textArea\n}\n```\n```python\n# select characters 100-200\ns.invokeMethod(\"root/textArea\", \"select\", [100, 200])\n```\n\nIn addition, you can use custom functions in the QML to implement more complicated interactions, and have Spix interact with the function:\n```qml\nTextArea {\n    id: textArea\n    function customFunction(arg1, arg2) {\n        // insert QML interactions here\n        return {'key1': true, 'key2': false}\n    }\n}\n```\n```python\n# invoke the custom function\nresult = s.invokeMethod(\"root/textArea\", \"customFunction\", ['a string', 34])\n# prints {'key1': True, 'key2': False}\nprint(result)\n```\n\nSpix supports the following types as arguments/return values:\n| Python Type       | XMLRPC Type          | QML Type(s)     | JavaScript Type(s)| Notes                                            |\n|-------------------|----------------------|-----------------|-------------------|--------------------------------------------------|\n| int               | \\\u003cint\\\u003e              | int             | number            | Values over/under int max are upcasted to double |\n| bool              | \\\u003cboolean\\\u003e          | bool            | boolean           |                                                  |\n| str               | \\\u003cstring\\\u003e           | string          | string            |                                                  |\n| float             | \\\u003cdouble\\\u003e           | double, real    | number            | Defaults to double                               |\n| datetime.datetime | \\\u003cdateTime.iso8601\\\u003e | date            | Date              | No timezone support (always uses local timezone) |\n| dict              | \\\u003cstruct\\\u003e           | var             | object            | String keys only                                 |\n| list              | \\\u003carray\\\u003e            | var             | Array             |                                                  |\n| None              | no type              | null, undefined | object, undefined | Defaults to null                                 |                              |\n\nIn general Spix will attempt to coerce the arguments and return value to the correct types to match the method being invoked. Valid conversion are listed under the [`QVariant` docs](https://doc.qt.io/qt-5/qvariant.html#canConvert). If Spix cannot find a valid conversion it will generate an error.\n```qml\nItem {\n    id: item\n    function test(arg1: bool) {\n        ...\n    }\n}\n```\n```python\n# ok\ns.invokeMethod(\"root/item\", \"test\", [False])\n\n# argument will implicitly be converted to a boolean (True) to match the declaration type\ns.invokeMethod(\"root/item\", \"test\", [34])\n\n# no conversion from object to boolean, so an error is thrown\ns.invokeMethod(\"root/item\", \"test\", [{}])\n```\n\n###  Using generic/custom command\nYou can register your own commands in your C++ Application.\nIt could be useful for Example to reset your hole Application.\n\nRegister the Commands in your C++ Code:\n```C++\n    ...\n    spix::AnyRpcServer server;\n    server.setGenericCommandHandler([](std::string command, std::string payload) {\n        // do whatever needs to be done\n    });\n    ...\n```\nNow you have all capabilities that the Application has.\nThe Payload handling must be done by your own.\n\nYou can call this in Python like this:\n```python\ns.command('reset', 'now')\n```\n\n## Two modes of operation\nIn general, Spix can be used in two ways, which are different in how events are generated and sent\nto your application:\n\n### Generate Qt events directly\nYou can use Spix to directly create Qt events, either from C++ as a unit test, or from\nan external script via the network through RPC. Since the Qt events are generated directly inside the\napp, and do not come from the system, the mouse cursor will not actually move and interaction\nwith other applications is limited. On the plus side, this mechanism is independent from\nthe system your app is running on and can easily be used to control software on an embedded\ndevice via the network (RPC).\n\n### Generate system events externally\nIn this case, Spix is not generating the events itself. Instead, you use a script to query\nSpix for the screen coordinates of qt objects and then generate events on the system level\nthrough other tools. One option is to use python together with PyAutoGUI for this, as is\ndone in the [RemoteCtrl](examples/RemoteCtrl) example.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaaxm%2Fspix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaaxm%2Fspix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaaxm%2Fspix/lists"}