Skip to content

Testing OpenGL viewports

Many Qt applications render their central view with OpenGL: CAD viewports, map canvases, 3D previews, game editors. veriCue drives them out of the box, because it never needs to understand what the viewport draws. The server synthesizes ordinary Qt input events (QMouseEvent, QWheelEvent, QTouchEvent) and sends them to the widget; your application's own camera code reacts exactly as it would to a real user.

Anything that is a QOpenGLWidget (or any QWidget subclass hosting a GL context, or a QQuickItem) is resolvable by path like every other widget:

python
VIEWPORT = "MainWindow/glViewport"

Driving the camera

Orbit, pan and zoom are just input sequences:

python
# Orbit: left-drag across the viewport. drag() sends a press, ten
# interpolated mouse moves, and a release - like a real hand.
await client.drag(VIEWPORT, 100, 100, 220, 160)

# Pan: most apps map it to middle-drag or a modifier.
await client.drag(VIEWPORT, 100, 100, 200, 150, button="middle")
await client.drag(VIEWPORT, 100, 100, 200, 150, modifiers=["shift"])

# Zoom: dy is in wheel notches (the server converts to angle delta)...
await client.scroll(VIEWPORT, dy=2)

# ...or a two-finger pinch. end > start zooms in; `rotation` (degrees)
# rotates the fingers while pinching.
await client.pinch(VIEWPORT, 100, 220, rotation=30.0)

Free-form multi-touch gestures can be composed point-by-point with multi_touch. Note: the widget must opt into touch with setAttribute(Qt::WA_AcceptTouchEvents), same as for real touchscreens.

Asserting on camera state, not pixels

The scene inside a GL viewport is pixels, not QObjects - there is no object tree to query inside the canvas. The robust pattern is to expose the camera (or scene) state as Q_PROPERTY with NOTIFY on the viewport widget:

cpp
class GLViewport : public QOpenGLWidget {
    Q_OBJECT
    Q_PROPERTY(qreal yaw READ yaw WRITE setYaw NOTIFY cameraChanged)
    Q_PROPERTY(qreal distance READ distance WRITE setDistance NOTIFY cameraChanged)
    // ...
};

Then tests read and synchronize on it like on any other property:

python
before = await client.get_properties(VIEWPORT, ["yaw", "distance"])

await client.drag(VIEWPORT, 100, 100, 190, 100)

result = await client.wait_for_property(VIEWPORT, "yaw",
                                        before["yaw"] + 45.0, timeout=2000)
assert result["matched"]

This keeps assertions exact and GPU-independent: the tests run under QT_QPA_PLATFORM=offscreen in CI with no GPU at all, because input handling and property updates do not need a real GL context.

Pixel-level verification still works when you want it: screenshot and screenshot_compare capture the composited widget (QWidget::grab() reads back the GL framebuffer). For GL content prefer a slightly higher threshold, since GPU drivers may rasterize with minor differences across machines.

Working example

The public vericue-examples repository ships gl_app - a spinning-cube viewport with an orbit/pan/zoom camera exposed as properties. veriCue's own integration suite drives it with drag, scroll and pinch and asserts on the camera state. Use it as a template for your own viewport.

Limitations

  • Objects inside the GL scene are not individually addressable - interaction is coordinate-based on the viewport widget. If you need to click scene entities by name, expose a pick API from the app (for example an invoke_method-callable slot that returns the screen position of an entity).
  • A bare QOpenGLWindow/QWindow without a widget container is not currently resolvable; host the GL surface in a QOpenGLWidget or wrap it with QWidget::createWindowContainer inside a widget hierarchy.

Released under a commercial licence. Privacy · Terms