Python UIAutomation for Windows - comprehensive library for automating Windows applications using Microsoft's UIAutomation framework
Comprehensive logging system with color support and control tree enumeration for debugging automation scripts and understanding application structure. These tools help developers troubleshoot automation issues and analyze UI hierarchies.
Main logging class providing various output methods with color support.
class Logger:
"""Comprehensive logging system for automation debugging."""
def Write(self, text: str) -> None:
"""
Write text to log without newline.
Args:
text: Text to write
"""
def WriteLine(self, text: str) -> None:
"""
Write text to log with newline.
Args:
text: Text to write
"""
def ColorfulWrite(self, text: str, color: int) -> None:
"""
Write colored text to log without newline.
Args:
text: Text to write
color: Console color constant
"""
def ColorfulWriteLine(self, text: str, color: int) -> None:
"""
Write colored text to log with newline.
Args:
text: Text to write
color: Console color constant
"""
def Log(self, message: str) -> None:
"""
Log message with timestamp.
Args:
message: Message to log
"""
def ColorfulLog(self, message: str, color: int) -> None:
"""
Log colored message with timestamp.
Args:
message: Message to log
color: Console color constant
"""
def SetLogFile(self, filename: str) -> None:
"""
Set output log file.
Args:
filename: Path to log file
"""
def Close(self) -> None:
"""Close log file."""class ConsoleColor:
"""Console color constants for colored logging."""
# Standard colors
Black: int = 0
DarkBlue: int = 1
DarkGreen: int = 2
DarkCyan: int = 3
DarkRed: int = 4
DarkMagenta: int = 5
DarkYellow: int = 6
Gray: int = 7
DarkGray: int = 8
Blue: int = 9
Green: int = 10
Cyan: int = 11
Red: int = 12
Magenta: int = 13
Yellow: int = 14
White: int = 15
class Logger:
# Color name to value mapping
ColorName2Value: dict = {
'Black': 0,
'DarkBlue': 1,
'DarkGreen': 2,
'DarkCyan': 3,
'DarkRed': 4,
'DarkMagenta': 5,
'DarkYellow': 6,
'Gray': 7,
'DarkGray': 8,
'Blue': 9,
'Green': 10,
'Cyan': 11,
'Red': 12,
'Magenta': 13,
'Yellow': 14,
'White': 15
}Global functions for logging control information and hierarchies.
def LogControl(control: Control) -> None:
"""
Log detailed information about a control.
Args:
control: Control to log information for
"""
def EnumAndLogControl(control: Control, depth: int = 1, showAllControls: bool = True) -> None:
"""
Enumerate and log control hierarchy.
Args:
control: Root control to start enumeration from
depth: Maximum depth to enumerate (-1 for unlimited)
showAllControls: Whether to show all controls or just visible ones
"""
def EnumAndLogControlAncestors(control: Control) -> None:
"""
Log all ancestor controls of the given control.
Args:
control: Control to log ancestors for
"""def ShowDesktop(waitTime: float = 1) -> None:
"""
Show the desktop (minimize all windows).
Args:
waitTime: Time to wait after showing desktop
"""
def RunWithHotKey(func: callable, hotkey: str) -> None:
"""
Run a function when a hotkey is pressed.
Args:
func: Function to execute
hotkey: Hotkey combination (e.g., 'ctrl+f1')
"""
def WaitHotKeyReleased(hotkey: str, timeout: float = 15) -> bool:
"""
Wait for a hotkey to be released.
Args:
hotkey: Hotkey combination
timeout: Maximum wait time in seconds
Returns:
bool: True if hotkey was released, False if timeout
"""Global debug settings for controlling logging behavior.
# Debug flags
DEBUG_SEARCH_TIME: bool = False # Log search timing information
DEBUG_EXIST_DISAPPEAR: bool = False # Log control existence checking
# Configuration functions
def SetDebugSearchTime(enabled: bool) -> None:
"""Enable/disable search time debugging."""
def SetDebugExistDisappear(enabled: bool) -> None:
"""Enable/disable existence checking debugging."""import uiautomation
# Create logger instance
logger = uiautomation.Logger()
# Basic text logging
logger.WriteLine("Starting automation script")
logger.Write("Processing... ")
logger.WriteLine("Done")
# Colored logging
logger.ColorfulWriteLine("Success!", uiautomation.ConsoleColor.Green)
logger.ColorfulWriteLine("Warning: Check configuration", uiautomation.ConsoleColor.Yellow)
logger.ColorfulWriteLine("Error: Failed to connect", uiautomation.ConsoleColor.Red)
# Timestamped logging
logger.Log("Script execution started")
logger.ColorfulLog("Critical error occurred", uiautomation.ConsoleColor.Red)# Set up file logging
logger = uiautomation.Logger()
logger.SetLogFile("automation_log.txt")
# Log to file
logger.WriteLine("This will be written to the file")
logger.ColorfulWriteLine("Colored text in file", uiautomation.ConsoleColor.Blue)
# Close file when done
logger.Close()# Log detailed information about a specific control
button = uiautomation.ButtonControl(Name='Submit')
if button.Exists():
uiautomation.LogControl(button)
# Log information about the focused control
focused = uiautomation.GetFocusedControl()
if focused:
uiautomation.LogControl(focused)# Enumerate and log entire application window
app_window = uiautomation.WindowControl(Name='Calculator')
if app_window.Exists():
# Log all controls in the window (unlimited depth)
uiautomation.EnumAndLogControl(app_window, depth=-1)
# Enumerate with limited depth
desktop = uiautomation.GetRootControl()
uiautomation.EnumAndLogControl(desktop, depth=2) # Only 2 levels deep
# Show only visible controls
visible_only = False # Set to True to show only visible controls
uiautomation.EnumAndLogControl(app_window, depth=3, showAllControls=visible_only)# Find a specific control and log its ancestry
text_field = uiautomation.EditControl(Name='Username')
if text_field.Exists():
uiautomation.EnumAndLogControlAncestors(text_field)
# This will show: Desktop -> Window -> Pane -> Group -> EditControl# Enable debug logging for performance analysis
uiautomation.SetDebugSearchTime(True)
uiautomation.SetDebugExistDisappear(True)
# Now control searches will log timing information
button = uiautomation.ButtonControl(Name='Click Me')
button.Click() # This will log search time and existence checks
# Disable debug logging
uiautomation.SetDebugSearchTime(False)
uiautomation.SetDebugExistDisappear(False)def debug_automation_step(step_name, func, *args, **kwargs):
"""Wrapper function to debug automation steps."""
logger = uiautomation.Logger()
logger.ColorfulWriteLine(f"Starting step: {step_name}", uiautomation.ConsoleColor.Cyan)
try:
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
logger.ColorfulWriteLine(
f"Step completed in {end_time - start_time:.2f}s: {step_name}",
uiautomation.ConsoleColor.Green
)
return result
except Exception as e:
logger.ColorfulWriteLine(
f"Step failed: {step_name} - {str(e)}",
uiautomation.ConsoleColor.Red
)
raise
# Use the debug wrapper
def click_submit_button():
button = uiautomation.ButtonControl(Name='Submit')
button.Click()
debug_automation_step("Click Submit Button", click_submit_button)def debug_control_search(control_type, **search_criteria):
"""Debug control search operations."""
logger = uiautomation.Logger()
# Log search criteria
criteria_str = ", ".join([f"{k}='{v}'" for k, v in search_criteria.items()])
logger.ColorfulWriteLine(f"Searching for {control_type.__name__} with {criteria_str}",
uiautomation.ConsoleColor.Yellow)
# Perform search
control = control_type(**search_criteria)
if control.Exists():
logger.ColorfulWriteLine("Control found!", uiautomation.ConsoleColor.Green)
# Log control details
uiautomation.LogControl(control)
return control
else:
logger.ColorfulWriteLine("Control not found!", uiautomation.ConsoleColor.Red)
# Try to find similar controls for debugging
logger.WriteLine("Looking for similar controls...")
root = uiautomation.GetRootControl()
uiautomation.EnumAndLogControl(root, depth=5, showAllControls=True)
return None
# Debug a control search
found_button = debug_control_search(
uiautomation.ButtonControl,
Name='Submit',
ClassName='Button'
)def analyze_application_structure(app_name):
"""Analyze and log the structure of an application."""
logger = uiautomation.Logger()
logger.SetLogFile(f"{app_name}_structure.log")
# Find application window
app_window = uiautomation.WindowControl(Name=app_name)
if not app_window.Exists():
logger.ColorfulWriteLine(f"Application '{app_name}' not found", uiautomation.ConsoleColor.Red)
return
logger.ColorfulWriteLine(f"Analyzing structure of '{app_name}'", uiautomation.ConsoleColor.Cyan)
# Log basic window information
logger.WriteLine(f"Window Handle: {app_window.Handle}")
logger.WriteLine(f"Process ID: {app_window.ProcessId}")
logger.WriteLine(f"Class Name: {app_window.ClassName}")
logger.WriteLine(f"Bounds: {app_window.BoundingRectangle}")
# Enumerate all controls
logger.WriteLine("\n=== COMPLETE CONTROL HIERARCHY ===")
uiautomation.EnumAndLogControl(app_window, depth=-1)
logger.Close()
logger.WriteLine(f"Structure analysis saved to {app_name}_structure.log")
# Analyze Calculator application
analyze_application_structure("Calculator")def setup_debug_hotkey():
"""Set up hotkey for interactive debugging."""
def debug_current_control():
logger = uiautomation.Logger()
logger.ColorfulWriteLine("=== DEBUG HOTKEY PRESSED ===", uiautomation.ConsoleColor.Magenta)
# Get control under cursor
cursor_control = uiautomation.ControlFromCursor()
if cursor_control:
logger.WriteLine("Control under cursor:")
uiautomation.LogControl(cursor_control)
logger.WriteLine("\nControl ancestors:")
uiautomation.EnumAndLogControlAncestors(cursor_control)
else:
logger.ColorfulWriteLine("No control found under cursor", uiautomation.ConsoleColor.Red)
# Set up Ctrl+F12 as debug hotkey
uiautomation.RunWithHotKey(debug_current_control, 'ctrl+f12')
print("Debug hotkey (Ctrl+F12) is active. Press it while hovering over controls to debug.")
# Activate debug hotkey
setup_debug_hotkey()def parse_automation_log(log_file):
"""Parse and analyze automation log files."""
logger = uiautomation.Logger()
try:
with open(log_file, 'r') as f:
lines = f.readlines()
logger.ColorfulWriteLine(f"Analyzing log file: {log_file}", uiautomation.ConsoleColor.Cyan)
logger.WriteLine(f"Total lines: {len(lines)}")
# Count different types of entries
error_count = sum(1 for line in lines if 'error' in line.lower())
warning_count = sum(1 for line in lines if 'warning' in line.lower())
logger.WriteLine(f"Errors found: {error_count}")
logger.WriteLine(f"Warnings found: {warning_count}")
# Show errors and warnings
if error_count > 0:
logger.ColorfulWriteLine("\nErrors:", uiautomation.ConsoleColor.Red)
for line in lines:
if 'error' in line.lower():
logger.ColorfulWriteLine(f" {line.strip()}", uiautomation.ConsoleColor.Red)
if warning_count > 0:
logger.ColorfulWriteLine("\nWarnings:", uiautomation.ConsoleColor.Yellow)
for line in lines:
if 'warning' in line.lower():
logger.ColorfulWriteLine(f" {line.strip()}", uiautomation.ConsoleColor.Yellow)
except FileNotFoundError:
logger.ColorfulWriteLine(f"Log file not found: {log_file}", uiautomation.ConsoleColor.Red)
# Analyze a log file
parse_automation_log("automation_log.txt")Install with Tessl CLI
npx tessl i tessl/pypi-uiautomation