Context
ResInsight currently has no in-process hook for driving the QtWidgets UI from external test scripts. gRPC + the rips Python package give scripted control over the data model, but cannot click menus, push buttons, or read widget state.
Spix (MIT) is a minimally invasive Qt UI test automation library. It runs an HTTP/AnyRPC server inside the application process and lets external scripts (Python, curl, etc.) issue mouseClick("mainWindow/ok_button")-style commands against the live widget tree. Spix supports QtWidgets on Qt 6 (the SPIX_BUILD_QTWIDGETS option, introduced in 0.6).
Goal: opt-in build flag RESINSIGHT_ENABLE_SPIX that links Spix into ResInsight, starts an AnyRpcServer when --spix-port <n> is passed on the command line, and ships a tiny example Python test demonstrating the workflow.
Decisions:
- Source: FetchContent (Spix v0.14) + vcpkg (
anyrpc). Initially planned to take Spix via vcpkg too, but the vcpkg spix port pulls in qtbase and qtdeclarative as transitive deps which would force a duplicate Qt6 build alongside the project's existing system Qt6. Pivoted to fetching Spix as source so it builds against the project Qt6 and only anyrpc flows through vcpkg.
- Scope: RPC server + one example test.
- Gating:
RESINSIGHT_ENABLE_SPIX CMake option, OFF by default.
- Pinned Spix tag:
v0.14 (commit a9c9bb178f). Pinning chosen deliberately — see prior comment about Qt 6.5 inputMethodHints regression that required bisecting Spix versions.
Steps
Approach
1. vcpkg manifest
File: vcpkg.json
Add "spix" to the dependencies array. vcpkg pulls in anyrpc automatically (both ports already live in microsoft/vcpkg).
The vcpkg spix port builds Spix::QtQuick by default. We need Spix::QtWidgets, which the port exposes via the qtwidgets feature; switch the dependencies entry to the object form:
{ "name": "spix", "features": ["qtwidgets"] }
(Verify the feature name when implementing — vcpkg's ports/spix/vcpkg.json is the source of truth. If the port lacks a qtwidgets feature, fall back to adding SPIX_BUILD_QTWIDGETS=ON via a custom triplet in ThirdParty/vcpkg-custom-triplets/.)
2. CMake option and link wiring
File: CMakeLists.txt
After the existing RESINSIGHT_ENABLE_GRPC block (around line 245), add:
option(RESINSIGHT_ENABLE_SPIX
"Enable Spix UI test automation (HTTP/RPC server)" OFF)
if(RESINSIGHT_ENABLE_SPIX)
find_package(Spix CONFIG REQUIRED)
add_definitions(-DENABLE_SPIX)
endif()
File: ApplicationExeCode/CMakeLists.txt
Inside the existing if(RESINSIGHT_ENABLE_GRPC) neighborhood (~line 183 or where LINK_LIBRARIES is assembled, line 206), add:
if(RESINSIGHT_ENABLE_SPIX)
list(APPEND LINK_LIBRARIES Spix::QtWidgets)
endif()
3. CLI flag
File: ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp:75
Register a new option next to portnumberfile:
progOpt->registerOption(\"spix-port\",
\"<portnumber>\",
\"Start Spix UI test automation server on the given HTTP port.\",
cvf::ProgramOptions::SINGLE_VALUE);
No #ifdef here — unknown options are harmless; the server only starts when the option is handled in the GUI app, which is gated on ENABLE_SPIX.
4. Server lifetime in the GUI app
Files:
ApplicationLibCode/Application/RiaGuiApplication.h
ApplicationLibCode/Application/RiaGuiApplication.cpp
In the header, behind #ifdef ENABLE_SPIX:
- Forward-declare
namespace spix { class AnyRpcServer; class QtWidgetsTestServer; }
- Add two
std::unique_ptr members.
In RiaGuiApplication::handleArguments (RiaGuiApplication.cpp near line 531) — not in initialize(), because the port is parsed from the CLI — add:
#ifdef ENABLE_SPIX
if (cvf::Option o = progOpt->option(\"spix-port\")) {
int port = (o.valueCount() == 1) ? o.value(0).toInt() : 0;
if (port > 0) startSpixServer(port);
}
#endif
Implement startSpixServer(int) as a private method that constructs spix::AnyRpcServer and a Spix QtWidgetsTestServer, stores them in the members, and calls their start method. Stop them in ~RiaGuiApplication (~line 210) before the windows are torn down.
The exact Spix QtWidgets API surface (class names: QtWidgetsBot vs QtWidgetsTestServer) shifted between releases — read spix/QtWidgetsBot.h from the vcpkg-installed include tree at implementation time and use whatever the headers expose.
5. Example Python test
New file: GrpcInterface/Python/rips/tests/test_spix_smoke.py
Minimal smoke test that:
- Skips with
pytest.skip(...) if RESINSIGHT_SPIX_PORT env var is unset (keeps it out of the default CI suite, since CI doesn't launch ResInsight with --spix-port).
- POSTs a
getProperty mainWindow visible JSON-RPC request to http://localhost:<port> using urllib.request (no new deps).
- Asserts the response is valid JSON with
result == true.
Use the stdlib-only style of existing tests (see test_launch.py for the pattern).
Files modified
| File |
Change |
vcpkg.json |
Add spix (with qtwidgets feature) |
CMakeLists.txt |
Add RESINSIGHT_ENABLE_SPIX option, find_package(Spix) |
ApplicationExeCode/CMakeLists.txt |
Conditionally link Spix::QtWidgets |
ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp |
Register --spix-port |
ApplicationLibCode/Application/RiaGuiApplication.h |
Forward-decl + members |
ApplicationLibCode/Application/RiaGuiApplication.cpp |
Start/stop server |
GrpcInterface/Python/rips/tests/test_spix_smoke.py |
New example test |
Verification
-
Default build (Spix off): cmake --build --preset x64-relwithdebinfo — must succeed, ResInsight binary unchanged in size/behavior. No spix in the linker line.
-
Spix-enabled build:
```
cmake . --preset x64-relwithdebinfo -DRESINSIGHT_ENABLE_SPIX=ON
cmake --build --preset x64-relwithdebinfo --target ResInsight
```
Confirm vcpkg installed spix and anyrpc, ResInsight links them.
-
Runtime smoke:
```
build\RelWithDebInfo\ResInsight.exe --spix-port 9000
```
In another shell:
```
curl -s -X POST http://localhost:9000 -H "Content-Type: application/json"
-d '{"jsonrpc":"2.0","id":1,"method":"command","params":["getProperty","mainWindow","visible"]}'
```
Expect a JSON response with result: true.
-
Python test:
```
$env:RESINSIGHT_SPIX_PORT=9000
python -m pytest GrpcInterface\Python\rips\tests\test_spix_smoke.py -v
```
With ResInsight running with --spix-port 9000, test passes. Without the env var, test is skipped.
-
No-flag run: launch ResInsight without --spix-port. Confirm no listening socket appears — server only starts when the flag is given.
Risks / open items
- Spix's exact Qt 6.4+ compatibility on Windows + MSVC 2022 should be confirmed during step 2. If the vcpkg port fails to build with the project's compiler flags (e.g.,
/W4 or WARNINGS_AS_ERRORS), wrap the Spix target in set_target_properties(... INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ...) the way Arrow::arrow_shared is treated in CMakeLists.txt:725.
- The
qtwidgets feature flag for the vcpkg spix port has to be verified against vcpkg/ports/spix/vcpkg.json — if it doesn't exist, custom triplet fallback is the next step.
- Spix headers expose
Q_OBJECT types; if AUTOMOC has trouble with them in a unity build, exclude the relevant translation unit from unity (mirroring the UNITY_EXCLUDE_FILES pattern in ApplicationExeCode/CMakeLists.txt:242).
Context
ResInsight currently has no in-process hook for driving the QtWidgets UI from external test scripts. gRPC + the
ripsPython package give scripted control over the data model, but cannot click menus, push buttons, or read widget state.Spix (MIT) is a minimally invasive Qt UI test automation library. It runs an HTTP/AnyRPC server inside the application process and lets external scripts (Python, curl, etc.) issue
mouseClick("mainWindow/ok_button")-style commands against the live widget tree. Spix supports QtWidgets on Qt 6 (theSPIX_BUILD_QTWIDGETSoption, introduced in 0.6).Goal: opt-in build flag
RESINSIGHT_ENABLE_SPIXthat links Spix into ResInsight, starts anAnyRpcServerwhen--spix-port <n>is passed on the command line, and ships a tiny example Python test demonstrating the workflow.Decisions:
anyrpc). Initially planned to take Spix via vcpkg too, but the vcpkgspixport pulls inqtbaseandqtdeclarativeas transitive deps which would force a duplicate Qt6 build alongside the project's existing system Qt6. Pivoted to fetching Spix as source so it builds against the project Qt6 and onlyanyrpcflows through vcpkg.RESINSIGHT_ENABLE_SPIXCMake option, OFF by default.v0.14(commita9c9bb178f). Pinning chosen deliberately — see prior comment about Qt 6.5 inputMethodHints regression that required bisecting Spix versions.Steps
anyrpctovcpkg.json. (Initially addedspixhere too; later removed in favour of FetchContent.)RESINSIGHT_ENABLE_SPIXCMake option inCMakeLists.txt, fetch Spix viaFetchContentat v0.14 withSPIX_BUILD_QTWIDGETS=ON/SPIX_BUILD_QTQUICK=OFF/SPIX_BUILD_EXAMPLES=OFF, and linkSpix::QtWidgetsPUBLIC intoApplicationLibCode(sinceRiaGuiApplication.cpplives there; ResInsight.exe picks it up transitively).--spix-portCLI option inRiaArgumentParser.cpp.RiaGuiApplication(header forward-decls + members, cpphandleArguments+ destructor), gated onENABLE_SPIX.GrpcInterface/Python/rips/tests/test_spix_smoke.py. Protocol revision: Spix's AnyRPC server is XML-RPC, not JSON-RPC; test usesxmlrpc.client.ServerProxy. Final assertion: callsgetErrors()(path-less) — verified end-to-end against a runningResInsight.exe --spix-port 9000. An earlier draft assertedexistsAndVisible("mainWindow")but that returned False because no top-level widget callssetObjectName(...); resolving that is a precondition for path-based UI tests, not for the smoke check.Approach
1. vcpkg manifest
File:
vcpkg.jsonAdd
"spix"to thedependenciesarray. vcpkg pulls inanyrpcautomatically (both ports already live in microsoft/vcpkg).The vcpkg
spixport buildsSpix::QtQuickby default. We needSpix::QtWidgets, which the port exposes via theqtwidgetsfeature; switch thedependenciesentry to the object form:{ "name": "spix", "features": ["qtwidgets"] }(Verify the feature name when implementing — vcpkg's
ports/spix/vcpkg.jsonis the source of truth. If the port lacks aqtwidgetsfeature, fall back to addingSPIX_BUILD_QTWIDGETS=ONvia a custom triplet inThirdParty/vcpkg-custom-triplets/.)2. CMake option and link wiring
File:
CMakeLists.txtAfter the existing
RESINSIGHT_ENABLE_GRPCblock (around line 245), add:File:
ApplicationExeCode/CMakeLists.txtInside the existing
if(RESINSIGHT_ENABLE_GRPC)neighborhood (~line 183 or whereLINK_LIBRARIESis assembled, line 206), add:3. CLI flag
File:
ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp:75Register a new option next to
portnumberfile:No
#ifdefhere — unknown options are harmless; the server only starts when the option is handled in the GUI app, which is gated onENABLE_SPIX.4. Server lifetime in the GUI app
Files:
ApplicationLibCode/Application/RiaGuiApplication.hApplicationLibCode/Application/RiaGuiApplication.cppIn the header, behind
#ifdef ENABLE_SPIX:namespace spix { class AnyRpcServer; class QtWidgetsTestServer; }std::unique_ptrmembers.In
RiaGuiApplication::handleArguments(RiaGuiApplication.cppnear line 531) — not ininitialize(), because the port is parsed from the CLI — add:Implement
startSpixServer(int)as a private method that constructsspix::AnyRpcServerand a SpixQtWidgetsTestServer, stores them in the members, and calls their start method. Stop them in~RiaGuiApplication(~line 210) before the windows are torn down.The exact Spix QtWidgets API surface (class names:
QtWidgetsBotvsQtWidgetsTestServer) shifted between releases — readspix/QtWidgetsBot.hfrom the vcpkg-installed include tree at implementation time and use whatever the headers expose.5. Example Python test
New file:
GrpcInterface/Python/rips/tests/test_spix_smoke.pyMinimal smoke test that:
pytest.skip(...)ifRESINSIGHT_SPIX_PORTenv var is unset (keeps it out of the default CI suite, since CI doesn't launch ResInsight with--spix-port).getProperty mainWindow visibleJSON-RPC request tohttp://localhost:<port>usingurllib.request(no new deps).result == true.Use the stdlib-only style of existing tests (see
test_launch.pyfor the pattern).Files modified
vcpkg.jsonspix(withqtwidgetsfeature)CMakeLists.txtRESINSIGHT_ENABLE_SPIXoption,find_package(Spix)ApplicationExeCode/CMakeLists.txtSpix::QtWidgetsApplicationLibCode/Application/Tools/RiaArgumentParser.cpp--spix-portApplicationLibCode/Application/RiaGuiApplication.hApplicationLibCode/Application/RiaGuiApplication.cppGrpcInterface/Python/rips/tests/test_spix_smoke.pyVerification
Default build (Spix off):
cmake --build --preset x64-relwithdebinfo— must succeed, ResInsight binary unchanged in size/behavior. Nospixin the linker line.Spix-enabled build:
```
cmake . --preset x64-relwithdebinfo -DRESINSIGHT_ENABLE_SPIX=ON
cmake --build --preset x64-relwithdebinfo --target ResInsight
```
Confirm vcpkg installed
spixandanyrpc, ResInsight links them.Runtime smoke:
```
build\RelWithDebInfo\ResInsight.exe --spix-port 9000
```
In another shell:
```
curl -s -X POST http://localhost:9000 -H "Content-Type: application/json"
-d '{"jsonrpc":"2.0","id":1,"method":"command","params":["getProperty","mainWindow","visible"]}'
```
Expect a JSON response with
result: true.Python test:
```
$env:RESINSIGHT_SPIX_PORT=9000
python -m pytest GrpcInterface\Python\rips\tests\test_spix_smoke.py -v
```
With ResInsight running with
--spix-port 9000, test passes. Without the env var, test is skipped.No-flag run: launch ResInsight without
--spix-port. Confirm no listening socket appears — server only starts when the flag is given.Risks / open items
/W4orWARNINGS_AS_ERRORS), wrap the Spix target inset_target_properties(... INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ...)the wayArrow::arrow_sharedis treated inCMakeLists.txt:725.qtwidgetsfeature flag for the vcpkgspixport has to be verified againstvcpkg/ports/spix/vcpkg.json— if it doesn't exist, custom triplet fallback is the next step.Q_OBJECTtypes; if AUTOMOC has trouble with them in a unity build, exclude the relevant translation unit from unity (mirroring theUNITY_EXCLUDE_FILESpattern inApplicationExeCode/CMakeLists.txt:242).