File: ui_tester.py

package info (click to toggle)
python-traitsui 8.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 18,232 kB
  • sloc: python: 58,982; makefile: 113
file content (184 lines) | stat: -rw-r--r-- 6,912 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# (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


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

    @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()

    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)

    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,
        )