Skip to content

Integrate Spix UI test automation library #925

Description

@magnesj

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

  • 1. Add anyrpc to vcpkg.json. (Initially added spix here too; later removed in favour of FetchContent.)
  • 2. Add RESINSIGHT_ENABLE_SPIX CMake option in CMakeLists.txt, fetch Spix via FetchContent at v0.14 with SPIX_BUILD_QTWIDGETS=ON / SPIX_BUILD_QTQUICK=OFF / SPIX_BUILD_EXAMPLES=OFF, and link Spix::QtWidgets PUBLIC into ApplicationLibCode (since RiaGuiApplication.cpp lives there; ResInsight.exe picks it up transitively).
  • 3. Register --spix-port CLI option in RiaArgumentParser.cpp.
  • 4. Start/stop Spix server in RiaGuiApplication (header forward-decls + members, cpp handleArguments + destructor), gated on ENABLE_SPIX.
  • 5. Add example Python smoke test at GrpcInterface/Python/rips/tests/test_spix_smoke.py. Protocol revision: Spix's AnyRPC server is XML-RPC, not JSON-RPC; test uses xmlrpc.client.ServerProxy. Final assertion: calls getErrors() (path-less) — verified end-to-end against a running ResInsight.exe --spix-port 9000. An earlier draft asserted existsAndVisible("mainWindow") but that returned False because no top-level widget calls setObjectName(...); resolving that is a precondition for path-based UI tests, not for the smoke check.

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:

  1. 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).
  2. POSTs a getProperty mainWindow visible JSON-RPC request to http://localhost:<port> using urllib.request (no new deps).
  3. 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

  1. Default build (Spix off): cmake --build --preset x64-relwithdebinfo — must succeed, ResInsight binary unchanged in size/behavior. No spix in the linker line.

  2. 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.

  3. 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.

  4. 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.

  5. 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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions