Source code for traitsui.testing.tester.ui_tester
# (C) Copyright 2004-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" Define top-level object(s) to support testing TraitsUI applications.
"""
import contextlib
from traitsui.testing._gui import process_cascade_events
from traitsui.testing._exception_handling import reraise_exceptions
from traitsui.testing.tester._ui_tester_registry.default_registry import (
get_default_registries,
)
from traitsui.testing.tester.ui_wrapper import UIWrapper
[docs]class UITester:
"""UITester assists testing of GUI applications developed using TraitsUI.
See :ref:`testing-traitsui-applications` Section in the User Manual for
further details.
Parameters
----------
registries : list of TargetRegistry, optional
Registries of interaction for different targets, in the order
of decreasing priority. If provided, a shallow copy will be made.
Additional registries are appended to the list to provide
builtin support for TraitsUI UI and editors.
delay : int, optional
Time delay (in ms) in which actions by the tester are performed. Note
it is propagated through to created child wrappers. The delay allows
visual confirmation test code is working as desired. Defaults to 0.
auto_process_events : bool, optional
Whether to process (cascade) GUI events automatically. Default is True.
For tests that launch a modal dialog and rely on a recurring timer to
poll if the dialog is closed, it may be necessary to set this flag to
false in order to avoid deadlocks. Note that this is propagated
through to created child wrappers.
Attributes
----------
delay : int
Time delay (in ms) in which actions by the tester are performed. Note
it is propagated through to created child wrappers. The delay allows
visual confirmation test code is working as desired.
"""
def __init__(self, *, registries=None, delay=0, auto_process_events=True):
if registries is None:
self._registries = []
else:
self._registries = registries.copy()
# These registries contribute the support for TraitsUI UI and editors.
# The find_by_name/find_by_id methods in this class also depend on
# one of these registries.
self._registries.extend(get_default_registries())
self.delay = delay
self._auto_process_events = auto_process_events
@property
def auto_process_events(self):
"""Flag to indicate whether to process (cascade) GUI events
automatically. This is propagated through to
:class:`~traitsui.testing.tester.ui_wrapper.UIWrapper` instances
created from this tester.
This value can only be set at instantiation.
"""
# No mutations post-init are allowed because UIWrapper created prior to
# the mutation will not respect the new value, and so it would be
# confusing if this attribute was allowed to be mutated.
return self._auto_process_events
[docs] @contextlib.contextmanager
def create_ui(self, object, ui_kwargs=None):
"""Context manager to create a UI and dispose it upon exit.
This method does not modify the states of the :class:`UITester` and is
not a requirement for using other methods on the tester.
Parameters
----------
object : HasTraits
An instance of HasTraits for which a GUI will be created.
ui_kwargs : dict or None, optional
Keyword arguments to be provided to ``HasTraits.edit_traits``.
Default is to call ``edit_traits`` with no additional keyword
arguments.
Yields
------
ui : traitsui.ui.UI
"""
ui_kwargs = {} if ui_kwargs is None else ui_kwargs
ui = object.edit_traits(**ui_kwargs)
try:
yield ui
finally:
with reraise_exceptions():
if self._auto_process_events:
# At the end of a test, there may be events to be
# processed. If dispose happens first, those events will be
# processed after various editor states are removed,
# causing errors. But if we are asked not to process
# events, then don't.
process_cascade_events()
try:
ui.dispose()
finally:
# dispose is not atomic and may push more events to the
# event queue. Flush those too unless we are asked not to.
if self._auto_process_events:
process_cascade_events()
[docs] def find_by_name(self, ui, name):
"""Find the TraitsUI editor with the given name and return a new
``UIWrapper`` object for further interactions with the editor.
``name`` is typically a value defined on
:attr:`traitsui.item.Item.name`. This name is passed onto
:func:`traitsui.ui.UI.get_editors`.
Parameters
----------
ui : traitsui.ui.UI
The UI created, e.g. by ``create_ui``.
name : str
A single name for retrieving a target on a UI.
Returns
-------
wrapper : UIWrapper
"""
return self._get_wrapper(ui).find_by_name(name=name)
[docs] def find_by_id(self, ui, id):
"""Find the TraitsUI editor with the given identifier and return a new
``UIWrapper`` object for further interactions with the editor.
``id`` is typically a value defined on :attr:`traitsui.item.Item.id`
or :attr:`traitsui.group.Group.id`. This provides a shortcut to locate
a very nested editor in a view.
Parameters
----------
ui : traitsui.ui.UI
The UI created, e.g. by ``create_ui``.
id : str
Id for finding an item in the UI.
Returns
-------
wrapper : UIWrapper
"""
return self._get_wrapper(ui).find_by_id(id=id)
def _get_wrapper(self, ui):
"""Return a new UIWrapper wrapping the given traitsui.ui.UI.
Parameters
----------
ui : traitsui.ui.UI
The UI created, e.g. by ``create_ui``.
Returns
-------
wrapper : UIWrapper
"""
return UIWrapper(
target=ui,
registries=self._registries,
delay=self.delay,
auto_process_events=self._auto_process_events,
)