-
-
Notifications
You must be signed in to change notification settings - Fork 70
Add waitExposed and waitActive methods to QtBot #159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
The-Compiler
merged 8 commits into
pytest-dev:master
from
nicoddemus:wait-window-params
Oct 25, 2016
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
bc211c2
Add CHANGELOG entry for #153
nicoddemus 5d36883
Move timer fixture to conftest.py and support generic callbacks
nicoddemus a8e461c
Add waitActive and waitExposed methods
nicoddemus 3a7c377
Alias captureExceptions as capture_exceptions
nicoddemus b8f32a5
pep-8 aliases for waitActive and waitExposed
nicoddemus 502c290
waitActive test expected to fail on Travis
nicoddemus 8f32b33
Small adjustments as discussed in review
nicoddemus 24702b7
Change `SignalTimeoutError` to `TimeoutError`
nicoddemus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| import functools | ||
| import contextlib | ||
| import functools | ||
| import weakref | ||
|
|
||
| from pytestqt.wait_signal import SignalBlocker, MultiSignalBlocker, SignalTimeoutError, SignalEmittedSpy | ||
| from pytestqt.exceptions import SignalTimeoutError, TimeoutError | ||
| from pytestqt.qt_compat import qt_api | ||
| from pytestqt.wait_signal import SignalBlocker, MultiSignalBlocker, SignalEmittedSpy, SignalEmittedError | ||
|
|
||
|
|
||
| def _parse_ini_boolean(value): | ||
|
|
@@ -26,6 +27,9 @@ class QtBot(object): | |
| **Widgets** | ||
|
|
||
| .. automethod:: addWidget | ||
| .. automethod:: captureExceptions | ||
| .. automethod:: waitActive | ||
| .. automethod:: waitExposed | ||
| .. automethod:: waitForWindowShown | ||
| .. automethod:: stopForInteraction | ||
| .. automethod:: wait | ||
|
|
@@ -141,6 +145,66 @@ def addWidget(self, widget): | |
|
|
||
| add_widget = addWidget # pep-8 alias | ||
|
|
||
| def waitActive(self, widget, timeout=1000): | ||
| """ | ||
| Context manager that waits for ``timeout`` milliseconds or until the window is active. | ||
| If window is not exposed within ``timeout`` milliseconds, raise ``TimeoutError``. | ||
|
|
||
| This is mainly useful for asynchronous systems like X11, where a window will be mapped to screen | ||
| some time after being asked to show itself on the screen. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| with qtbot.waitActive(widget, timeout=500): | ||
| show_action() | ||
|
|
||
| :param QWidget widget: | ||
| Widget to wait for. | ||
|
|
||
| :param int|None timeout: | ||
| How many milliseconds to wait for. | ||
|
|
||
| .. note:: | ||
| This function is only available in PyQt5, raising a ``RuntimeError`` if called from | ||
| ``PyQt4`` or ``PySide``. | ||
|
|
||
| .. note:: This method is also available as ``wait_active`` (pep-8 alias) | ||
| """ | ||
| __tracebackhide__ = True | ||
| return _WaitWidgetContextManager('qWaitForWindowActive', 'activated', widget, timeout) | ||
|
|
||
| wait_active = waitActive # pep-8 alias | ||
|
|
||
| def waitExposed(self, widget, timeout=1000): | ||
| """ | ||
| Context manager that waits for ``timeout`` milliseconds or until the window is exposed. | ||
| If the window is not exposed within ``timeout`` milliseconds, raise ``TimeoutError``. | ||
|
|
||
| This is mainly useful for asynchronous systems like X11, where a window will be mapped to screen | ||
| some time after being asked to show itself on the screen. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| with qtbot.waitExposed(splash, timeout=500): | ||
| startup() | ||
|
|
||
| :param QWidget widget: | ||
| Widget to wait for. | ||
|
|
||
| :param int|None timeout: | ||
| How many milliseconds to wait for. | ||
|
|
||
| .. note:: | ||
| This function is only available in PyQt5, raising a ``RuntimeError`` if called from | ||
| ``PyQt4`` or ``PySide``. | ||
|
|
||
| .. note:: This method is also available as ``wait_exposed`` (pep-8 alias) | ||
| """ | ||
| __tracebackhide__ = True | ||
| return _WaitWidgetContextManager('qWaitForWindowExposed', 'exposed', widget, timeout) | ||
|
|
||
| wait_exposed = waitExposed # pep-8 alias | ||
|
|
||
| def waitForWindowShown(self, widget): | ||
| """ | ||
| Waits until the window is shown in the screen. This is mainly useful for asynchronous | ||
|
|
@@ -150,17 +214,14 @@ def waitForWindowShown(self, widget): | |
| :param QWidget widget: | ||
| Widget to wait on. | ||
|
|
||
| .. note:: In Qt5, the actual method called is qWaitForWindowExposed, | ||
| but this name is kept for backward compatibility | ||
| .. note:: In ``PyQt5`` this function is considered deprecated in favor of :meth:`waitExposed`. | ||
|
|
||
| .. note:: This method is also available as ``wait_for_window_shown`` (pep-8 alias) | ||
| """ | ||
| if hasattr(qt_api.QtTest.QTest, 'qWaitForWindowShown'): # pragma: no cover | ||
| # PyQt4 and PySide | ||
| qt_api.QtTest.QTest.qWaitForWindowShown(widget) | ||
| else: # pragma: no cover | ||
| # PyQt5 | ||
| qt_api.QtTest.QTest.qWaitForWindowExposed(widget) | ||
| if qt_api.pytest_qt_api == 'pyqt5': | ||
| return qt_api.QtTest.QTest.qWaitForWindowExposed(widget) | ||
| else: | ||
| return qt_api.QtTest.QTest.qWaitForWindowShown(widget) | ||
|
|
||
| wait_for_window_shown = waitForWindowShown # pep-8 alias | ||
|
|
||
|
|
@@ -221,11 +282,11 @@ def waitSignal(self, signal=None, timeout=1000, raising=None, check_params_cb=No | |
|
|
||
| :param Signal signal: | ||
| A signal to wait for, or a tuple ``(signal, signal_name_as_str)`` to improve the error message that is part | ||
| of ``SignalTimeoutError``. Set to ``None`` to just use timeout. | ||
| of ``TimeoutError``. Set to ``None`` to just use timeout. | ||
| :param int timeout: | ||
| How many milliseconds to wait before resuming control flow. | ||
| :param bool raising: | ||
| If :class:`QtBot.SignalTimeoutError <pytestqt.plugin.SignalTimeoutError>` | ||
| If :class:`QtBot.TimeoutError <pytestqt.plugin.TimeoutError>` | ||
| should be raised if a timeout occurred. | ||
| This defaults to ``True`` unless ``qt_wait_signal_raising = false`` | ||
| is set in the config. | ||
|
|
@@ -280,12 +341,12 @@ def waitSignals(self, signals=None, timeout=1000, raising=None, check_params_cbs | |
|
|
||
| :param list signals: | ||
| A list of :class:`Signal` objects to wait for. Alternatively: a list of (``Signal, str``) tuples of the form | ||
| ``(signal, signal_name_as_str)`` to improve the error message that is part of ``SignalTimeoutError``. | ||
| ``(signal, signal_name_as_str)`` to improve the error message that is part of ``TimeoutError``. | ||
| Set to ``None`` to just use timeout. | ||
| :param int timeout: | ||
| How many milliseconds to wait before resuming control flow. | ||
| :param bool raising: | ||
| If :class:`QtBot.SignalTimeoutError <pytestqt.plugin.SignalTimeoutError>` | ||
| If :class:`QtBot.TimeoutError <pytestqt.plugin.TimeoutError>` | ||
| should be raised if a timeout occurred. | ||
| This defaults to ``True`` unless ``qt_wait_signal_raising = false`` | ||
| is set in the config. | ||
|
|
@@ -390,7 +451,7 @@ def view_updated(): | |
| qtbot.waitUntil(lambda: view_model.count() > 10) | ||
|
|
||
| Note that this usage only accepts returning actual ``True`` and ``False`` values, | ||
| so returning an empty list to express "falseness" raises an ``ValueError``. | ||
| so returning an empty list to express "falseness" raises a ``ValueError``. | ||
|
|
||
| :param callback: callable that will be called periodically. | ||
| :param timeout: timeout value in ms. | ||
|
|
@@ -433,7 +494,7 @@ def timed_out(): | |
| wait_until = waitUntil # pep-8 alias | ||
|
|
||
| @contextlib.contextmanager | ||
| def capture_exceptions(self): | ||
| def captureExceptions(self): | ||
| """ | ||
| .. versionadded:: 2.1 | ||
|
|
||
|
|
@@ -447,11 +508,15 @@ def capture_exceptions(self): | |
|
|
||
| # exception is a list of sys.exc_info tuples | ||
| assert len(exceptions) == 1 | ||
|
|
||
| .. note:: This method is also available as ``capture_exceptions`` (pep-8 alias) | ||
| """ | ||
| from pytestqt.exceptions import capture_exceptions | ||
| with capture_exceptions() as exceptions: | ||
| yield exceptions | ||
|
|
||
| capture_exceptions = captureExceptions | ||
|
|
||
| @classmethod | ||
| def _inject_qtest_methods(cls): | ||
| """ | ||
|
|
@@ -495,8 +560,10 @@ def result(*args, **kwargs): | |
| setattr(cls, method_name, method) | ||
|
|
||
|
|
||
| # provide easy access to SignalTimeoutError to qtbot fixtures | ||
| # provide easy access to exceptions to qtbot fixtures | ||
| QtBot.SignalTimeoutError = SignalTimeoutError | ||
| QtBot.SignalEmittedError = SignalEmittedError | ||
| QtBot.TimeoutError = TimeoutError | ||
|
|
||
|
|
||
| def _add_widget(item, widget): | ||
|
|
@@ -527,3 +594,40 @@ def _iter_widgets(item): | |
| Iterates over widgets registered in the given pytest item. | ||
| """ | ||
| return iter(getattr(item, 'qt_widgets', [])) | ||
|
|
||
|
|
||
| class _WaitWidgetContextManager(object): | ||
| """ | ||
| Context manager implementation used by ``waitActive`` and ``waitExposed`` methods. | ||
| """ | ||
|
|
||
| def __init__(self, method_name, adjective_name, widget, timeout): | ||
| """ | ||
| :param str method_name: name ot the ``QtTest`` method to call to check if widget is active/exposed. | ||
| :param str adjective_name: "activated" or "exposed". | ||
| :param widget: | ||
| :param timeout: | ||
| """ | ||
| self._method_name = method_name | ||
| self._adjective_name = adjective_name | ||
| self._widget = widget | ||
| self._timeout = timeout | ||
|
|
||
| def __enter__(self): | ||
| __tracebackhide__ = True | ||
| if qt_api.pytest_qt_api != 'pyqt5': | ||
| raise RuntimeError('Available in PyQt5 only') | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc_val, exc_tb): | ||
| __tracebackhide__ = True | ||
| try: | ||
| if exc_type is None: | ||
| method = getattr(qt_api.QtTest.QTest, self._method_name) | ||
| r = method(self._widget, self._timeout) | ||
| if not r: | ||
| msg = 'widget {} not {} in {} ms.'.format(self._widget, self._adjective_name, self._timeout) | ||
| raise TimeoutError(msg) | ||
| finally: | ||
| self._widget = None | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this to avoid leaking memory?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! |
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same changes as above in this docstring 😉