Add support for performing actions or inspecting states

Support for perform() and inspect() can be extended by registering additional interaction type and handling logic via register_interaction() on a TargetRegistry.

Suppose we want to perform many mouse clicks on a UI component in a test, but instead of calling perform(MouseClick()) many times in a loop like this:

my_widget = UITester().find_by_id(ui, "some_id")
for _ in range(10):
    my_widget.perform(MouseClick())

We want to exercise the mouse click many times by invoking perform() once only:

my_widget = UITester().find_by_id(ui, "some_id")
my_widget.perform(ManyMouseClick(n_times=10))

Define the interaction

We can define this ManyMouseClick object simply like this:

class ManyMouseClick:
    def __init__(self, n_times):
        self.n_times = n_times

Identify the target

Next, we need to know which object implements the GUI component. This is where implementation details start to come in (see Why is Target protected?). We can inspect the object being wrapped:

>>> my_widget
<traitsui.testing.tester.ui_wrapper.UIWrapper object at 0x7f940a3f10b8>
>>> my_widget._target
<package.ui.qt.shiny_button.ShinyButton object at 0x7fc90fb3b570>

The target is an instance of a ShinyButton class (made up for this document). In this object, there is an instance of Qt QPushButton widget which we want to click with the mouse:

>>> my_widget._target.control
<PyQt5.QtWidgets.QPushButton object at 0x7fbcc3ac3558>

Implement a handler

So now all we need to do, is to tell UITester how to perform ManyMouseClick on an instance of ShinyButton.

We define a function to perform the mouse clicks:

def many_mouse_click(wrapper, interaction):
    # wrapper is an instance of UIWrapper
    # interaction is an instance of ManyMouseClick
    for _ in range(interaction.n_times):
        wrapper._target.control.click()

Then we need to register this function with an instance of TargetRegistry:

from traitsui.testing.api import TargetRegistry
from package.ui.qt.shiny_button import ShinyButton

custom_registry = TargetRegistry()
custom_registry.register_interaction(
    target_class=ShinyButton,
    interaction_class=ManyMouseClick,
    handler=many_mouse_click,
)

The signature of many_mouse_click is required by the register_interaction() method on TargetRegistry. By setting the target_class and interaction_class, we restrict the types of wrapper._target and interaction received by many_mouse_click respectively.

Apply it

Finally, we can use this registry with the UITester:

tester = UITester(registries=[custom_registry])
my_widget = tester.find_by_id(ui, "some_id")
my_widget.perform(ManyMouseClick(n_times=10))

All the builtin testing support for TraitsUI editors are still present, but now this tester can perform the additional, custom user interaction.

Inspecting states

The distinction between perform() and inspect() is merely in their returned values.

We can call inspect() with the ManyMouseClick object we just created:

value = my_widget.inspect(ManyMouseClick(n_times=10))

The returned value is the value returned by many_mouse_click, the handler registered for ManyMouseClick and ShinyButton. In this case, the value is None.