Python API reference

Table of contents

Registering applications

Available applications are defined in files named applications.json. They contain a list of applications names mapped to their path and default arguments. Two different files are used: a local one in the current folder and a shared one in the user folder. Applications registered in the shared file will be globally available from any script and any test suite.

These files can be edited in any text editor, with the Qat GUI or with the following API functions:

def register_application(name: str, path: str, args='', shared=None) -> None

Adds the given application to the current applications.json file.

  • name Must be a unique name for this application. It will be used when calling qat.start_application() for example.

  • path The absolute path to the application executable.

  • args (optional) The default arguments used when launching the application (e.g. when using Qat-Spy). They can be overridden when calling qat.start_application().

  • shared (optional) True to use the shared (global) file, False to use the local one. If None (default), the local file will be used if it exists otherwise the shared one will be used.


def unregister_application(name: str, shared=None) -> None

Removes the given application from the current applications.json file.

  • name The unique name of the application, must be the same as the one given to register_application().

  • shared True to use the shared (global) file, False to use the local one. If None, the local file will be used if it exists otherwise the shared one will be used.

Note: If the given application does not exist in the current applications.json file, this function does nothing.


def get_application_path(name: str) -> str

Returns the path of the given application from the current applications.json file.

  • name The unique name of the application, must be the same as the one given to register_application().


def get_config_file(shared=None) -> Path

Returns the path to the current applications.json file.

  • shared True to use the shared (global) file, False to use the local one.

If shared is None and if a configuration file exists in the current working directory, it will be returned. Otherwise, the global one from the user directory (e.g. ~/.qat) will be returned if it exists. Finally, if no file exists, the path to a default file in the current working directory will be returned.


def list_applications() -> dict

Returns the list of registered applications. The contents of both local and shared configurations is merged. Values from the local configuration take precedence: if an application is registered in both configurations with different paths or arguments, the values from the local configuration will be returned.

Running applications

The following API function starts the given application.

def start_application(app_name: str, args = None, detached = False) -> ApplicationContext

Notes:

The application must be registered in applications.json.

The function will raise an exception if the application could not start before testSettings.wait_for_app_start_timeout.

The function will raise an exception if the application is started on an unsupported system.

When detached is True, the application will start without initializing Qat. This is useful when testing cases where the application returns early (e.g. in case of errors such as invalid arguments). The returned ApplicationContext can still be used to retrieve the corresponding exit code and process ID.

The returned ApplicationContext uniquely identifies the started application instance.

This allows multiple applications / instances to be launched at the same time. To switch between applications, the current ApplicationContext must be changed:

def set_current_application_context(app_context: ApplicationContext)

All subsequent API calls will then be redirected to the application identified by app_context.

Note: Objects already retrieved with wait_for_object[_exists]() or similar functions (see sections below) will still be linked to their original ApplicationContext. You need to get a new Object instance if you want to interact with the same object but in another application instance.

The current application context can be retrieved with the following function:

def current_application_context() -> ApplicationContext

The close_application() function terminates the application identified by the given context and returns its exit code.

def close_application(app_context: None) -> int
  • app_context The context of the application to be closed. If None, the current context will be used.


You can also attach Qat to an already running application:

def attach_to_application(name_or_pid) -> ApplicationContext
  • name_or_pid can be the name of a registered application, the name of a (running) executable file or a process ID.

Note: On linux, the application must have been launched by Qat (by calling qat.start_application()) for this function to work.


To prevent user activity from affecting test results, tested applications can be “locked”: they will ignore events such as mouse clicks and moves, keyboard input and focus changes.

def lock_application()
def unlock_application()

By default, Qat locks all applications when running tests in release mode. Applications are unlocked in debug mode to allow you to interact with the application while debugging a test.

This behavior can be changed by editing lockUI parameter in the testSettings.json file.

Finding objects

def list_top_windows() -> list<QtObject>

This functions returns a list of all top windows existing in the application, including the ones that are not currently open/visible.


def wait_for_object_exists(definition: dict, timeout = Settings.wait_for_object_timeout) -> QtObject

This function searches for the unique object corresponding to the given definition. It returns an Object that can be used to access properties and methods of the remote object (see section below).

It raises a LookupError if the object cannot be found before the given timeout (in milliseconds) or if there are multiple objects matching the given definition.


def wait_for_object(definition: dict, timeout = Settings.wait_for_object_timeout) -> QtObject

This function is similar to the previous one but also waits for the object to be accessible (i.e. visible and enabled).


def wait_for_object_missing(definition: dict, timeout = Settings.wait_for_object_timeout)

This function waits for the given object to be deleted from the application i.e it waits until wait_for_object_exists() fails. It raises a TimeoutError if the given object still exists after the given timeout (in milliseoncds) has been reached.


def find_all_objects(definition: dict, timeout = Settings.wait_for_object_timeout) -> list<QtObject>

This function returns a list of Objects that match the given definition.


To retrieve all children of a given object, you can use the children property:

parent = qat.wait_for_object_exists(parent_definition)
for child in parent.children:
   print(child.objectName)

Accessing properties and functions

Any property or function accessible through Qt’s MetaObject system is available to the Qat API. This means all Q_INVOKABLE methods and all Q_PROPERTY-defined members, including QML objects and custom C++ objects.

Any property/method accessible by QML is also accessible from the Qat API.

Properties are automatically mapped to Python attributes of the objects returned by functions such as wait_for_object and wait_for_object_exists (see section above). If a property is writeable, its value can be changed with a normal assignment in Python:

button_def = {
   'objectName': "someUniqueName",
   'type': 'Button'
}
button = qat.wait_for_object(button_def)
# Verify that the right object has been found
assert button.objectName == "someUniqueName"
# Change the button's width to be twice its height
button.width = 2 * button.height

In a similar way, methods can be called on Objects:

panel_def = {
   'objectName': "somePanelName",
   'type': 'PanelLayout'
}
panel = qat.wait_for_object(panel_def)
# Access the 'proxy' property, which can be a C++ context proxy
proxy = panel.proxy

# Call the saveData() method on the proxy.
# Argument(s) must match the function's prototype.
proxy.saveData(True)

Synchronization

Although Qat executes functions synchronously, some changes may occur asynchronously in the tested application. For example, clicking on a widget may update some binding that will be evaluated later by the Qt Event loop.

To avoid timing issues when running tests, the following functions can be used to provide synchronization points:

def wait_for_property_value(
    definition: dict,
    property_name: str,
    new_value,
    comparator = None,
    check = False,
    timeout = Settings.wait_for_object_timeout) -> bool

This function waits for the given object’s property to reach the given value. It returns True if the value was reached, False otherwise.

  • definition: an object definition or an Object returned by wait_for_object*()

  • property_name: the name of the property to wait for

  • new_value: the value to reach

  • comparator: Callable used to compare property values. == is used by default.

  • check: If True, raises an exception in case of failure. False by default.

  • timeout: If the new_value is not reached after this timeout, return False.


def wait_for_property_change(
   definition: dict,
   property_name: str,
   old_value,
   comparator = None,
   check = False,
   timeout = Settings.wait_for_object_timeout) -> bool

This function waits for the given object’s property to change its value. It returns True if the value has changed, False otherwise.

  • definition: an object definition or an Object returned by wait_for_object*()

  • property_name: the name of the property to wait for

  • old_value: the original value

  • comparator: Callable used to compare property values. == is used by default.

  • check: If True, raises an exception in case of failure. False by default.

  • timeout: If the property value has not changed after this timeout, returns False.

You can also wait for any given condition using Python’s lambdas:

def wait_for(condition, timeout = Settings.wait_for_object_timeout) -> bool

Example:

object_def = {'objectName': 'someText'}
object = qat.wait_for_object_exists(object_def)
reached = qat.wait_for(
   lambda: object.text == "0",
   timeout = 5000
)
if not reached:
   raise Exception("Condition was not reached in 5 seconds")

Connections and Bindings

Qat uses the Qt’s Signal and Slot mechanism to enable tests to react to any change in the tested applications.

There are two ways of using this feature: Connections call a custom callback function when a property changes in the tested application, whereas Bindings automatically synchronize a local Python attribute with an object’s property in the tested application.

def connect(object, property, callback) -> str

Connects a signal from the application to the given callback. If ‘property’ is a signal name, the given callback will be called without argument. If ‘property’ is a Qt property name, the given callback will be called with one argument containing the new value of the property. Returns a unique identifier for the newly created connection.

  • object: an object definition or an Object returned by wait_for_object*()

  • property: the name of the property or signal to connect to.

  • callback: a Python Callable (e.g. function or lambda)


def disconnect(conn_id)

Disconnects a signal or property from its callback.

  • conn_id: a connection identifier, as returned by connect()


def bind(remote_object, remote_property, local_object, local_property) -> qat.Binding

Automatically establishes a connection between the given remote object’s property and the given local receiver. The returned Binding object can be used to manage the connection. Note: this is equivalent to create a Binding object with the same arguments.

  • remote_object: an object definition or an Object returned by wait_for_object*()

  • remote_property: the name of the property or signal to connect to.

  • local_object: any Python object

  • local_attribute: the name of the Python attribute to be connected. Must be an attribute of local_object and be of a compatible type.

Example:

label = {
   'objectName': 'someLabel'
}

class Receiver():
   def __init__(self):
      self.value = ''

   def __setattr__(self, name: str, value):
      print(f'Value changed: {name} => {value}')
      

# Create a binding between the text of the label and the receiver's value
receiver = Receiver()
binding = qat.bind(label, 'text', receiver, 'value')

# Change the text of the label in the application
qat.wait_for_object(label).text = 'Some new text'

# Verify that the receiver has updated its value
assert receiver.value == 'Some new text'

The bind function returns a Binding object which provides the following functions:

def connect()

Connects (or re-connects) this binding to the remote object.


def disconnect()

Disconnects this binding. Receiver will not be updated anymore.

Mouse and keyboard

To emulate user activity, Qat generates Qt Events in C++ (as opposed to sending OS events and using the mouse cursor).

This means that:

  • Events can be sent when the application is in the background

  • Mouse cursor is left free to the user

  • Events may be sent to the target object even if it is not really accessible by the user (e.g. another dialog is above the object, or the object is outside the window boundaries).

  • Events can be “auto-verified”: if not object accepted an event, an exception can be raised to let the test know that something went wrong.

The following mouse events are supported:

def mouse_press(definition, x, y, modifier, button, check)

Press the button of the mouse on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT

  • check: Optional check. When True this function will raise an exception if no widget accepted the event. Default is False.


def mouse_release(definition, x, y, modifier, button, check)

Release the button of the mouse on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT

  • check: Optional check. When True this function will raise an exception if no widget accepted the event. Default is False.


def mouse_click(definition, x, y, modifier, button, check)

Click with the button of the mouse on the definition object at local coordinates x, y while holding the modifier key. Equivalent to a mousePress() immediately followed by a mouseRelease().

Note: Sending two mouse_click events quickly will not generate a double-click event. Use double_click() function instead

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT

  • check: Optional check. When True this function will raise an exception if no widget accepted the event. Default is False.


def double_click(definition, x, y, modifier, button, check)

Double-click with the button of the mouse on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT

  • check: Optional check. When True this function will raise an exception if no widget accepted the event. Default is False.


def mouse_move(definition, x, y, modifier, button)

Move the mouse to the local coordinates x, y of the definition object while holding the button of the mouse and the modifier key.

Note: if mousePress was called before, this will act as drag operation.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT


def mouse_drag(definition, x, y, dx, dy, modifier, button, check)

Drag the mouse by dx, dy pixels from the local coordinates x, y of the definition object while holding the button of the mouse and the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • dx, dy: the number of pixels to move the mouse by, in each direction.

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT

  • check: Optional check. When True this function will raise an exception if no widget accepted the event. Default is False.


def mouse_wheel(definition, x, y, xDegrees, yDegrees, modifier, check)

Scroll the mouse wheel by xDegrees, yDegrees at the local coordinates x, y of the definition object while holding the modifier key.

Note: a physical wheel increment generally corresponds to 15 degrees

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of the event, relative to the object. If not given, event will occur at the center of the object

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

  • button: Optional mouse button. Must be one of the constants in qat.Button. Default is LEFT

  • check: Optional check. When True this function will raise an exception if no widget accepted the event. Default is False.


Keyboard events are supported by the following functions:

def type_in(definition, text: str)

Type text in definition object, character by character.

  • definition: an object definition or an Object returned by wait_for_object*()

  • text: Any string. Can also use the following special keys: <Backspace>, <Delete>, <Enter>, <Escape>, <Return>, <Tab>, <Control>, <Shift>, <Alt>


def shortcut(definition, key_combination)

Triggers the given shortcut on the given object. Shortcut string must follow the Qt syntax, e.g: ‘Ctrl+Z, Alt+O, Alt+Shift+R, …’

  • definition: an object definition or an Object returned by wait_for_object*()

  • key_combination: The key combination to trigger.


def press_key(definition, key: str)

Press key in definition object.

  • definition: an object definition or an Object returned by wait_for_object*()

  • key: Any character. Can also use the following special keys: <Backspace>, <Delete>, <Enter>, <Escape>, <Return>, <Tab>, <Control>, <Shift>, <Alt>


def release_key(definition, key: str)

Release key in definition object.

  • definition: an object definition or an Object returned by wait_for_object*()

  • key: Any character. Can also use the following special keys: <Backspace>, <Delete>, <Enter>, <Escape>, <Return>, <Tab>, <Control>, <Shift>, <Alt>

Touch devices

Qat can simulate events related to touch devices, such as touch screens and touch pads.

The following functions support both single and multiple touch points:

def touch_tap(definition, x, y, modifier)

Tap one or more fingers on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of each finger, relative to the object. If not given, event will occur at the center of the object. Each argument can be a single value (for single touch point) or an array (for multiple touch points). When using arrays, x and y must be of the same size.

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE

Note: Sending two touch_tap events quickly will not generate a double-tap event. Use double_click() function instead

A touch_tap call is equivalent to a touch_press immediately followed by a touch_release.


def touch_press(definition, x, y, modifier)

Press one or more fingers on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of each finger, relative to the object. If not given, event will occur at the center of the object. Each argument can be a single value (for single touch point) or an array (for multiple touch points). When using arrays, x and y must be of the same size.

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE.


def touch_release(definition, x, y, modifier)

Release one or more fingers on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of each finger, relative to the object. If not given, event will occur at the center of the object. Each argument can be a single value (for single touch point) or an array (for multiple touch points). When using arrays, x and y must be of the same size.

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE.


def touch_move(definition, x, y, modifier)

Move one or more fingers on the definition object at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: the coordinates of each finger, relative to the object. If not given, event will occur at the center of the object. Each argument can be a single value (for single touch point) or an array (for multiple touch points). When using arrays, x and y must be of the same size.

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE.

This function has no effect if touch_press was not called on the same object.


def touch_drag(definition, x, y, dx, dy, modifier)

Press and drag one or more fingers on the definition object starting at local coordinates x, y while holding the modifier key.

  • definition: an object definition or an Object returned by wait_for_object*()

  • x, y: start coordinates of each finger, relative to the object. If not given, event will occur at the center of the object. Each argument can be a single value (for single touch point) or an array (for multiple touh points). When using arrays, x and y must be of the same size.

  • dx, dy: the number of pixels to move each finger, relative to the object. Each argument can be a single value (to apply the same movement to all touch points) or an array (to have different movements for each touch point). When using arrays, dx and dy must be of the same size.

  • modifier: Optional keyboard modifier. Must be one of the constants in qat.Modifier. Default is NONE.

Gestures

Qat also provides some helper functions to simulate complete gestures:

def pinch(
     definition: dict,
     rotation = 0.,
     translation = None,
     scale = 1.0):

Generates a sequence of touch events representing a pinch gesture. Such gestures are compatible with both the default QGestureRecognizer (for QWidget-based applications) and QML’s PinchArea and MultiTouchArea.

  • definition: an object definition or an Object returned by wait_for_object*()

  • rotation (optional): Total angle of rotation to apply. Value is in degrees and in clockwise direction.

  • translation (optional): Global translation of the pinch. This corresponds to the translation of the central point between the two fingers.

  • scale (optional): Variation of the distance between the two fingers, typically representing a zoom factor. For example, if scale is set to 2.0, the distance between the fingers will double between the beginning and the end of the gesture.

All three optional parameters can be combined to define a complete pinch gesture.

The distance between the two fingers during the gesture will be automatically determined based on the target widget’s size.


def flick(definition: dict, dx, dy)

Moves the given Flickable by the given horizontal and vertical distances in pixels.

  • definition: an object definition or an Object returned by wait_for_object*()

  • dx: the horizontal distance in pixels

  • dy: the vertical distance in pixels


def native_pinch(
      definition: dict,
      angle: int = None,
      scale: float = None,
      check: bool = False):

Generates a native pinch event.

Native gesture events are high-level events generated by the operating system, usually from a sequence of trackpad events.

  • definition: an object definition or an Object returned by wait_for_object*()

  • angle (optional): Total angle of rotation to apply. Value is in degrees and in clockwise direction.

  • scale (optional): Variation of the distance between the two fingers, typically representing a zoom factor. For example, if scale is set to 2.0, the distance between the fingers will double between the beginning and the end of the gesture.

  • check (optional): If True, raises an exception in case of failure (i.e when the event is not handled by any widget). False by default.

This function works with QML/QtQuick widgets only.

Screenshots

By default, Qat will take a screenshot of the application when a failure is detected and will add it to the report. This feature can be disabled by setting logScreenshotOnFail to False in the testSettings.json file.

At any time, you can take a screenshot of each top-level window and save it to an image file (format will be deduced from extension):

def take_screenshot(path = None)

If path is not set, the screenshot will be saved into the screenshots folder of the current directory. The file name will be qat-screenshotN.png where N is the index of the window.


You can also grab a screenshot of any widget and manipulate it within your test as a normal Object:

def grab_screenshot(definition, delay = 0, timeout = Settings.wait_for_object_timeout)
  • definition: an object definition or an Object returned by wait_for_object*()

  • delay: wait delay milliseconds before taking the screenshot. Default is 0: no delay

  • timeout: Number of milliseconds to wait for the screenshot to be available once taken. If timeout is reached, an exception is raised.

Warning: Since screenshots reside in tested application’s memory, and to avoid running out of RAM, the maximum number of screenshots is set to 10. Once this limit is reached, taking a new screenshot will delete the oldest one in memory.

When successful, the grabScreenshot function returns an Image object which provides the following functions:

def getPixel(x: int, y: int) -> int
def getPixelRGBA(x: int, y: int) -> Color
def save(file_name: str)

getPixel returns the 32-bit value of the pixel at x, y. This value is the hexadecimal value of each component alpha, red, green, blue in this order.

Example:

image = qat.grabScreenshot(someObject)
assert image.getPixel(5, 5) == 0xff008000 # opaque green

getPixelRGBA returns the color of the pixel at x, y as a Color object. This object has a member for each component red, green, blue, alpha. Components can be accessed by index, in this order.

Example:

pixel = image.getPixelRGBA(10, 10)
assert pixel.red == 0
assert pixel.green == 127
assert pixel.blue == 255
assert pixel.alpha == 255

assert pixel[0] == 0
assert pixel[1] == 127
assert pixel[2] == 255
assert pixel[3] == 255

save saves the image to the given file. Format will be deduced from the extension. Raises an exception if image cannot be saved.

file_path = Path(os.getcwd()) / 'test_image.png'
image.save(str(file_path))

The following members are also available:

  • width: The width of the image, in pixels

  • height: The height of the image, in pixels