Skip to content

Commit

Permalink
Merge pull request pywinauto#886 from airelil/atspi
Browse files Browse the repository at this point in the history
issue pywinauto#852, issue pywinauto#547, typos
  • Loading branch information
vasily-v-ryabov authored Feb 25, 2020
2 parents 5832acf + 59f550d commit 052c810
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 43 deletions.
8 changes: 7 additions & 1 deletion pywinauto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,19 @@ def _get_com_threading_mode(module_sys):
sys.coinit_flags = _get_com_threading_mode(sys)


#=========================================================================
class WindowNotFoundError(Exception):

"""No window could be found"""
pass

from . import findwindows

WindowAmbiguousError = findwindows.WindowAmbiguousError
WindowNotFoundError = findwindows.WindowNotFoundError
ElementNotFoundError = findwindows.ElementNotFoundError
ElementAmbiguousError = findwindows.ElementAmbiguousError


from . import findbestmatch
from . import backend as backends

Expand Down
3 changes: 2 additions & 1 deletion pywinauto/base_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ def client_to_screen(self, client_point):
# Use a direct call to element_info.rectangle instead of self.rectangle
# because the latter can be overriden in one of derived wrappers
# (see _treeview_element.rectangle or _listview_item.rectangle)
raise NotImplementedError()
rect = self.element_info.rectangle
return (client_point[0] + rect.left, client_point[1] + rect.top)

#-----------------------------------------------------------
def process_id(self):
Expand Down
2 changes: 1 addition & 1 deletion pywinauto/controls/atspi_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def is_expanded(self):
return self.children()[0].is_visible()

def texts(self):
"""Get texts of all items in in the control as array"""
"""Get texts of all items in the control as list"""
combo_box_container = self.children()[0]
texts = []
for el in combo_box_container.children():
Expand Down
14 changes: 9 additions & 5 deletions pywinauto/controls/hwndwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
from .. import handleprops
from ..windows.win32_element_info import HwndElementInfo
from .. import backend
from .. import WindowNotFoundError # noqa #E402

# I leave this optional because PIL is a large dependency
try:
Expand Down Expand Up @@ -1165,11 +1166,14 @@ def has_closed():

# Keep waiting until both this control and it's parent
# are no longer valid controls
timings.wait_until(
wait_time,
Timings.closeclick_retry,
has_closed
)
try:
timings.wait_until(
wait_time,
Timings.closeclick_retry,
has_closed
)
except timings.TimeoutError:
raise WindowNotFoundError

self.actions.log('Closed window "{0}"'.format(window_text))
# Non PEP-8 alias
Expand Down
6 changes: 5 additions & 1 deletion pywinauto/controls/uiawrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import comtypes

from .. import backend
from .. import WindowNotFoundError # noqa #E402
from ..timings import Timings
from .win_base_wrapper import WinBaseWrapper
from ..base_wrapper import BaseMeta
Expand Down Expand Up @@ -436,7 +437,10 @@ def close(self):
if name and control_type:
self.actions.log("Closed " + control_type.lower() + ' "' + name + '"')
except(uia_defs.NoPatternInterfaceError):
self.type_keys("{ESC}")
try:
self.type_keys("{ESC}")
except comtypes.COMError:
raise WindowNotFoundError

# -----------------------------------------------------------
def minimize(self):
Expand Down
12 changes: 0 additions & 12 deletions pywinauto/controls/win_base_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,6 @@ class WinBaseWrapper(BaseWrapper):
def __new__(cls, element_info):
return WinBaseWrapper._create_wrapper(cls, element_info, WinBaseWrapper)

#------------------------------------------------------------
def client_to_screen(self, client_point):
"""Maps point from client to screen coordinates"""
# Use a direct call to element_info.rectangle instead of self.rectangle
# because the latter can be overriden in one of derived wrappers
# (see _treeview_element.rectangle or _listview_item.rectangle)
rect = self.element_info.rectangle
if isinstance(client_point, win32structures.POINT):
return (client_point.x + rect.left, client_point.y + rect.top)
else:
return (client_point[0] + rect.left, client_point[1] + rect.top)

#-----------------------------------------------------------
def draw_outline(
self,
Expand Down
7 changes: 1 addition & 6 deletions pywinauto/findwindows.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,13 @@
import six

from . import findbestmatch
from . import WindowNotFoundError
from . import controls
from .backend import registry


# TODO: we should filter out invalid elements before returning

#=========================================================================
class WindowNotFoundError(Exception):

"""No window could be found"""
pass


#=========================================================================
class WindowAmbiguousError(Exception):
Expand Down
40 changes: 30 additions & 10 deletions pywinauto/linux/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

from ..backend import registry
from ..base_application import AppStartError, ProcessNotFoundError, AppNotConnected, BaseApplication
from ..timings import Timings # noqa: E402


class Application(BaseApplication):
Expand All @@ -48,16 +49,16 @@ def __init__(self, backend="atspi", allow_magic_lookup=True):
Initialize the Application object
* **backend** is a name of used back-end (values: "atspi").
* **allow_magic_lookup** whether attribute access must turn into
child_window(best_match=...) search as fallback
* **allow_magic_lookup** whether attribute access must turn into
child_window(best_match=...) search as fallback
"""
self.process = None
self.xmlpath = ''

self._proc_descriptor = None
self.match_history = []
self.use_history = False
self.actions = None # TODO Action logger for linux
self.actions = None # TODO Action logger for linux
if backend not in registry.backends:
raise ValueError('Backend "{0}" is not registered!'.format(backend))
self.backend = registry.backends[backend]
Expand Down Expand Up @@ -120,13 +121,32 @@ def cpu_usage(self, interval=None):
if not self.process:
raise AppNotConnected("Please use start or connect before trying "
"anything else")
if interval:
time.sleep(interval)
proc_pid_stat = "/proc/{}/stat".format(self.process)

def read_cpu_info():
with open(proc_pid_stat, 'r') as s:
pid_info = s.read().split()
with open("/proc/stat") as s:
info = s.read().split()
# return a tuple as following:
# pid utime, pid stime, total utime, total stime
return (int(pid_info[13]), int(pid_info[14]), int(info[1]), int(info[3]))

try:
proc_info = subprocess.check_output(["ps", "-p", str(self.process), "-o", "%cpu"], universal_newlines=True)
proc_info = proc_info.split("\n")
return float(proc_info[1])
except Exception:
before = read_cpu_info()
if not interval:
interval = Timings.cpu_usage_interval
time.sleep(interval)
after = read_cpu_info()
pid_time = (after[0] - before[0]) + (after[1] - before[1])
sys_time = (after[2] - before[2]) + (after[3] - before[3])
if not sys_time:
res = 0.0
else:
res = 100.0 * (float(pid_time) / float(sys_time))
return res

except IOError:
raise ProcessNotFoundError()

def kill(self, soft=False):
Expand All @@ -146,7 +166,7 @@ def kill(self, soft=False):
self._proc_descriptor = None

if not self.is_process_running():
return True # already closed
return True # already closed
status = subprocess.check_output(["kill", "-9", str(self.process)], universal_newlines=True)
if "Operation not permitted" in status:
raise Exception("Cannot kill process: {}".format(status))
Expand Down
20 changes: 16 additions & 4 deletions pywinauto/unittests/test_application_linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import time

sys.path.append(".")
from pywinauto.linux.application import Application, AppStartError, AppNotConnected
from pywinauto.application import WindowSpecification # noqa: E402
if sys.platform.startswith('linux'):
from pywinauto.controls import atspiwrapper # register atspi backend
from pywinauto.linux.application import Application # noqa: E402
from pywinauto.linux.application import AppStartError # noqa: E402
from pywinauto.linux.application import AppNotConnected # noqa: E402
from pywinauto.linux.application import ProcessNotFoundError # noqa: E402

app_name = r"gtk_example.py"

Expand Down Expand Up @@ -77,10 +80,19 @@ def test_connect_by_path(self):
self.app.connect(path=_test_app())
self.assertEqual(self.app.process, self.subprocess_app.pid)

def test_get_cpu_usage(self):
def test_cpu_usage(self):
self.app.start(_test_app())
time.sleep(1)
self.assertGreater(self.app.cpu_usage(), 0)
self.assertGreater(self.app.cpu_usage(0.1), 0)
self.app.wait_cpu_usage_lower(threshold=0.1, timeout=2.9, usage_interval=0.2)
# default timings
self.assertEqual(self.app.cpu_usage(), 0)

# non-existing process
self.app.kill()
self.assertRaises(ProcessNotFoundError, self.app.cpu_usage, 7.8)

# not connected or not started app
self.assertRaises(AppNotConnected, Application().cpu_usage, 12.3)

def test_is_process_running(self):
self.app.start(_test_app())
Expand Down
10 changes: 8 additions & 2 deletions pywinauto/unittests/test_atspi_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@

import os
import sys
import time
import unittest

if sys.platform.startswith("linux"):
Expand All @@ -43,7 +42,7 @@
from pywinauto.linux.application import Application
from pywinauto.controls.atspiwrapper import AtspiWrapper
from pywinauto.linux.atspi_objects import IATSPI
from pywinauto import mouse
from pywinauto.linux.atspi_objects import POINT

app_name = r"gtk_example.py"

Expand Down Expand Up @@ -130,6 +129,13 @@ def test_can_get_rectangle(self):
rect = self.app_frame.Icon.rectangle()
self.assertAlmostEqual(rect.height(), 26, delta=2)

def test_client_to_screen(self):
rect = self.app_wrapper.rectangle()
self.assertEqual(self.app_wrapper.client_to_screen((0, 0)),
(rect.left, rect.top))
self.assertEqual(self.app_wrapper.client_to_screen(POINT(20, 20)),
(rect.left + 20, rect.top + 20))

def test_can_get_process_id(self):
self.assertEqual(self.app_wrapper.process_id(), self.app.process)

Expand Down
9 changes: 9 additions & 0 deletions pywinauto/unittests/test_hwndwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
from pywinauto.base_wrapper import ElementNotVisible # noqa E402
from pywinauto import findbestmatch # noqa E402
from pywinauto import keyboard # noqa E402
from pywinauto import timings # noqa E402
from pywinauto import WindowNotFoundError # noqa E402


mfc_samples_folder = os.path.join(
Expand Down Expand Up @@ -103,6 +105,13 @@ def tearDown(self):
#self.dlg.close()
self.app.kill()

def test_close_not_found(self):
"""Test dialog close handle non existing window"""
wrp = self.dlg.wrapper_object()
with mock.patch.object(timings, 'wait_until') as mock_wait_until:
mock_wait_until.side_effect = timings.TimeoutError
self.assertRaises(WindowNotFoundError, wrp.close)

def test_scroll(self):
"""Test control scrolling"""
self.dlg.TabControl.select('CNetworkAddressCtrl')
Expand Down
24 changes: 24 additions & 0 deletions pywinauto/unittests/test_uiawrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
from pywinauto.actionlogger import ActionLogger # noqa: E402
from pywinauto import Desktop
from pywinauto import mouse # noqa: E402
from pywinauto import WindowNotFoundError # noqa: E402
if UIA_support:
import comtypes
import pywinauto.windows.uia_defines as uia_defs
import pywinauto.controls.uia_controls as uia_ctls
from pywinauto.controls.uiawrapper import UIAWrapper

wpf_samples_folder = os.path.join(
os.path.dirname(__file__), r"..\..\apps\WPF_samples")
Expand Down Expand Up @@ -178,6 +180,28 @@ def test_is_dialog(self):
name="OK").wrapper_object()
self.assertEqual(button.is_dialog(), False)

def test_close(self):
"""Test close method of a control"""
wrp = self.dlg.wrapper_object()

# mock a failure in get_elem_interface() method only for 'Window' param
orig_get_elem_interface = uia_defs.get_elem_interface
with mock.patch.object(uia_defs, 'get_elem_interface') as mock_get_iface:
def side_effect(elm_info, ptrn_name):
if ptrn_name == "Window":
raise uia_defs.NoPatternInterfaceError()
else:
return orig_get_elem_interface(elm_info, ptrn_name)
mock_get_iface.side_effect=side_effect
# also mock a failure in type_keys() method
with mock.patch.object(UIAWrapper, 'type_keys') as mock_type_keys:
exception_err = comtypes.COMError(-2147220991, 'An event was unable to invoke any of the subscribers', ())
mock_type_keys.side_effect = exception_err
self.assertRaises(WindowNotFoundError, self.dlg.close)

self.dlg.close()
self.assertEqual(self.dlg.exists(), False)

def test_parent(self):
"""Test getting a parent of a control"""
button = self.dlg.Alpha.wrapper_object()
Expand Down

0 comments on commit 052c810

Please sign in to comment.