Update to itb_screenshot and friends

This commit is contained in:
2024-12-27 17:07:19 +01:00
parent fe72969b4f
commit e0bc7a7159
15 changed files with 6739 additions and 552 deletions

View File

@@ -2,22 +2,17 @@
import sys
import os
import time
import random
import argparse
import json
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.mouse_button import MouseButton
from selenium.webdriver.common.by import By
def debug(msg: str):
"""Print debug message to stderr"""
print(f"DEBUG: {msg}", file=sys.stderr)
class ClickExecutor:
def __init__(self, debug_port: int):
debug(f"Connecting to Chrome on debug port {debug_port}")
def __init__(self, debug_port: int, verbose: bool = False):
"""Initialize connection to Chrome instance"""
self.verbose = verbose
options = webdriver.ChromeOptions()
options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}")
self.driver = webdriver.Chrome(options=options)
@@ -29,15 +24,15 @@ class ClickExecutor:
with open(os.path.join(js_dir, filename)) as f:
self.js_code += f.read() + "\n"
# Get initial window info
# Get window and viewport info
self.window_size = self.driver.get_window_size()
debug(f"Window size: {json.dumps(self.window_size)}")
self.viewport_size = self.get_viewport_size()
debug(f"Viewport size: {json.dumps(self.viewport_size)}")
self.scroll_position = self.get_scroll_position()
debug(f"Scroll position: {json.dumps(self.scroll_position)}")
def log(self, msg: str):
"""Log message if verbose mode is enabled"""
if self.verbose:
print(f"DEBUG: {msg}", file=sys.stderr)
def get_viewport_size(self):
"""Get viewport dimensions"""
@@ -58,37 +53,47 @@ class ClickExecutor:
max(0, x - self.viewport_size['width'] // 2),
max(0, y - self.viewport_size['height'] // 2)
)
# Wait for scroll to complete
time.sleep(0.5)
self.scroll_position = self.get_scroll_position()
debug(f"New scroll position: {json.dumps(self.scroll_position)}")
def get_element_at_position(self, x: int, y: int):
"""Get the element at the specified coordinates"""
return self.driver.execute_script(self.js_code + "; return getElementAtPosition(arguments[0], arguments[1]);", x, y)
return self.driver.execute_script(
self.js_code + "; return getElementAtPosition(arguments[0], arguments[1]);",
x, y
)
def execute_click(self, x: int, y: int, double: bool = False, right: bool = False):
"""Execute click operation with improved coordinate handling"""
def execute_click(self, x: int, y: int, double: bool = False, right: bool = False, element_id: str = None):
"""Execute click operation with improved handling"""
try:
debug(f"Attempting to click at coordinates ({x}, {y})")
debug(f"Click type: {'double' if double else 'right' if right else 'single'}")
self.log(f"Attempting {'double' if double else 'right' if right else 'single'} click at ({x}, {y})")
# First scroll to make target visible
self.scroll_to_target(x, y)
# Get the element at the target position
element = self.get_element_at_position(x, y)
if not element:
debug(f"No element found at coordinates ({x}, {y})")
return False
if element_id:
# Try to find element by ID first
try:
element = self.driver.find_element(By.ID, element_id)
self.log(f"Found element by ID: {element_id}")
except:
self.log(f"Failed to find element by ID: {element_id}")
return False
else:
# Scroll to make target visible
self.scroll_to_target(x, y)
# Try clicking the element directly
# Get element at position
element = self.get_element_at_position(x, y)
if not element:
self.log(f"No element found at coordinates ({x}, {y})")
return False
# Try clicking the element
try:
debug("Attempting to click element directly")
element = self.driver.execute_script("return arguments[0];", element)
actions = ActionChains(self.driver)
actions.move_to_element(element)
if element_id:
actions.move_to_element(element)
else:
actions.move_by_offset(x, y)
if right:
actions.context_click()
@@ -101,28 +106,29 @@ class ClickExecutor:
return True
except Exception as e:
debug(f"Direct element click failed: {str(e)}")
debug("Falling back to JavaScript click")
self.log(f"Direct click failed: {str(e)}")
self.log("Attempting JavaScript click fallback")
# Fallback to JavaScript click
try:
self.driver.execute_script("arguments[0].click();", element)
return True
except Exception as js_error:
debug(f"JavaScript click failed: {str(js_error)}")
self.log(f"JavaScript click failed: {str(js_error)}")
return False
except Exception as e:
debug(f"Click operation failed with error: {str(e)}")
self.log(f"Click operation failed: {str(e)}")
return False
def main():
parser = argparse.ArgumentParser(description='Execute mouse clicks in Chrome')
parser.add_argument('debug_port', type=int, help='Chrome debug port')
parser.add_argument('-x', type=int, required=True, help='X coordinate')
parser.add_argument('-y', type=int, required=True, help='Y coordinate')
parser.add_argument('-x', type=int, help='X coordinate')
parser.add_argument('-y', type=int, help='Y coordinate')
parser.add_argument('--double', action='store_true', help='Perform double click')
parser.add_argument('--right', action='store_true', help='Perform right click')
parser.add_argument('--id', help='Element ID to click')
parser.add_argument('-v', '--verbose', action='store_true', help='Enable debug output')
args = parser.parse_args()
@@ -130,15 +136,25 @@ def main():
print("Cannot specify both --double and --right", file=sys.stderr)
sys.exit(1)
if not args.id and (args.x is None or args.y is None):
print("Must specify either element ID or both x and y coordinates", file=sys.stderr)
sys.exit(1)
try:
debug("Initializing ClickExecutor")
executor = ClickExecutor(args.debug_port)
success = executor.execute_click(args.x, args.y, args.double, args.right)
executor = ClickExecutor(args.debug_port, args.verbose)
success = executor.execute_click(
x=args.x if args.x is not None else 0,
y=args.y if args.y is not None else 0,
double=args.double,
right=args.right,
element_id=args.id
)
sys.exit(0 if success else 1)
except Exception as e:
debug(f"Fatal error: {str(e)}")
if args.verbose:
print(f"Fatal error: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
main()