Skip to content

CI integration

veriCue tests run headless out of the box: Qt's offscreen platform plugin renders without a display server, and the pytest fixtures spawn your instrumented app automatically.

The essentials

Three environment variables cover most CI setups:

VariableValuePurpose
QT_QPA_PLATFORMoffscreenHeadless rendering, no X11/Wayland needed
VERICUE_TEST_APPpath to your app binaryTells the pytest fixtures what to spawn
MYAPP_VERICUE_TOKENa secretAuth token, if your app sets one

Screenshots (screenshot, screenshot_compare) work fine offscreen - widgets render into the offscreen buffer.

Pytest fixtures

Installing the Python client (pip install vericue) auto-registers a pytest plugin with three fixtures:

  • vericue_test_app (module-scoped) - spawns the binary at $VERICUE_TEST_APP with QT_QPA_PLATFORM=offscreen, passes --port 0 and reads the actual port from the app's VERICUE_PORT=<port> stdout line. Yields the port; terminates the app on module teardown.

  • vericue_client - a connected, handshaken VeriCueClient against that app. Disconnects on test teardown. This is the one you want:

    python
    async def test_login(vericue_client):
        await vericue_client.type_text("MainWindow/userField", "alice")
        await vericue_client.mouse_click("MainWindow/loginButton")
        props = await vericue_client.get_properties("MainWindow/statusLabel", ["text"])
        assert props["text"] == "Logged in"
  • vericue_client_factory - for tests needing several concurrent connections (e.g. multi-client scenarios).

For the port-discovery handoff to work in your own app, print the port after start():

cpp
printf("VERICUE_PORT=%d\n", server.serverPort());

Reports

The plugin writes an HTML report and JUnit XML at session end. Configure via CLI (--vericue-report-html, --vericue-report-xml, --vericue-report-name) or pyproject.toml:

toml
[tool.pytest.ini_options]
vericue_report_html = "reports/vericue.html"
vericue_report_xml  = "reports/vericue.xml"
vericue_report_name = "MyApp UI suite"

Most CI systems ingest the JUnit XML natively (GitHub checks, GitLab reports: junit:, Jenkins).

GitHub Actions

A complete workflow: build the app with veriCue enabled, run the UI suite headless, publish reports.

yaml
name: ui-tests
on: [push, pull_request]

jobs:
  ui-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Qt
        uses: jurplel/install-qt-action@v4
        with:
          version: '6.7.*'

      - name: Build app (veriCue embedded)
        run: |
          cmake -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_VERICUE=ON
          cmake --build build --parallel

      - name: Install test deps
        run: pip install vericue pytest pytest-asyncio

      - name: Run UI tests
        env:
          QT_QPA_PLATFORM: offscreen
          VERICUE_TEST_APP: build/my-app
        run: |
          pytest tests/ui -v \
            --vericue-report-html reports/vericue.html \
            --vericue-report-xml  reports/vericue.xml

      - name: Upload reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: ui-test-reports
          path: reports/

GitLab CI

yaml
ui-tests:
  image: your-qt-build-image
  script:
    - cmake -B build -DENABLE_VERICUE=ON && cmake --build build --parallel
    - pip install vericue pytest pytest-asyncio
    - export QT_QPA_PLATFORM=offscreen
    - export VERICUE_TEST_APP=build/my-app
    - pytest tests/ui -v --vericue-report-xml reports/vericue.xml
  artifacts:
    when: always
    reports:
      junit: reports/vericue.xml
    paths:
      - reports/

Parallel jobs

Because vericue_test_app starts the app with --port 0 (OS-assigned port), any number of jobs or pytest-xdist workers can run on the same machine without port collisions - each spawned app instance gets its own port and its own client connection.

Concurrent automation sessions in CI

With an organization key file (or trial), a session is one live client connection - it frees as soon as the connection closes. With floating licensing, a session is one running app process: each instrumented app checks out a lease at startup and returns it on exit. Either way, a typical CI job - one app instance, one client - uses one session at a time. Size floating pools to the maximum number of simultaneous jobs, not the number of tests.

Released under a commercial licence. Privacy · Terms