Skip to content

Commit

Permalink
Fix multi instances and add test_new_window_connection
Browse files Browse the repository at this point in the history
  • Loading branch information
dpratmarty committed Apr 8, 2024
1 parent dee2988 commit 6ae371f
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 22 deletions.
10 changes: 3 additions & 7 deletions pywinauto_recorder/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,12 @@ def get_sorted_region(elements, min_height=8, max_height=9999, min_width=8, max_
return h + 1, w, arrays


def find_window_candidates(root_entry):
def find_window_candidates(root_entry, handle=None):
"""
It returns a list of windows that match the given root entry
:return: A list of window candidates.
"""
handle = native_window_handle
title, control_type, _, _ = get_entry(root_entry)
if root_entry == "*":
title = None
Expand All @@ -319,9 +318,6 @@ def find_window_candidates(root_entry):
else:
window_candidates = desktop.windows(title=title, control_type=control_type,
visible_only=False, enabled_only=False, active_only=False, handle=handle)
if not window_candidates:
print("Warning: No window '" + title + "' with control type '" + control_type + "' found! ")
return None
return window_candidates


Expand Down Expand Up @@ -425,8 +421,8 @@ def find_elements(full_element_path=None):
:return: The elements found
"""
entry_list = get_entry_list(full_element_path)
window_candidates = find_window_candidates(entry_list[0])
if window_candidates is None:
window_candidates = find_window_candidates(entry_list[0], handle=native_window_handle)
if window_candidates == []:
return []
window_candidates = filter_window_candidates(window_candidates)
if len(entry_list) == 1 and len(window_candidates) == 1:
Expand Down
51 changes: 37 additions & 14 deletions pywinauto_recorder/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, MOUSEEVENTF_WHEEL, \
WHEEL_DELTA, SW_RESTORE,HWND_NOTOPMOST, HWND_TOPMOST
from .core import type_separator, path_separator, get_entry, get_entry_list, find_elements, get_sorted_region, \
get_wrapper_path, is_int, is_absolute_path, set_native_window_handle, get_native_window_handle
get_wrapper_path, is_int, is_absolute_path, set_native_window_handle, get_native_window_handle, find_window_candidates
from functools import partial, update_wrapper
from cachetools import func
import math
Expand Down Expand Up @@ -50,7 +50,7 @@ class FailedSearch(PywinautoRecorderException):
'drag_and_drop', 'middle_drag_and_drop', 'right_drag_and_drop', 'menu_click',
'mouse_wheel', 'send_keys', 'set_combobox', 'set_text', 'exists', 'select_file', 'playback',
'find_cache_clear', 'UIApplication', 'start_application', 'connect_application', 'focus_on_application',
'kill_application']
'find_main_windows', 'kill_application']


# TODO special_char_array in core for recorder.py and player.py (check when to call escape & unescape)
Expand Down Expand Up @@ -1094,32 +1094,55 @@ def connect_application(**kwargs):
:func:`pywinauto.findwindows.find_elements` - the keyword arguments that
are also can be used instead of **process**, **handle** or **path**
"""
app = pywinauto.Application(backend="win32")
if 'exclude_main_windows' in kwargs or 'main_window_uipath' in kwargs:
excluded_main_windows = kwargs['exclude_main_windows'] if 'exclude_main_windows' in kwargs else []
main_window_uipath = kwargs['main_window_uipath'] if 'main_window_uipath' in kwargs else '*'
timeout = kwargs['timeout'] if 'timeout' in kwargs else 20
main_windows = []
t0 = time.time()
t1 = t0
while len(main_windows) != 1 and t1-t0 < timeout:
main_windows = find_main_windows(main_window_uipath)
if main_windows:
excluded_handles = [w.handle for w in excluded_main_windows]
main_windows = [w for w in main_windows if w.handle not in excluded_handles]
time.sleep(1)
t1 = time.time()
if len(main_windows) == 1:
kwargs['handle'] = main_windows[0].handle
else:
raise FailedSearch("Window not found using args '", kwargs + "'")

app = pywinauto.Application(backend="uia")
app.connect(**kwargs)
top_window = app.top_window().wrapper_object()
native_window_handle = top_window.handle
return UIApplication(app, native_window_handle)


def focus_on_application(application=None):
def focus_on_application(application_or_window=None):
"""
Focuses on a specified application by bringing its main window to the foreground.
If 'application' is None, it clears the focus, allowing subsequent automation commands
to target any application without restrictions.
If 'application_or_window' is None, it clears the focus, allowing subsequent automation commands
to target any window without restrictions.
:param application: UIApplication object or None
The UIApplication object representing the application to focus on.
:param application_or_window: UIApplication object, UIAWrapper object of a main window or None
The object to focus on the main window.
If None, the focus is cleared.
"""
if application is None:
if application_or_window is None:
set_native_window_handle(None)
else:
set_native_window_handle(application.native_window_handle)
set_native_window_handle(application_or_window.native_window_handle)
time.sleep(1)
if win32gui_IsIconic(application.native_window_handle):
win32gui_ShowWindow(application.native_window_handle, SW_RESTORE)
win32gui_SetWindowPos(application.native_window_handle, HWND_TOPMOST, 0, 0, 0, 0, 3)
win32gui_SetWindowPos(application.native_window_handle, HWND_NOTOPMOST, 0, 0, 0, 0, 3)
if win32gui_IsIconic(application_or_window.native_window_handle):
win32gui_ShowWindow(application_or_window.native_window_handle, SW_RESTORE)
win32gui_SetWindowPos(application_or_window.native_window_handle, HWND_TOPMOST, 0, 0, 0, 0, 3)
win32gui_SetWindowPos(application_or_window.native_window_handle, HWND_NOTOPMOST, 0, 0, 0, 0, 3)


def find_main_windows(main_window_uipath='*'):
return find_window_candidates(main_window_uipath, handle=None)


def kill_application(application, timeout=10):
Expand Down
36 changes: 35 additions & 1 deletion tests/tests_Calculator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import os
from pywinauto_recorder.player import PlayerSettings, UIPath, load_dictionary, shortcut, \
from pywinauto_recorder.player import PlayerSettings, UIPath, load_dictionary, shortcut, find_main_windows, \
find, move, click, double_click, triple_click, move_window, start_application, focus_on_application, kill_application, connect_application
from pywinauto_recorder.recorder import Recorder
import time
Expand Down Expand Up @@ -203,3 +203,37 @@ def test_multi_instances():
kill_application(calculator_1)
kill_application(calculator_2)


def test_new_window_connection():
"""
Opens a new application within an application, navigating through UI elements,
and then connecting to the new window that opens as a result of a user action.
Steps:
1. Start the application "calc" (calculator).
2. Focus on the calculator application.
3. Click on the "Open Navigation" button.
4. Click on the "Settings" list item.
5. Record all main windows before clicking on a UI element.
6. Click on the "Send feedback" hyperlink.
7. Connect to the Feedback Hub window that opens after excluding previously recorded main windows.
8. Focus on the Feedback Hub application.
9. Resize the Feedback Hub window to 800x800 pixels.
10. Click on the "Close Feedback Hub" button.
"""
calculator = start_application("calc")
focus_on_application(calculator)
with UIPath("*"):
click("Open Navigation||Button")
click("Settings||ListItem")
all_main_windows_before_click = find_main_windows('*')
click("Send feedback||Hyperlink")
feedback_hub = connect_application(exclude_main_windows=all_main_windows_before_click, main_window_uipath="Feedback Hub||Window", timout=10)
focus_on_application(feedback_hub)
with UIPath("*"):
move_window(x=0, y=0, width=800, height=800)
click("Close Feedback Hub||Button")
kill_application(calculator)



0 comments on commit 6ae371f

Please sign in to comment.