From 45d93dd914df10299d9b923f93a12c67dfd212f5 Mon Sep 17 00:00:00 2001 From: Vasily Ryabov Date: Tue, 24 Jul 2018 03:49:44 +0300 Subject: [PATCH 1/6] Improve set_focus() for both backends including minimized state (fix #443). --- pywinauto/base_wrapper.py | 11 ++++++ pywinauto/controls/hwndwrapper.py | 13 ++++--- pywinauto/controls/uiawrapper.py | 19 ++++++---- pywinauto/unittests/test_uiawrapper.py | 48 ++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/pywinauto/base_wrapper.py b/pywinauto/base_wrapper.py index 58de29585..eacbb865a 100644 --- a/pywinauto/base_wrapper.py +++ b/pywinauto/base_wrapper.py @@ -39,6 +39,8 @@ import re import time import win32process +import win32gui +import win32con import six try: @@ -310,6 +312,15 @@ def is_enabled(self): """ return self.element_info.enabled #and self.top_level_parent().element_info.enabled + # ----------------------------------------------------------- + def was_maximized(self): + """Indicate whether the window was maximized before minimizing or not""" + if self.handle: + (flags, _, _, _, _) = win32gui.GetWindowPlacement(self.handle) + return (flags & win32con.WPF_RESTORETOMAXIMIZED == win32con.WPF_RESTORETOMAXIMIZED) + else: + return None + #------------------------------------------------------------ def rectangle(self): """ diff --git a/pywinauto/controls/hwndwrapper.py b/pywinauto/controls/hwndwrapper.py index a912750b8..e9088fa26 100644 --- a/pywinauto/controls/hwndwrapper.py +++ b/pywinauto/controls/hwndwrapper.py @@ -1301,9 +1301,7 @@ def set_focus(self): """ # "steal the focus" if there is another active window # otherwise it is already into the foreground and no action required - cur_foreground = win32gui.GetForegroundWindow() - - if self.handle != cur_foreground: + if not self.has_focus(): # Notice that we need to move the mouse out of the screen # but we don't use the built-in methods of the class: # self.mouse_move doesn't do the job well even with absolute=True @@ -1312,7 +1310,10 @@ def set_focus(self): # change active window if self.is_minimized(): - win32gui.ShowWindow(self.handle, win32con.SW_RESTORE) + if self.was_maximized(): + self.maximize() + else: + self.restore() else: win32gui.ShowWindow(self.handle, win32con.SW_SHOW) win32gui.SetForegroundWindow(self.handle) @@ -1327,6 +1328,10 @@ def set_focus(self): # Non PEP-8 alias SetFocus = set_focus + def has_focus(self): + """Check the window is in focus (foreground)""" + return self.handle == win32gui.GetForegroundWindow() + def has_keyboard_focus(self): """Check the keyboard focus on this control.""" control_thread = win32process.GetWindowThreadProcessId(self.handle)[0] diff --git a/pywinauto/controls/uiawrapper.py b/pywinauto/controls/uiawrapper.py index 2e67336d9..fc5ee1d0d 100644 --- a/pywinauto/controls/uiawrapper.py +++ b/pywinauto/controls/uiawrapper.py @@ -35,8 +35,8 @@ from __future__ import print_function import six -import comtypes import time +import comtypes from .. import backend from ..timings import Timings @@ -400,14 +400,21 @@ def has_keyboard_focus(self): # ----------------------------------------------------------- def set_focus(self): """Set the focus to this element""" - if self.is_keyboard_focusable() and not self.has_keyboard_focus(): - try: - self.element_info.element.SetFocus() - except comtypes.COMError: - pass # TODO: add RuntimeWarning here + if self.is_minimized(): + if self.was_maximized(): + self.maximize() + else: + self.restore() + try: + self.element_info.element.SetFocus() + except comtypes.COMError as exc: + warnings.warn('The window has not been focused due to ' \ + 'COMError: {}'.format(exc), RuntimeWarning) return self + # TODO: figure out how to implement .has_focus() method (if no handle available) + # ----------------------------------------------------------- def close(self): """ diff --git a/pywinauto/unittests/test_uiawrapper.py b/pywinauto/unittests/test_uiawrapper.py index a729b2357..642b39a95 100644 --- a/pywinauto/unittests/test_uiawrapper.py +++ b/pywinauto/unittests/test_uiawrapper.py @@ -1584,6 +1584,54 @@ def test_tv_drag_n_drop(self): self.assertEqual(itm.window_text(), 'Months') + class WindowWrapperTests(unittest.TestCase): + + """Unit tests for the UIAWrapper class for Window elements""" + + def setUp(self): + """Set some data and ensure the application is in the state we want""" + _set_timings() + + test_folder = os.path.join(os.path.dirname(os.path.dirname( + os.path.dirname(os.path.abspath(__file__)))), r"apps/MouseTester") + self.qt5_app = os.path.join(test_folder, "mousebuttons.exe") + + # start the application + self.app = Application(backend='uia') + self.app = self.app.start(self.qt5_app) + + self.dlg = self.app.MouseButtonTester.wrapper_object() + self.another_app = None + + def tearDown(self): + """Close the application after tests""" + self.app.kill() + if self.another_app: + self.another_app.kill() + self.another_app = None + + def test_issue_443(self): + """Test .set_focus() for window that is not keyboard focusable""" + self.dlg.minimize() + self.assertEqual(self.dlg.is_minimized(), True) + self.dlg.set_focus() + self.assertEqual(self.dlg.is_minimized(), False) + self.assertEqual(self.dlg.is_normal(), True) + + # run another app instance (in focus now) + self.another_app = Application(backend="win32").start(self.qt5_app) + # eliminate clickable point at original app by maximizing second window + self.another_app.MouseButtonTester.maximize() + self.another_app.MouseButtonTester.set_focus() + self.assertEqual(self.another_app.MouseButtonTester.has_focus(), True) + + self.dlg.set_focus() + # another app instance has lost focus + self.assertEqual(self.another_app.MouseButtonTester.has_focus(), False) + # our window has been brought to the focus (clickable point exists) + self.assertEqual(self.dlg.element_info.element.GetClickablePoint()[-1], 1) + + if __name__ == "__main__": if UIA_support: unittest.main() From 081d1351ae69d142656c26a871336307f9f147f5 Mon Sep 17 00:00:00 2001 From: Vasily Ryabov Date: Wed, 25 Jul 2018 01:51:02 +0300 Subject: [PATCH 2/6] Fix UIAWrapper.set_focus() for non-window. --- pywinauto/controls/uiawrapper.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pywinauto/controls/uiawrapper.py b/pywinauto/controls/uiawrapper.py index fc5ee1d0d..44ed1a62e 100644 --- a/pywinauto/controls/uiawrapper.py +++ b/pywinauto/controls/uiawrapper.py @@ -400,11 +400,14 @@ def has_keyboard_focus(self): # ----------------------------------------------------------- def set_focus(self): """Set the focus to this element""" - if self.is_minimized(): - if self.was_maximized(): - self.maximize() - else: - self.restore() + try: + if self.is_minimized(): + if self.was_maximized(): + self.maximize() + else: + self.restore() + except uia_defs.NoPatternInterfaceError: + pass try: self.element_info.element.SetFocus() except comtypes.COMError as exc: From 6d2e3d05d169ad8410d0f84ae23f1ce3d9fc7712 Mon Sep 17 00:00:00 2001 From: Vasily Ryabov Date: Wed, 25 Jul 2018 01:52:22 +0300 Subject: [PATCH 3/6] Update Pillow to support Py3.7 in tests. --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 6f96f02a0..f8ef79f9c 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,6 @@ pywin32 six -Pillow==4.0.0 +Pillow==5.2.0 coverage nose codecov From 2d80c0b1090547d8afaf3c8da804b2673143d1f2 Mon Sep 17 00:00:00 2001 From: Vasily Ryabov Date: Wed, 25 Jul 2018 23:21:39 +0300 Subject: [PATCH 4/6] Add missing import (fix Codacy findings). --- pywinauto/controls/uiawrapper.py | 1 + pywinauto/unittests/test_uiawrapper.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pywinauto/controls/uiawrapper.py b/pywinauto/controls/uiawrapper.py index 44ed1a62e..9b56ea53e 100644 --- a/pywinauto/controls/uiawrapper.py +++ b/pywinauto/controls/uiawrapper.py @@ -36,6 +36,7 @@ import six import time +import warnings import comtypes from .. import backend diff --git a/pywinauto/unittests/test_uiawrapper.py b/pywinauto/unittests/test_uiawrapper.py index 642b39a95..4e4fdd789 100644 --- a/pywinauto/unittests/test_uiawrapper.py +++ b/pywinauto/unittests/test_uiawrapper.py @@ -1591,7 +1591,7 @@ class WindowWrapperTests(unittest.TestCase): def setUp(self): """Set some data and ensure the application is in the state we want""" _set_timings() - + test_folder = os.path.join(os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__)))), r"apps/MouseTester") self.qt5_app = os.path.join(test_folder, "mousebuttons.exe") From feb76010707e30e3df788f3198de7ea059094e92 Mon Sep 17 00:00:00 2001 From: Vasily Ryabov Date: Fri, 27 Jul 2018 01:20:56 +0300 Subject: [PATCH 5/6] Update release history. --- docs/HISTORY.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt index 56269965b..09736c002 100644 --- a/docs/HISTORY.txt +++ b/docs/HISTORY.txt @@ -2,6 +2,34 @@ Change Log ========== +0.6.5 Handling Privileges, AutomationID for Win32 etc. +-------------------------------------------------------------------- +27-July-2018 + +Enhancements: + * Check admin privileges for both target app and Python process. This + allows detecting cases when window messages won't work. + * Add ``automation_id`` and ``control_type`` properties + for "win32" backend (the most useful for WinForms). Correct + child_window() keywords are ``auto_id`` and ``control_type``. + * Switch pypiwin32 dependency to pywin32 which became official again. + * New generators ``iter_children()`` and ``iter_descendants()``. + * Add method ``is_checked()`` to "win32" check box. + +Bug Fixes: + * Method ``Application().connect(...)`` works better with ``timeout`` + argument. + * Fix ``.set_focus()`` for "uia" backend including minimized window case + (issue #443). + * ``maximize()/minimize()`` methods can be chained now. + * Fix passing keyword arguments to a function for decorators + ``@always_wait_until_passes`` and ``@always_wait_until``. + * Use correct types conversion for WaitGuiThreadIdle (issue #497). + * Fix reporting code coverage on Linux. + * Use .format() for logging BaseWrapper actions (issue #471). + * Print warning in case binary type is not determined (issue #387). + + 0.6.4 NULL pointer access fix and enhancements -------------------------------------------------------------------- 21-January-2018 From 4cf4192a4e877d3366f114a3ba1d5862689de587 Mon Sep 17 00:00:00 2001 From: Vasily Ryabov Date: Mon, 30 Jul 2018 11:09:28 +0300 Subject: [PATCH 6/6] Update release date. --- docs/HISTORY.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt index 09736c002..57d8472dd 100644 --- a/docs/HISTORY.txt +++ b/docs/HISTORY.txt @@ -4,7 +4,7 @@ Change Log 0.6.5 Handling Privileges, AutomationID for Win32 etc. -------------------------------------------------------------------- -27-July-2018 +30-July-2018 Enhancements: * Check admin privileges for both target app and Python process. This