diff --git a/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py b/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py index 8beefa530..3206ee01b 100644 --- a/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py +++ b/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py @@ -5,6 +5,7 @@ desktop, rest, selenium, + playwright, utility, windows, linux, @@ -20,6 +21,7 @@ desktop, rest, selenium, + playwright, utility, windows, linux, diff --git a/Framework/Built_In_Automation/Sequential_Actions/action_declarations/playwright.py b/Framework/Built_In_Automation/Sequential_Actions/action_declarations/playwright.py new file mode 100644 index 000000000..d0aca46ac --- /dev/null +++ b/Framework/Built_In_Automation/Sequential_Actions/action_declarations/playwright.py @@ -0,0 +1,101 @@ +""" +Playwright Action Declarations + +Defines all available Playwright actions for the Zeuz Node framework. +Users can switch from Selenium to Playwright by changing "selenium action" to "playwright action" +in their test steps while keeping all other parameters the same. + +Author: Zeuz/AutomationSolutionz +""" + +declarations = ( + # Browser Management + { "name": "open browser", "function": "Open_Browser", "screenshot": "web" }, + { "name": "go to link", "function": "Go_To_Link", "screenshot": "web" }, + { "name": "tear down browser", "function": "Tear_Down_Playwright", "screenshot": "none" }, + { "name": "teardown", "function": "Tear_Down_Playwright", "screenshot": "none" }, + { "name": "switch browser", "function": "Switch_Browser", "screenshot": "none" }, + + # Click Actions + { "name": "click", "function": "Click_Element", "screenshot": "web" }, + { "name": "double click", "function": "Double_Click_Element", "screenshot": "web" }, + { "name": "right click", "function": "Right_Click_Element", "screenshot": "web" }, + { "name": "hover", "function": "Hover_Over_Element", "screenshot": "web" }, + + # Text Input + { "name": "text", "function": "Enter_Text_In_Text_Box", "screenshot": "web" }, + { "name": "keystroke keys", "function": "Keystroke_For_Element", "screenshot": "web" }, + { "name": "keystroke chars", "function": "Keystroke_For_Element", "screenshot": "web" }, + + # Validation + { "name": "validate full text", "function": "Validate_Text", "screenshot": "web" }, + { "name": "validate partial text", "function": "Validate_Text", "screenshot": "web" }, + { "name": "if element exists", "function": "if_element_exists", "screenshot": "web" }, + + # Element Information + { "name": "save attribute", "function": "Save_Attribute", "screenshot": "web" }, + { "name": "get element info", "function": "get_element_info", "screenshot": "web" }, + { "name": "extract table data", "function": "Extract_Table_Data", "screenshot": "web" }, + + # Navigation + { "name": "navigate", "function": "Navigate", "screenshot": "web" }, + { "name": "get current url", "function": "Get_Current_URL", "screenshot": "none" }, + + # Scrolling + { "name": "scroll", "function": "Scroll", "screenshot": "web" }, + { "name": "scroll to element", "function": "scroll_to_element", "screenshot": "web" }, + { "name": "scroll element to top", "function": "scroll_to_element", "screenshot": "web" }, + + # Selection (Dropdowns/Checkboxes) + { "name": "select by visible text", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "deselect by visible text", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "select by value", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "deselect by value", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "select by index", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "deselect by index", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "deselect all", "function": "Select_Deselect", "screenshot": "web" }, + { "name": "check uncheck", "function": "check_uncheck", "screenshot": "web" }, + + # Window/Tab Management + { "name": "switch window", "function": "switch_window_or_tab", "screenshot": "web" }, + { "name": "switch window or frame", "function": "switch_window_or_tab", "screenshot": "web" }, + { "name": "switch window/tab", "function": "switch_window_or_tab", "screenshot": "web" }, + { "name": "open new tab", "function": "open_new_tab", "screenshot": "web" }, + { "name": "close tab", "function": "close_tab", "screenshot": "web" }, + + # iframe Handling + { "name": "switch iframe", "function": "switch_iframe", "screenshot": "web" }, + + # Alerts/Dialogs + { "name": "handle alert", "function": "Handle_Browser_Alert", "screenshot": "desktop" }, + + # Drag and Drop + { "name": "drag and drop", "function": "drag_and_drop", "screenshot": "web" }, + + # Screenshots + { "name": "take screenshot web", "function": "take_screenshot_playwright", "screenshot": "web" }, + + # JavaScript + { "name": "execute javascript", "function": "execute_javascript", "screenshot": "web" }, + + # File Upload + { "name": "upload file", "function": "upload_file", "screenshot": "web" }, + + # Window Management + { "name": "resize window", "function": "resize_window", "screenshot": "web" }, + + # Wait Actions + { "name": "wait for element", "function": "Wait_For_Element", "screenshot": "web" }, + + # Tracing/Performance (Playwright-specific) + { "name": "start tracing", "function": "Start_Tracing", "screenshot": "none" }, + { "name": "stop tracing", "function": "Stop_Tracing", "screenshot": "none" }, + + # Network Interception (Playwright-specific) + { "name": "intercept network", "function": "Intercept_Network", "screenshot": "none" }, +) # yapf: disable + +module_name = "playwright" + +for dec in declarations: + dec["module"] = module_name diff --git a/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py b/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py index 7a6fcc3cb..4ff0044c8 100755 --- a/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py +++ b/Framework/Built_In_Automation/Sequential_Actions/sequential_actions.py @@ -121,6 +121,11 @@ def load_sa_modules( from Framework.Built_In_Automation.Web.Selenium import ( BuiltInFunctions as selenium, ) + elif module == "playwright": + global playwright + from Framework.Built_In_Automation.Web.Playwright import ( + BuiltInFunctions as playwright, + ) elif module == "rest": global rest from Framework.Built_In_Automation.Web.REST import BuiltInFunctions as rest diff --git a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py new file mode 100644 index 000000000..b13120e32 --- /dev/null +++ b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py @@ -0,0 +1,2566 @@ +# -*- coding: utf-8 -*- +""" +Playwright Built-In Functions for Zeuz Node + +This module provides web automation actions using Playwright as an alternative +to Selenium. All actions follow the same step_data format and parameter patterns +as Selenium actions for seamless migration. + +Key Benefits over Selenium: +- Faster execution (WebSocket vs HTTP) +- Built-in auto-wait (no manual WebDriverWait) +- Modern selector support (test-id, role, text) +- Better iframe and shadow DOM handling +- Video recording and tracing support + +Usage: + Change "selenium action" to "playwright action" in test steps. + All element parameters work identically. + +Author: Zeuz/AutomationSolutionz +""" + +import sys +import os +import inspect +import time +import re +from pathlib import Path + +from playwright.sync_api import ( + sync_playwright, + Page, + Browser, + BrowserContext, + Locator, + TimeoutError as PlaywrightTimeoutError, + Error as PlaywrightError, +) + +from Framework.Utilities import CommonUtil +from Framework.Utilities.decorators import logger +from Framework.Built_In_Automation.Shared_Resources import ( + BuiltInFunctionSharedResources as sr, +) +from Framework.Utilities.CommonUtil import passed_tag_list, failed_tag_list +from . import locator as PlaywrightLocator + +######################### +# # +# Global Variables # +# # +######################### + +MODULE_NAME = inspect.getmodulename(__file__) + +# Playwright instances +playwright_instance = None +browser: Browser = None +context: BrowserContext = None +current_page: Page = None + +# Multi-page/context support +playwright_details = {} # {"page_id": {"page": Page, "context": Context, "browser": Browser}} +current_page_id = None + +# Default settings +default_timeout = 30000 # 30 seconds +default_viewport = {"width": 1920, "height": 1080} + + +######################### +# # +# Browser Management # +# # +######################### + +@logger +def Open_Browser(step_data): + """ + Launch a new browser instance with Playwright. + + Example 1 - Basic: + Field Sub Field Value + go to link input parameter https://example.com + open browser playwright action open browser + + Example 2 - With options: + Field Sub Field Value + go to link input parameter https://example.com + browser input parameter chrome + headless optional parameter false + resolution optional parameter 1920,1080 + timeout optional parameter 60 + add argument optional parameter --disable-gpu + open browser playwright action open browser + + Supported browsers: chrome, chromium, firefox, webkit, safari, edge + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global playwright_instance, browser, context, current_page + global current_page_id, playwright_details, default_timeout + + try: + # Parse parameters + url = None + browser_name = "chromium" + headless = True + viewport = default_viewport.copy() + args = [] + timeout = default_timeout + slow_mo = 0 + devtools = False + downloads_path = None + record_video = False + video_dir = None + locale = None + timezone = None + geolocation = None + permissions = [] + color_scheme = None + page_id = "default" + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("go to link", "url", "link"): + url = right_v + elif left_l in ("browser", "browser name"): + browser_name = right_v.lower() + elif left_l in ("driver id", "page id", "driver tag"): + page_id = right_v + + elif mid_l == "optional parameter": + if left_l == "headless": + headless = right_v.lower() in ("true", "yes", "1") + elif left_l == "resolution": + parts = right_v.replace("x", ",").split(",") + viewport = {"width": int(parts[0].strip()), "height": int(parts[1].strip())} + elif left_l in ("timeout", "wait time to page load", "page load timeout"): + timeout = int(float(right_v) * 1000) + elif left_l in ("add argument", "arg", "argument"): + args.append(right_v) + elif left_l == "slow mo": + slow_mo = int(float(right_v)) + elif left_l == "devtools": + devtools = right_v.lower() in ("true", "yes", "1") + elif left_l in ("downloads path", "download folder"): + downloads_path = right_v + elif left_l == "record video": + record_video = right_v.lower() in ("true", "yes", "1") + elif left_l == "video dir": + video_dir = right_v + elif left_l == "locale": + locale = right_v + elif left_l == "timezone": + timezone = right_v + elif left_l == "color scheme": + color_scheme = right_v + elif left_l == "permission": + permissions.append(right_v) + + elif mid_l == "shared capability": + # Handle Selenium-style capabilities where possible + pass + + # Launch Playwright + CommonUtil.ExecLog(sModuleInfo, f"Launching Playwright with {browser_name} browser", 1) + playwright_instance = sync_playwright().start() + + # Browser launch options + launch_options = { + "headless": headless, + "slow_mo": slow_mo, + "devtools": devtools, + } + if args: + launch_options["args"] = args + if downloads_path: + launch_options["downloads_path"] = downloads_path + + # Select and launch browser + if browser_name in ("chrome", "chromium"): + browser = playwright_instance.chromium.launch(**launch_options) + elif browser_name == "firefox": + browser = playwright_instance.firefox.launch(**launch_options) + elif browser_name in ("webkit", "safari"): + browser = playwright_instance.webkit.launch(**launch_options) + elif browser_name in ("edge", "msedge", "microsoft edge"): + launch_options["channel"] = "msedge" + browser = playwright_instance.chromium.launch(**launch_options) + elif browser_name == "chrome-beta": + launch_options["channel"] = "chrome-beta" + browser = playwright_instance.chromium.launch(**launch_options) + else: + CommonUtil.ExecLog(sModuleInfo, f"Unknown browser '{browser_name}', using chromium", 2) + browser = playwright_instance.chromium.launch(**launch_options) + + # Context options + context_options = {"viewport": viewport} + if record_video: + context_options["record_video_dir"] = video_dir or "videos/" + if locale: + context_options["locale"] = locale + if timezone: + context_options["timezone_id"] = timezone + if geolocation: + context_options["geolocation"] = geolocation + if permissions: + context_options["permissions"] = permissions + if color_scheme: + context_options["color_scheme"] = color_scheme + + # Create context and page + context = browser.new_context(**context_options) + context.set_default_timeout(timeout) + current_page = context.new_page() + current_page_id = page_id + + # Store in details + playwright_details[page_id] = { + "page": current_page, + "context": context, + "browser": browser, + "playwright": playwright_instance, + } + + # Navigate if URL provided + if url: + current_page.goto(url, wait_until="domcontentloaded") + CommonUtil.ExecLog(sModuleInfo, f"Navigated to: {url}", 1) + + # Save to shared variables for compatibility + sr.Set_Shared_Variables("playwright_page", current_page) + sr.Set_Shared_Variables("playwright_context", context) + sr.Set_Shared_Variables("playwright_browser", browser) + sr.Set_Shared_Variables("element_wait", timeout / 1000) # In seconds + + CommonUtil.ExecLog(sModuleInfo, f"Browser opened successfully (page_id: {page_id})", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Go_To_Link(step_data): + """ + Navigate to a URL. + + Example: + Field Sub Field Value + go to link input parameter https://example.com + wait until optional parameter networkidle + go to link playwright action go to link + + wait until options: load, domcontentloaded, networkidle, commit + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open. Use 'open browser' first.", 3) + return "zeuz_failed" + + url = None + wait_until = "domcontentloaded" + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("go to link", "url", "link"): + url = right_v + elif mid_l == "optional parameter": + if left_l in ("wait until", "wait_until", "waituntil"): + wait_until = right_v.lower() + elif left_l == "timeout": + timeout = int(float(right_v) * 1000) + + if not url: + CommonUtil.ExecLog(sModuleInfo, "No URL provided", 3) + return "zeuz_failed" + + goto_options = {"wait_until": wait_until} + if timeout: + goto_options["timeout"] = timeout + + current_page.goto(url, **goto_options) + CommonUtil.ExecLog(sModuleInfo, f"Navigated to: {url}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Tear_Down_Playwright(step_data=None): + """ + Close browser and clean up Playwright resources. + + Example: + Field Sub Field Value + tear down playwright action tear down + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global playwright_instance, browser, context, current_page + global playwright_details, current_page_id + + try: + # Close all tracked pages/contexts + for page_id, details in playwright_details.items(): + try: + if details.get("page"): + details["page"].close() + if details.get("context"): + details["context"].close() + except Exception: + pass + + # Close main instances + try: + if current_page and current_page not in [d.get("page") for d in playwright_details.values()]: + current_page.close() + except Exception: + pass + + try: + if context: + context.close() + except Exception: + pass + + try: + if browser: + browser.close() + except Exception: + pass + + try: + if playwright_instance: + playwright_instance.stop() + except Exception: + pass + + # Reset all globals + current_page = None + context = None + browser = None + playwright_instance = None + playwright_details = {} + current_page_id = None + + CommonUtil.ExecLog(sModuleInfo, "Browser closed successfully", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Switch_Browser(step_data): + """ + Switch between multiple browser instances/pages. + + Example: + Field Sub Field Value + driver id input parameter my_page_id + switch browser playwright action switch browser + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page, current_page_id, context + + try: + target_id = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("driver id", "page id", "driver tag"): + target_id = right_v + + if not target_id: + CommonUtil.ExecLog(sModuleInfo, "No driver/page ID provided", 3) + return "zeuz_failed" + + if target_id not in playwright_details: + CommonUtil.ExecLog(sModuleInfo, f"Page ID '{target_id}' not found", 3) + return "zeuz_failed" + + details = playwright_details[target_id] + current_page = details["page"] + context = details["context"] + current_page_id = target_id + + current_page.bring_to_front() + + sr.Set_Shared_Variables("playwright_page", current_page) + sr.Set_Shared_Variables("playwright_context", context) + + CommonUtil.ExecLog(sModuleInfo, f"Switched to page: {target_id}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Click Actions # +# # +######################### + +@logger +def Click_Element(step_data): + """ + Click an element. + + Example 1 - Basic: + Field Sub Field Value + id element parameter submit-btn + click playwright action click + + Example 2 - With options: + Field Sub Field Value + id element parameter submit-btn + use js optional parameter true + offset optional parameter 10,5 + click playwright action click + + Example 3 - Double click: + Field Sub Field Value + id element parameter item + double click playwright action double click + + Example 4 - Right click: + Field Sub Field Value + id element parameter item + right click playwright action right click + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + # Parse options + use_js = False + offset = None + double_click = False + right_click = False + click_count = 1 + modifiers = [] + delay = None + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "optional parameter": + if left_l == "use js": + use_js = right_v.lower() in ("true", "yes", "1") + elif left_l == "offset": + parts = right_v.split(",") + offset = {"x": float(parts[0].strip()), "y": float(parts[1].strip())} + elif left_l == "click count": + click_count = int(right_v) + elif left_l == "modifier": + modifiers.append(right_v) + elif left_l == "delay": + delay = int(float(right_v) * 1000) + elif left_l == "timeout": + timeout = int(float(right_v) * 1000) + + elif mid_l == "action": + if "double" in left_l: + double_click = True + elif "right" in left_l: + right_click = True + + # Get element + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + # Build click options + click_options = {} + if use_js: + click_options["force"] = True + if offset: + click_options["position"] = offset + if modifiers: + click_options["modifiers"] = modifiers + if delay: + click_options["delay"] = delay + if timeout: + click_options["timeout"] = timeout + if click_count > 1: + click_options["click_count"] = click_count + + # Perform click + if double_click: + locator.dblclick(**{k: v for k, v in click_options.items() if k != "click_count"}) + CommonUtil.ExecLog(sModuleInfo, "Double click performed", 1) + elif right_click: + click_options["button"] = "right" + locator.click(**click_options) + CommonUtil.ExecLog(sModuleInfo, "Right click performed", 1) + else: + locator.click(**click_options) + CommonUtil.ExecLog(sModuleInfo, "Click performed", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Double_Click_Element(step_data): + """ + Double-click an element. + + Example: + Field Sub Field Value + id element parameter item + double click playwright action double click + """ + # Reuse Click_Element with double click flag + modified_step_data = list(step_data) + # Ensure the action indicates double click + for i, (left, mid, right) in enumerate(modified_step_data): + if mid.strip().lower() == "action": + modified_step_data[i] = ("double click", mid, right) + break + + return Click_Element(modified_step_data) + + +@logger +def Right_Click_Element(step_data): + """ + Right-click (context click) an element. + + Example: + Field Sub Field Value + id element parameter item + right click playwright action right click + """ + modified_step_data = list(step_data) + for i, (left, mid, right) in enumerate(modified_step_data): + if mid.strip().lower() == "action": + modified_step_data[i] = ("right click", mid, right) + break + + return Click_Element(modified_step_data) + + +@logger +def Hover_Over_Element(step_data): + """ + Hover over an element. + + Example: + Field Sub Field Value + id element parameter menu-item + hover playwright action hover + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + use_js = False + offset = None + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "optional parameter": + if left_l == "use js": + use_js = right_v.lower() in ("true", "yes", "1") + elif left_l == "offset": + parts = right_v.split(",") + offset = {"x": float(parts[0].strip()), "y": float(parts[1].strip())} + elif left_l == "timeout": + timeout = int(float(right_v) * 1000) + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + hover_options = {} + if use_js: + hover_options["force"] = True + if offset: + hover_options["position"] = offset + if timeout: + hover_options["timeout"] = timeout + + locator.hover(**hover_options) + CommonUtil.ExecLog(sModuleInfo, "Hover performed", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Text Input # +# # +######################### + +@logger +def Enter_Text_In_Text_Box(step_data): + """ + Enter text in a text field. + + Example 1 - Basic: + Field Sub Field Value + id element parameter username + text action my_username + text playwright action text + + Example 2 - With options: + Field Sub Field Value + id element parameter username + text action my_username + delay optional parameter 0.1 + clear optional parameter true + use js optional parameter false + text playwright action text + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + text_value = "" + delay = 0 + use_js = False + clear = True + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + + if mid_l == "action": + text_value = right # Don't strip - preserve whitespace + elif mid_l == "optional parameter": + if left_l == "delay": + delay = float(right.strip()) + elif left_l == "use js": + use_js = right.strip().lower() in ("true", "yes", "1") + elif left_l == "clear": + clear = right.strip().lower() not in ("false", "no", "0") + elif left_l == "timeout": + timeout = int(float(right.strip()) * 1000) + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + # Enter text based on options + if use_js: + # Use JavaScript to set value directly + locator.evaluate(f"el => {{ el.value = `{text_value}`; }}") + # Trigger events + locator.dispatch_event("input") + locator.dispatch_event("change") + CommonUtil.ExecLog(sModuleInfo, f"Text entered via JS: {text_value[:50]}{'...' if len(text_value) > 50 else ''}", 1) + elif clear: + # fill() clears and sets value - recommended approach + fill_options = {} + if timeout: + fill_options["timeout"] = timeout + locator.fill(text_value, **fill_options) + CommonUtil.ExecLog(sModuleInfo, f"Text filled: {text_value[:50]}{'...' if len(text_value) > 50 else ''}", 1) + else: + # type() appends to existing value + type_options = {} + if delay > 0: + type_options["delay"] = int(delay * 1000) + if timeout: + type_options["timeout"] = timeout + locator.type(text_value, **type_options) + CommonUtil.ExecLog(sModuleInfo, f"Text typed: {text_value[:50]}{'...' if len(text_value) > 50 else ''}", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Keystroke_For_Element(step_data): + """ + Send keystrokes to an element or the page. + + Example 1 - Keys to element: + Field Sub Field Value + id element parameter search-box + keystroke keys action Enter + keystroke playwright action keystroke + + Example 2 - Key combination: + Field Sub Field Value + id element parameter editor + keystroke keys action Control+a + keystroke playwright action keystroke + + Example 3 - Characters without element: + Field Sub Field Value + keystroke chars action Hello World + keystroke playwright action keystroke + + Supported keys: Enter, Tab, Escape, Backspace, Delete, ArrowUp, ArrowDown, + ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, + Control, Shift, Alt, Meta, F1-F12, etc. + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + keystroke_type = None # "keys" or "chars" + keystroke_value = "" + key_count = 1 + has_element = False + delay = 0 + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "action": + if left_l == "keystroke keys": + keystroke_type = "keys" + keystroke_value = right_v + # Check for count: "Tab,3" means press Tab 3 times + if "," in keystroke_value: + parts = keystroke_value.rsplit(",", 1) + try: + key_count = int(parts[1].strip()) + keystroke_value = parts[0].strip() + except ValueError: + pass + elif left_l == "keystroke chars": + keystroke_type = "chars" + keystroke_value = right # Don't strip to preserve whitespace + + elif mid_l == "element parameter": + has_element = True + + elif mid_l == "optional parameter": + if left_l == "delay": + delay = float(right_v) + + if not keystroke_type: + CommonUtil.ExecLog(sModuleInfo, "No keystroke type specified (keystroke keys or keystroke chars)", 3) + return "zeuz_failed" + + # Convert common key names + key_map = { + "CTRL": "Control", + "CONTROL": "Control", + "ALT": "Alt", + "SHIFT": "Shift", + "ENTER": "Enter", + "RETURN": "Enter", + "TAB": "Tab", + "ESC": "Escape", + "ESCAPE": "Escape", + "BACKSPACE": "Backspace", + "DELETE": "Delete", + "SPACE": " ", + "UP": "ArrowUp", + "DOWN": "ArrowDown", + "LEFT": "ArrowLeft", + "RIGHT": "ArrowRight", + "HOME": "Home", + "END": "End", + "PAGEUP": "PageUp", + "PAGEDOWN": "PageDown", + } + + if keystroke_type == "keys": + # Convert key names + key = keystroke_value.upper() + if "+" in key: + # Key combination like Ctrl+A + parts = key.split("+") + converted = [key_map.get(p.strip(), p.strip().capitalize()) for p in parts] + key = "+".join(converted) + else: + key = key_map.get(key, keystroke_value) + + if has_element: + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + for _ in range(key_count): + locator.press(key) + if delay > 0: + time.sleep(delay) + else: + for _ in range(key_count): + current_page.keyboard.press(key) + if delay > 0: + time.sleep(delay) + + CommonUtil.ExecLog(sModuleInfo, f"Pressed key: {key} ({key_count} times)", 1) + + elif keystroke_type == "chars": + type_options = {} + if delay > 0: + type_options["delay"] = int(delay * 1000) + + if has_element: + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + locator.type(keystroke_value, **type_options) + else: + current_page.keyboard.type(keystroke_value, **type_options) + + CommonUtil.ExecLog(sModuleInfo, f"Typed chars: {keystroke_value[:50]}{'...' if len(keystroke_value) > 50 else ''}", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Validation # +# # +######################### + +@logger +def Validate_Text(step_data): + """ + Validate that an element contains expected text. + + Example 1 - Exact match: + Field Sub Field Value + id element parameter message + validate text action Success! + validate text playwright action validate text + + Example 2 - Partial match: + Field Sub Field Value + id element parameter message + *validate text action Success + validate text playwright action validate text + + Example 3 - Case-insensitive partial: + Field Sub Field Value + id element parameter message + **validate text action success + validate text playwright action validate text + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + expected_text = "" + partial_match = False + case_insensitive = False + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "action": + if left_l.startswith("**"): + partial_match = True + case_insensitive = True + elif left_l.startswith("*"): + partial_match = True + expected_text = right_v + + elif mid_l == "optional parameter": + if left_l == "timeout": + timeout = int(float(right_v) * 1000) + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + # Get actual text + actual_text = locator.text_content() or "" + + # Compare + match = False + if case_insensitive: + if partial_match: + match = expected_text.lower() in actual_text.lower() + else: + match = expected_text.lower() == actual_text.lower() + else: + if partial_match: + match = expected_text in actual_text + else: + match = expected_text == actual_text + + if match: + CommonUtil.ExecLog(sModuleInfo, f"Text validation passed: '{expected_text}'", 1) + return "passed" + else: + CommonUtil.ExecLog( + sModuleInfo, + f"Text validation failed.\nExpected: '{expected_text}'\nActual: '{actual_text}'", + 3 + ) + return "zeuz_failed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def if_element_exists(step_data): + """ + Check if an element exists on the page. + + Example: + Field Sub Field Value + id element parameter optional-element + if element exists playwright action if element exists + + Returns "passed" if element exists, "zeuz_failed" if not. + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + timeout = 1000 # Short timeout for existence check + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "optional parameter" and left_l == "timeout": + timeout = int(float(right_v) * 1000) + + locator = PlaywrightLocator.Get_Element(step_data, current_page, element_wait=timeout/1000) + + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element does not exist", 1) + return "zeuz_failed" + + try: + count = locator.count() + if count > 0: + CommonUtil.ExecLog(sModuleInfo, f"Element exists ({count} found)", 1) + return "passed" + else: + CommonUtil.ExecLog(sModuleInfo, "Element does not exist", 1) + return "zeuz_failed" + except Exception: + CommonUtil.ExecLog(sModuleInfo, "Element does not exist", 1) + return "zeuz_failed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Save_Attribute(step_data): + """ + Save an element's attribute value to a shared variable. + + Example: + Field Sub Field Value + id element parameter my-link + href input parameter attribute_name + my_variable save parameter ignore + save attribute playwright action save attribute + + Special attribute names: + - text: Get text content + - innertext: Get inner text + - innerhtml: Get inner HTML + - outerhtml: Get outer HTML + - value: Get input value + - checked: Get checkbox state + - selected: Get select option state + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + attribute_name = None + save_variable = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + attribute_name = left.strip() # Keep original case + elif mid_l == "save parameter": + save_variable = left.strip() + + if not attribute_name: + CommonUtil.ExecLog(sModuleInfo, "No attribute name specified", 3) + return "zeuz_failed" + + if not save_variable: + CommonUtil.ExecLog(sModuleInfo, "No save variable specified", 3) + return "zeuz_failed" + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + # Get attribute value based on name + attr_lower = attribute_name.lower() + if attr_lower == "text": + value = locator.text_content() + elif attr_lower == "innertext": + value = locator.inner_text() + elif attr_lower == "innerhtml": + value = locator.inner_html() + elif attr_lower == "outerhtml": + value = locator.evaluate("el => el.outerHTML") + elif attr_lower == "value": + value = locator.input_value() + elif attr_lower == "checked": + value = locator.is_checked() + elif attr_lower == "selected": + value = locator.evaluate("el => el.selected") + elif attr_lower == "visible": + value = locator.is_visible() + elif attr_lower == "enabled": + value = locator.is_enabled() + elif attr_lower == "disabled": + value = locator.is_disabled() + else: + value = locator.get_attribute(attribute_name) + + sr.Set_Shared_Variables(save_variable, value) + CommonUtil.ExecLog(sModuleInfo, f"Saved '{attribute_name}' = '{value}' to '{save_variable}'", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def get_element_info(step_data): + """ + Get detailed information about an element. + + Example: + Field Sub Field Value + id element parameter my-element + element_info save parameter ignore + get element info playwright action get element info + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + save_variable = None + for left, mid, right in step_data: + if mid.strip().lower() == "save parameter": + save_variable = left.strip() + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + # Gather element info + info = { + "tag_name": locator.evaluate("el => el.tagName"), + "text": locator.text_content(), + "inner_html": locator.inner_html(), + "visible": locator.is_visible(), + "enabled": locator.is_enabled(), + "bounding_box": locator.bounding_box(), + } + + # Get all attributes + attributes = locator.evaluate("""el => { + const attrs = {}; + for (const attr of el.attributes) { + attrs[attr.name] = attr.value; + } + return attrs; + }""") + info["attributes"] = attributes + + if save_variable: + sr.Set_Shared_Variables(save_variable, info) + CommonUtil.ExecLog(sModuleInfo, f"Element info saved to '{save_variable}'", 1) + else: + CommonUtil.ExecLog(sModuleInfo, f"Element info: {info}", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Navigation # +# # +######################### + +@logger +def Navigate(step_data): + """ + Navigate browser (back, forward, refresh). + + Example: + Field Sub Field Value + navigate action back + navigate playwright action navigate + + Options: back, forward, refresh, reload + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + direction = "back" + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip().lower() + + if mid_l == "action": + direction = right_v + elif mid_l == "optional parameter": + if left_l == "timeout": + timeout = int(float(right.strip()) * 1000) + + nav_options = {} + if timeout: + nav_options["timeout"] = timeout + + if direction in ("back", "go back"): + current_page.go_back(**nav_options) + CommonUtil.ExecLog(sModuleInfo, "Navigated back", 1) + elif direction in ("forward", "go forward"): + current_page.go_forward(**nav_options) + CommonUtil.ExecLog(sModuleInfo, "Navigated forward", 1) + elif direction in ("refresh", "reload"): + current_page.reload(**nav_options) + CommonUtil.ExecLog(sModuleInfo, "Page reloaded", 1) + else: + CommonUtil.ExecLog(sModuleInfo, f"Unknown navigation direction: {direction}", 3) + return "zeuz_failed" + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Get_Current_URL(step_data): + """ + Get the current page URL and save to variable. + + Example: + Field Sub Field Value + current_url save parameter ignore + get current url playwright action get current url + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + save_variable = None + for left, mid, right in step_data: + if mid.strip().lower() == "save parameter": + save_variable = left.strip() + + url = current_page.url + + if save_variable: + sr.Set_Shared_Variables(save_variable, url) + CommonUtil.ExecLog(sModuleInfo, f"Current URL saved to '{save_variable}': {url}", 1) + else: + CommonUtil.ExecLog(sModuleInfo, f"Current URL: {url}", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Scroll # +# # +######################### + +@logger +def Scroll(step_data): + """ + Scroll the page in a direction. + + Example: + Field Sub Field Value + direction input parameter down + pixel input parameter 500 + scroll playwright action scroll + + Directions: up, down, left, right + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + direction = "down" + pixels = 300 + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l == "direction": + direction = right_v.lower() + elif left_l in ("pixel", "pixels", "amount"): + pixels = int(right_v) + + # Calculate delta + delta_x = 0 + delta_y = 0 + + if direction == "down": + delta_y = pixels + elif direction == "up": + delta_y = -pixels + elif direction == "right": + delta_x = pixels + elif direction == "left": + delta_x = -pixels + + current_page.mouse.wheel(delta_x, delta_y) + CommonUtil.ExecLog(sModuleInfo, f"Scrolled {direction} by {pixels}px", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def scroll_to_element(step_data): + """ + Scroll an element into view. + + Example: + Field Sub Field Value + id element parameter footer + use js optional parameter false + scroll to element playwright action scroll to element + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + use_js = False + align_to_top = True + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "optional parameter": + if left_l == "use js": + use_js = right_v.lower() in ("true", "yes", "1") + elif left_l == "align to top": + align_to_top = right_v.lower() in ("true", "yes", "1") + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + if use_js: + locator.evaluate(f"el => el.scrollIntoView({str(align_to_top).lower()})") + else: + locator.scroll_into_view_if_needed() + + CommonUtil.ExecLog(sModuleInfo, "Scrolled element into view", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Select/Dropdown # +# # +######################### + +@logger +def Select_Deselect(step_data): + """ + Select or deselect options in a dropdown/select element. + + Example 1 - Select by visible text: + Field Sub Field Value + id element parameter country-select + select action United States + select playwright action select + + Example 2 - Select by value: + Field Sub Field Value + id element parameter country-select + select by value action US + select playwright action select + + Example 3 - Select by index: + Field Sub Field Value + id element parameter country-select + select by index action 2 + select playwright action select + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + select_type = "label" # label, value, index + select_value = None + is_deselect = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "action": + if "deselect" in left_l: + is_deselect = True + + if "by value" in left_l or "byvalue" in left_l: + select_type = "value" + elif "by index" in left_l or "byindex" in left_l: + select_type = "index" + elif "by label" in left_l or "by text" in left_l: + select_type = "label" + + select_value = right_v + + if not select_value: + CommonUtil.ExecLog(sModuleInfo, "No selection value provided", 3) + return "zeuz_failed" + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + # Build selection option + if select_type == "value": + option = {"value": select_value} + elif select_type == "index": + option = {"index": int(select_value)} + else: # label + option = {"label": select_value} + + if is_deselect: + # Playwright doesn't have direct deselect, use JavaScript + locator.evaluate(f"""el => {{ + for (const opt of el.options) {{ + if (opt.{'value' if select_type == 'value' else 'text'} === '{select_value}') {{ + opt.selected = false; + }} + }} + el.dispatchEvent(new Event('change')); + }}""") + CommonUtil.ExecLog(sModuleInfo, f"Deselected: {select_value}", 1) + else: + locator.select_option(**option) + CommonUtil.ExecLog(sModuleInfo, f"Selected: {select_value}", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Checkbox/Radio # +# # +######################### + +@logger +def check_uncheck(step_data): + """ + Check or uncheck a checkbox/radio button. + + Example: + Field Sub Field Value + id element parameter agree-checkbox + check uncheck action check + check playwright action check + + Actions: check, uncheck + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + action = "check" + use_js = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip().lower() + + if mid_l == "action": + if "uncheck" in left_l or "uncheck" in right_v: + action = "uncheck" + else: + action = "check" + elif mid_l == "optional parameter": + if left_l == "use js": + use_js = right_v in ("true", "yes", "1") + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + options = {} + if use_js: + options["force"] = True + + if action == "check": + locator.check(**options) + CommonUtil.ExecLog(sModuleInfo, "Checkbox checked", 1) + else: + locator.uncheck(**options) + CommonUtil.ExecLog(sModuleInfo, "Checkbox unchecked", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Windows/Tabs # +# # +######################### + +@logger +def switch_window_or_tab(step_data): + """ + Switch to a different window/tab. + + Example 1 - By title: + Field Sub Field Value + window title input parameter Google + switch window/tab playwright action switch window or tab + + Example 2 - By partial title: + Field Sub Field Value + *window title input parameter Goo + switch window/tab playwright action switch window or tab + + Example 3 - By index: + Field Sub Field Value + window index input parameter 1 + switch window/tab playwright action switch window or tab + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page, context + + try: + if context is None: + CommonUtil.ExecLog(sModuleInfo, "No browser context open", 3) + return "zeuz_failed" + + switch_by_title = None + switch_by_index = None + partial_match = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("window title", "tab title"): + switch_by_title = right_v + elif left_l in ("*window title", "*tab title"): + switch_by_title = right_v + partial_match = True + elif left_l in ("window index", "tab index"): + switch_by_index = int(right_v) + + pages = context.pages + + if switch_by_title: + for page in pages: + page_title = page.title() + if partial_match: + if switch_by_title.lower() in page_title.lower(): + current_page = page + page.bring_to_front() + sr.Set_Shared_Variables("playwright_page", current_page) + CommonUtil.ExecLog(sModuleInfo, f"Switched to tab: {page_title}", 1) + return "passed" + else: + if switch_by_title.lower() == page_title.lower(): + current_page = page + page.bring_to_front() + sr.Set_Shared_Variables("playwright_page", current_page) + CommonUtil.ExecLog(sModuleInfo, f"Switched to tab: {page_title}", 1) + return "passed" + + CommonUtil.ExecLog(sModuleInfo, f"No tab found with title: {switch_by_title}", 3) + return "zeuz_failed" + + elif switch_by_index is not None: + if 0 <= switch_by_index < len(pages): + current_page = pages[switch_by_index] + current_page.bring_to_front() + sr.Set_Shared_Variables("playwright_page", current_page) + CommonUtil.ExecLog(sModuleInfo, f"Switched to tab index {switch_by_index}: {current_page.title()}", 1) + return "passed" + else: + CommonUtil.ExecLog(sModuleInfo, f"Invalid tab index: {switch_by_index}", 3) + return "zeuz_failed" + + CommonUtil.ExecLog(sModuleInfo, "No window title or index provided", 3) + return "zeuz_failed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def open_new_tab(step_data): + """ + Open a new browser tab. + + Example: + Field Sub Field Value + url input parameter https://example.com + open new tab playwright action open new tab + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page, context + + try: + if context is None: + CommonUtil.ExecLog(sModuleInfo, "No browser context open", 3) + return "zeuz_failed" + + url = None + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter" and left_l in ("url", "link", "go to link"): + url = right_v + + new_page = context.new_page() + current_page = new_page + sr.Set_Shared_Variables("playwright_page", current_page) + + if url: + new_page.goto(url) + CommonUtil.ExecLog(sModuleInfo, f"Opened new tab with URL: {url}", 1) + else: + CommonUtil.ExecLog(sModuleInfo, "Opened new blank tab", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def close_tab(step_data): + """ + Close a browser tab. + + Example 1 - Close current: + Field Sub Field Value + close tab playwright action close tab + + Example 2 - Close by title: + Field Sub Field Value + tab title input parameter Google + close tab playwright action close tab + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page, context + + try: + if context is None: + CommonUtil.ExecLog(sModuleInfo, "No browser context open", 3) + return "zeuz_failed" + + tab_title = None + tab_index = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l == "tab title": + tab_title = right_v + elif left_l == "tab index": + tab_index = int(right_v) + + pages = context.pages + + if tab_title: + for page in pages: + if tab_title.lower() in page.title().lower(): + page.close() + CommonUtil.ExecLog(sModuleInfo, f"Closed tab: {tab_title}", 1) + break + else: + CommonUtil.ExecLog(sModuleInfo, f"Tab not found: {tab_title}", 3) + return "zeuz_failed" + elif tab_index is not None: + if 0 <= tab_index < len(pages): + pages[tab_index].close() + CommonUtil.ExecLog(sModuleInfo, f"Closed tab at index {tab_index}", 1) + else: + CommonUtil.ExecLog(sModuleInfo, f"Invalid tab index: {tab_index}", 3) + return "zeuz_failed" + else: + # Close current tab + if current_page: + current_page.close() + CommonUtil.ExecLog(sModuleInfo, "Closed current tab", 1) + + # Switch to remaining tab if available + pages = context.pages + if pages: + current_page = pages[-1] + sr.Set_Shared_Variables("playwright_page", current_page) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# IFrames # +# # +######################### + +@logger +def switch_iframe(step_data): + """ + Switch to an iframe or back to main content. + + Example 1 - Switch by locator: + Field Sub Field Value + id iframe parameter my-iframe + switch iframe playwright action switch iframe + + Example 2 - Switch by index: + Field Sub Field Value + index input parameter 0 + switch iframe playwright action switch iframe + + Example 3 - Switch to default/main: + Field Sub Field Value + index input parameter default content + switch iframe playwright action switch iframe + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + iframe_index = None + iframe_selector = None + switch_to_default = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l in ("iframe parameter", "frame parameter"): + iframe_selector = f"[{left}='{right_v}']" if left_l not in ("tag",) else right_v + elif mid_l == "input parameter": + if left_l == "index": + if right_v.lower() in ("default content", "default", "main"): + switch_to_default = True + else: + iframe_index = int(right_v) + + if switch_to_default: + # In Playwright, we work with the main page directly + # Store a flag or reset frame locator + CommonUtil.ExecLog(sModuleInfo, "Switched to default content", 1) + return "passed" + + # Build frame locator + if iframe_selector: + frame_locator = current_page.frame_locator(iframe_selector) + elif iframe_index is not None: + frame_locator = current_page.frame_locator(f"iframe >> nth={iframe_index}") + else: + CommonUtil.ExecLog(sModuleInfo, "No iframe selector or index provided", 3) + return "zeuz_failed" + + # Store frame locator for subsequent actions + sr.Set_Shared_Variables("playwright_frame", frame_locator) + CommonUtil.ExecLog(sModuleInfo, "Switched to iframe", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Alerts/Dialogs # +# # +######################### + +@logger +def Handle_Browser_Alert(step_data): + """ + Handle browser alerts/dialogs. + + Example 1 - Accept alert: + Field Sub Field Value + handle alert action accept + handle alert playwright action handle alert + + Example 2 - Dismiss alert: + Field Sub Field Value + handle alert action dismiss + handle alert playwright action handle alert + + Example 3 - Get text and accept: + Field Sub Field Value + handle alert action accept + alert_text save parameter ignore + handle alert playwright action handle alert + + Example 4 - Enter text in prompt: + Field Sub Field Value + handle alert action accept + prompt text input parameter my response + handle alert playwright action handle alert + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + action = "accept" + prompt_text = None + save_variable = None + timeout = 5000 + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "action": + action = right_v.lower() + elif mid_l == "input parameter": + if left_l in ("prompt text", "text", "send text"): + prompt_text = right_v + elif mid_l == "save parameter": + save_variable = left.strip() + elif mid_l == "optional parameter": + if left_l in ("timeout", "wait"): + timeout = int(float(right_v) * 1000) + + # Set up dialog handler + dialog_info = {"message": None, "handled": False} + + def handle_dialog(dialog): + dialog_info["message"] = dialog.message + dialog_info["type"] = dialog.type + + if action in ("accept", "ok", "yes"): + if prompt_text: + dialog.accept(prompt_text) + else: + dialog.accept() + elif action in ("dismiss", "cancel", "no"): + dialog.dismiss() + else: + dialog.accept() + + dialog_info["handled"] = True + + current_page.on("dialog", handle_dialog) + + # Wait for dialog + try: + current_page.wait_for_event("dialog", timeout=timeout) + except PlaywrightTimeoutError: + CommonUtil.ExecLog(sModuleInfo, "No alert appeared within timeout", 2) + current_page.remove_listener("dialog", handle_dialog) + return "passed" # Not necessarily a failure + + # Remove listener + current_page.remove_listener("dialog", handle_dialog) + + # Save text if requested + if save_variable and dialog_info["message"]: + sr.Set_Shared_Variables(save_variable, dialog_info["message"]) + + CommonUtil.ExecLog( + sModuleInfo, + f"Alert handled ({action}): {dialog_info.get('message', 'N/A')}", + 1 + ) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Drag & Drop # +# # +######################### + +@logger +def drag_and_drop(step_data): + """ + Drag and drop an element to a target. + + Example: + Field Sub Field Value + id element parameter drag-item + id target parameter drop-zone + drag and drop playwright action drag and drop + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + # Separate source and target parameters + source_params = [] + target_params = [] + + for left, mid, right in step_data: + mid_l = mid.strip().lower() + if mid_l == "target parameter": + target_params.append((left, "element parameter", right)) + elif mid_l == "element parameter": + source_params.append((left, mid, right)) + else: + source_params.append((left, mid, right)) + target_params.append((left, mid, right)) + + # Get source element + source_locator = PlaywrightLocator.Get_Element(source_params, current_page) + if source_locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Source element not found", 3) + return "zeuz_failed" + + # Get target element + target_locator = PlaywrightLocator.Get_Element(target_params, current_page) + if target_locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Target element not found", 3) + return "zeuz_failed" + + # Perform drag and drop + source_locator.drag_to(target_locator) + CommonUtil.ExecLog(sModuleInfo, "Drag and drop completed", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Screenshot # +# # +######################### + +@logger +def take_screenshot_playwright(step_data): + """ + Take a screenshot. + + Example 1 - Full page: + Field Sub Field Value + fullscreen optional parameter true + screenshot_path save parameter ignore + take screenshot playwright action take screenshot + + Example 2 - Element only: + Field Sub Field Value + id element parameter my-element + take screenshot playwright action take screenshot + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + full_page = False + save_variable = None + custom_path = None + has_element = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "element parameter": + has_element = True + elif mid_l == "optional parameter": + if left_l in ("fullscreen", "full page", "fullpage"): + full_page = right_v.lower() in ("true", "yes", "1") + elif left_l == "path": + custom_path = right_v + elif mid_l == "save parameter": + save_variable = left.strip() + + # Generate filename + if custom_path: + screenshot_path = custom_path + else: + timestamp = time.strftime("%Y_%m_%d_%H-%M-%S") + screenshot_path = f"screenshot_{timestamp}.png" + + # Take screenshot + if has_element: + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + locator.screenshot(path=screenshot_path) + else: + current_page.screenshot(path=screenshot_path, full_page=full_page) + + if save_variable: + sr.Set_Shared_Variables(save_variable, screenshot_path) + + CommonUtil.ExecLog(sModuleInfo, f"Screenshot saved: {screenshot_path}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# JavaScript # +# # +######################### + +@logger +def execute_javascript(step_data): + """ + Execute JavaScript code in the browser. + + Example 1 - Page-level: + Field Sub Field Value + javascript action return document.title + result save parameter ignore + execute javascript playwright action execute javascript + + Example 2 - On element: + Field Sub Field Value + id element parameter my-element + javascript action el => el.scrollTop = 100 + execute javascript playwright action execute javascript + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + js_code = None + save_variable = None + has_element = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + + if mid_l == "action": + js_code = right + elif mid_l == "element parameter": + has_element = True + elif mid_l == "save parameter": + save_variable = left.strip() + + if not js_code: + CommonUtil.ExecLog(sModuleInfo, "No JavaScript code provided", 3) + return "zeuz_failed" + + # Execute JS + if has_element: + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + result = locator.evaluate(js_code) + else: + result = current_page.evaluate(js_code) + + if save_variable: + sr.Set_Shared_Variables(save_variable, result) + CommonUtil.ExecLog(sModuleInfo, f"JS result saved to '{save_variable}': {result}", 1) + else: + CommonUtil.ExecLog(sModuleInfo, f"JS executed. Result: {result}", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# File Upload # +# # +######################### + +@logger +def upload_file(step_data): + """ + Upload a file via file input. + + Example: + Field Sub Field Value + id element parameter file-input + file path input parameter /path/to/file.pdf + upload file playwright action upload file + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + file_path = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("file path", "filepath", "file", "path"): + file_path = right_v + + if not file_path: + CommonUtil.ExecLog(sModuleInfo, "No file path provided", 3) + return "zeuz_failed" + + # Check if file exists + if not os.path.exists(file_path): + # Check in attachments + attachments = sr.Get_Shared_Variables("file_attachment") + if attachments and file_path in attachments: + file_path = attachments[file_path] + else: + CommonUtil.ExecLog(sModuleInfo, f"File not found: {file_path}", 3) + return "zeuz_failed" + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + locator.set_input_files(file_path) + CommonUtil.ExecLog(sModuleInfo, f"File uploaded: {file_path}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Window Resize # +# # +######################### + +@logger +def resize_window(step_data): + """ + Resize the browser viewport. + + Example: + Field Sub Field Value + width input parameter 1280 + height input parameter 720 + resize window playwright action resize window + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + width = None + height = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l == "width": + # Handle percentage + if "%" in right_v: + # Get current size first + current_size = current_page.viewport_size + pct = int(right_v.replace("%", "")) / 100 + width = int(current_size["width"] * pct) + else: + width = int(right_v) + elif left_l == "height": + if "%" in right_v: + current_size = current_page.viewport_size + pct = int(right_v.replace("%", "")) / 100 + height = int(current_size["height"] * pct) + else: + height = int(right_v) + + if width is None or height is None: + current_size = current_page.viewport_size + width = width or current_size["width"] + height = height or current_size["height"] + + current_page.set_viewport_size({"width": width, "height": height}) + CommonUtil.ExecLog(sModuleInfo, f"Window resized to {width}x{height}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Wait Actions # +# # +######################### + +@logger +def Wait_For_Element(step_data): + """ + Wait for an element to appear/disappear. + + Example 1 - Wait for visible: + Field Sub Field Value + id element parameter loading-spinner + wait input parameter hidden + wait for element playwright action wait for element + + Example 2 - Wait with timeout: + Field Sub Field Value + id element parameter results + timeout optional parameter 30 + wait for element playwright action wait for element + + States: attached, detached, visible, hidden + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + state = "visible" + timeout = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("wait", "state"): + state = right_v.lower() + elif mid_l == "optional parameter": + if left_l == "timeout": + timeout = int(float(right_v) * 1000) + + locator = PlaywrightLocator.Get_Element(step_data, current_page, element_wait=0.1) + + if locator == "zeuz_failed": + # For hidden/detached states, element not found is actually success + if state in ("hidden", "detached"): + CommonUtil.ExecLog(sModuleInfo, f"Element already {state}", 1) + return "passed" + CommonUtil.ExecLog(sModuleInfo, "Element not found", 3) + return "zeuz_failed" + + wait_options = {"state": state} + if timeout: + wait_options["timeout"] = timeout + + locator.wait_for(**wait_options) + CommonUtil.ExecLog(sModuleInfo, f"Element reached state: {state}", 1) + return "passed" + + except PlaywrightTimeoutError: + CommonUtil.ExecLog(sModuleInfo, f"Timeout waiting for element to be {state}", 3) + return "zeuz_failed" + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Playwright-Specific # +# # +######################### + +@logger +def Start_Tracing(step_data): + """ + Start Playwright trace recording. + + Example: + Field Sub Field Value + screenshots optional parameter true + snapshots optional parameter true + start tracing playwright action start tracing + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global context + + try: + if context is None: + CommonUtil.ExecLog(sModuleInfo, "No browser context open", 3) + return "zeuz_failed" + + screenshots = True + snapshots = True + sources = False + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip().lower() + + if mid_l == "optional parameter": + if left_l == "screenshots": + screenshots = right_v in ("true", "yes", "1") + elif left_l == "snapshots": + snapshots = right_v in ("true", "yes", "1") + elif left_l == "sources": + sources = right_v in ("true", "yes", "1") + + context.tracing.start( + screenshots=screenshots, + snapshots=snapshots, + sources=sources + ) + CommonUtil.ExecLog(sModuleInfo, "Tracing started", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Stop_Tracing(step_data): + """ + Stop tracing and save trace file. + + Example: + Field Sub Field Value + path input parameter trace.zip + stop tracing playwright action stop tracing + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global context + + try: + if context is None: + CommonUtil.ExecLog(sModuleInfo, "No browser context open", 3) + return "zeuz_failed" + + trace_path = "trace.zip" + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter" and left_l == "path": + trace_path = right_v + + context.tracing.stop(path=trace_path) + CommonUtil.ExecLog(sModuleInfo, f"Trace saved to: {trace_path}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +@logger +def Intercept_Network(step_data): + """ + Set up network request interception. + + Example: + Field Sub Field Value + url pattern input parameter **/api/** + action action abort + intercept network playwright action intercept network + + Actions: abort, continue, fulfill + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + url_pattern = "**/*" + action = "continue" + response_body = None + response_status = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "input parameter": + if left_l in ("url pattern", "pattern", "url"): + url_pattern = right_v + elif left_l == "response body": + response_body = right_v + elif left_l == "response status": + response_status = int(right_v) + elif mid_l == "action": + action = right_v.lower() + + def handle_route(route): + if action == "abort": + route.abort() + elif action == "fulfill": + fulfill_options = {} + if response_body: + fulfill_options["body"] = response_body + if response_status: + fulfill_options["status"] = response_status + route.fulfill(**fulfill_options) + else: + route.continue_() + + current_page.route(url_pattern, handle_route) + CommonUtil.ExecLog(sModuleInfo, f"Network interception set up for: {url_pattern}", 1) + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +######################### +# # +# Table Operations # +# # +######################### + +@logger +def Extract_Table_Data(step_data): + """ + Extract data from an HTML table. + + Example: + Field Sub Field Value + id element parameter data-table + table_data save parameter ignore + extract table data playwright action extract table data + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + global current_page + + try: + if current_page is None: + CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) + return "zeuz_failed" + + save_variable = None + row_filter = None + col_filter = None + + for left, mid, right in step_data: + left_l = left.strip().lower() + mid_l = mid.strip().lower() + right_v = right.strip() + + if mid_l == "save parameter": + save_variable = left.strip() + elif mid_l == "optional parameter": + if left_l == "row": + row_filter = right_v + elif left_l == "column": + col_filter = right_v + + locator = PlaywrightLocator.Get_Element(step_data, current_page) + if locator == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Table element not found", 3) + return "zeuz_failed" + + # Extract table data using JavaScript + table_data = locator.evaluate("""table => { + const data = []; + const rows = table.querySelectorAll('tr'); + rows.forEach(row => { + const rowData = []; + const cells = row.querySelectorAll('td, th'); + cells.forEach(cell => { + rowData.push(cell.textContent.trim()); + }); + if (rowData.length > 0) { + data.push(rowData); + } + }); + return data; + }""") + + if save_variable: + sr.Set_Shared_Variables(save_variable, table_data) + CommonUtil.ExecLog(sModuleInfo, f"Table data saved to '{save_variable}' ({len(table_data)} rows)", 1) + else: + CommonUtil.ExecLog(sModuleInfo, f"Table data extracted: {len(table_data)} rows", 1) + + return "passed" + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) diff --git a/Framework/Built_In_Automation/Web/Playwright/__init__.py b/Framework/Built_In_Automation/Web/Playwright/__init__.py new file mode 100644 index 000000000..e6549c989 --- /dev/null +++ b/Framework/Built_In_Automation/Web/Playwright/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +Playwright Automation Module for Zeuz Node + +This module provides Playwright-based web automation actions that can be used +as an alternative to Selenium with improved speed and modern features. + +Usage: + Change "selenium action" to "playwright action" in your test steps. + All element parameters and optional parameters work the same way. + +Example: + Field Sub Field Value + id element parameter submit-btn + click playwright action click +""" + +from . import BuiltInFunctions + +__all__ = ['BuiltInFunctions'] diff --git a/Framework/Built_In_Automation/Web/Playwright/locator.py b/Framework/Built_In_Automation/Web/Playwright/locator.py new file mode 100644 index 000000000..5e7420d83 --- /dev/null +++ b/Framework/Built_In_Automation/Web/Playwright/locator.py @@ -0,0 +1,507 @@ +# -*- coding: utf-8 -*- +""" +Playwright Element Locator Module + +This module provides element location functionality using Playwright's native +Locator API while reusing the query building logic from LocateElement.py. + +Key Features: +- Native Playwright Locator API (lazy evaluation, auto-wait) +- Supports all existing element parameter formats +- Supports Playwright-native selectors (test-id, role, text, etc.) +- Preserves Playwright's speed advantage +""" + +import sys +import inspect +import re + +from Framework.Utilities import CommonUtil +from Framework.Built_In_Automation.Shared_Resources import ( + BuiltInFunctionSharedResources as sr, +) +from Framework.Utilities.CommonUtil import passed_tag_list, failed_tag_list + +MODULE_NAME = inspect.getmodulename(__file__) + + +def Get_Element(step_data, page, return_all=False, element_wait=None): + """ + Get element using Playwright's native Locator API. + + This function parses the same step_data format as LocateElement.Get_Element() + but uses Playwright's Locator API for execution, preserving auto-wait and + lazy evaluation benefits. + + Args: + step_data: List of (left, mid, right) tuples - standard Zeuz format + page: Playwright Page object + return_all: If True, return list of all matching ElementHandles + element_wait: Override default wait timeout (in seconds) + + Returns: + Locator | List[ElementHandle] | "zeuz_failed" + + Example: + step_data = [ + ("id", "element parameter", "submit-btn"), + ("click", "playwright action", "click"), + ] + locator = Get_Element(step_data, page) + locator.click() # Auto-waits for element + """ + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + + try: + # Parse all parameters from step_data + params = _parse_element_params(step_data) + + # Check for get parameter (retrieve saved element) + if params.get('get_parameter'): + result = sr.parse_variable(params['get_parameter']) + if result not in failed_tag_list: + CommonUtil.ExecLog( + sModuleInfo, + f"Returning saved element '{params['get_parameter']}' from shared variables", + 1, + ) + return result + else: + CommonUtil.ExecLog( + sModuleInfo, + f"Element '{params['get_parameter']}' not found in shared variables", + 3, + ) + return "zeuz_failed" + + # Build the locator + locator = _build_locator(page, step_data, params) + + if locator is None: + CommonUtil.ExecLog(sModuleInfo, "Could not build locator from step data", 3) + return "zeuz_failed" + + # Set timeout if specified + if element_wait is not None: + timeout = int(float(element_wait) * 1000) + elif params.get('wait') is not None: + timeout = int(float(params['wait']) * 1000) + else: + # Get default from shared variables + default_wait = sr.Get_Shared_Variables("element_wait") + if default_wait not in failed_tag_list: + timeout = int(float(default_wait) * 1000) + else: + timeout = 10000 # Default 10 seconds + + # Apply visibility filter if not allowing hidden + if not params.get('allow_hidden'): + # Filter to visible elements only + locator = locator.locator("visible=true") + + # Apply index if specified + if params.get('index') is not None: + index = params['index'] + locator = locator.nth(index) + + # Log the locator being used + CommonUtil.ExecLog(sModuleInfo, f"Playwright locator: {locator}", 5) + + # Save if requested + if params.get('save_parameter'): + sr.Set_Shared_Variables(params['save_parameter'], locator) + CommonUtil.ExecLog( + sModuleInfo, + f"Saved element to variable '{params['save_parameter']}'", + 1, + ) + + # Return all elements if requested + if return_all: + try: + elements = locator.all() + CommonUtil.ExecLog(sModuleInfo, f"Found {len(elements)} elements", 1) + return elements + except Exception as e: + CommonUtil.ExecLog(sModuleInfo, f"Error getting all elements: {e}", 3) + return "zeuz_failed" + + # Check if element exists (with timeout) + try: + count = locator.count() + if count == 0: + CommonUtil.ExecLog(sModuleInfo, "No elements found matching locator", 3) + return "zeuz_failed" + elif count > 1 and params.get('index') is None: + CommonUtil.ExecLog( + sModuleInfo, + f"Found {count} elements. Returning first. Consider using index parameter.", + 2, + ) + except Exception: + pass # Count might fail, but locator might still work with auto-wait + + return locator + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +def _parse_element_params(step_data): + """ + Parse element parameters from step data. + + Returns dict with: + - index: Element index (int or None) + - allow_hidden: Whether to include hidden elements (bool) + - save_parameter: Variable name to save element (str or None) + - get_parameter: Variable name to retrieve element (str or None) + - wait: Custom wait timeout in seconds (float or None) + - element_params: List of element parameter tuples + - parent_params: List of parent parameter tuples + - And other parameter lists... + """ + params = { + 'index': None, + 'allow_hidden': False, + 'save_parameter': None, + 'get_parameter': None, + 'wait': None, + 'element_params': [], + 'parent_params': [], + 'child_params': [], + 'sibling_params': [], + 'unique_params': [], + 'shadow_root_params': [], + } + + for left, mid, right in step_data: + left_lower = left.strip().lower() + mid_lower = mid.strip().lower() + right_stripped = right.strip() + + # Save parameter + if mid_lower == "save parameter": + if right_stripped != "ignore": + params['save_parameter'] = left.strip() + + # Get parameter + elif mid_lower == "get parameter": + if right_stripped.startswith("%|") and right_stripped.endswith("|%"): + params['get_parameter'] = right_stripped.strip("%").strip("|") + + # Optional parameters + elif mid_lower == "optional parameter": + if left_lower in ("allow hidden", "allow disable"): + params['allow_hidden'] = right_stripped.lower() in ("yes", "true", "ok", "1") + elif left_lower == "wait": + params['wait'] = float(right_stripped) + + # Element parameters + elif mid_lower == "element parameter": + if left_lower == "index": + try: + params['index'] = int(right_stripped) + except ValueError: + pass + else: + params['element_params'].append((left.strip(), right_stripped)) + + # Unique parameter + elif mid_lower == "unique parameter": + params['unique_params'].append((left.strip(), right_stripped)) + + # Parent parameters (including numbered: parent 2 parameter) + elif "parent" in mid_lower and "parameter" in mid_lower: + params['parent_params'].append((left.strip(), mid.strip(), right_stripped)) + + # Child parameters + elif "child" in mid_lower and "parameter" in mid_lower: + params['child_params'].append((left.strip(), mid.strip(), right_stripped)) + + # Sibling parameters + elif "sibling" in mid_lower and "parameter" in mid_lower: + params['sibling_params'].append((left.strip(), mid.strip(), right_stripped)) + + # Shadow root parameters + elif mid_lower.startswith("sr"): + params['shadow_root_params'].append((left.strip(), mid.strip(), right_stripped)) + + return params + + +def _build_locator(page, step_data, params): + """ + Build a Playwright Locator from step data. + + Attempts these strategies in order: + 1. Playwright-native selectors (test-id, role, text, etc.) - fastest + 2. Direct xpath/css if provided + 3. Build xpath from element parameters using existing logic + """ + + # Strategy 1: Check for Playwright-native selectors (fastest path) + for left, right in params['element_params']: + left_lower = left.lower() + + # Test ID selectors + if left_lower in ("test-id", "testid", "data-testid", "data-test-id"): + return page.get_by_test_id(right) + + # Role selector + if left_lower == "role": + # Check if there's a name parameter too + name = None + for l, r in params['element_params']: + if l.lower() in ("name", "role name", "aria-label"): + name = r + break + if name: + return page.get_by_role(right, name=name) + return page.get_by_role(right) + + # Text selectors + if left_lower == "text": + return page.get_by_text(right, exact=True) + if left_lower == "*text": + return page.get_by_text(right, exact=False) + if left_lower == "**text": + # Case-insensitive partial match + return page.get_by_text(re.compile(re.escape(right), re.IGNORECASE)) + + # Label selector + if left_lower == "label": + return page.get_by_label(right) + + # Placeholder selector + if left_lower == "placeholder": + return page.get_by_placeholder(right) + + # Alt text selector + if left_lower in ("alt", "alt text", "alt-text"): + return page.get_by_alt_text(right) + + # Title selector + if left_lower == "title" and "parameter" not in params.get('mid', ''): + return page.get_by_title(right) + + # Direct xpath + if left_lower == "xpath": + return page.locator(f"xpath={right}") + + # Direct CSS selector + if left_lower in ("css", "css selector", "css_selector"): + return page.locator(right) + + # Strategy 2: Check for unique parameters + for left, right in params['unique_params']: + left_lower = left.lower() + + if left_lower == "id": + return page.locator(f"#{right}") + elif left_lower == "name": + return page.locator(f"[name='{right}']") + elif left_lower == "class": + return page.locator(f".{right}") + elif left_lower == "tag": + return page.locator(right) + + # Strategy 3: Build xpath from element/parent/child parameters + xpath = _build_xpath_from_params(step_data, params) + if xpath: + CommonUtil.ExecLog( + "_build_locator", + f"Built xpath from parameters: {xpath}", + 5 + ) + return page.locator(f"xpath={xpath}") + + # Strategy 4: Simple element parameters as xpath + if params['element_params']: + xpath_parts = [] + tag = "*" + + for left, right in params['element_params']: + left_lower = left.lower() + + if left_lower == "tag": + tag = right + elif left_lower == "id": + xpath_parts.append(f"@id='{right}'") + elif left_lower == "name": + xpath_parts.append(f"@name='{right}'") + elif left_lower == "class": + xpath_parts.append(f"contains(@class,'{right}')") + elif left_lower.startswith("*"): + # Partial match + attr = left_lower[1:] + xpath_parts.append(f"contains(@{attr},'{right}')") + elif left_lower.startswith("**"): + # Case-insensitive partial match + attr = left_lower[2:] + xpath_parts.append( + f"contains(translate(@{attr},'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'),'{right.lower()}')" + ) + elif left_lower not in ("index", "text", "*text", "**text"): + # Generic attribute + xpath_parts.append(f"@{left}='{right}'") + + if xpath_parts: + xpath = f"//{tag}[{' and '.join(xpath_parts)}]" + return page.locator(f"xpath={xpath}") + elif tag != "*": + return page.locator(tag) + + return None + + +def _build_xpath_from_params(step_data, params): + """ + Build complex xpath from element/parent/child/sibling parameters. + + This reuses the logic from LocateElement._construct_query() but simplified + for the most common cases. + """ + try: + # Import the existing query builder for complex cases + from Framework.Built_In_Automation.Shared_Resources import LocateElement + + # Filter step_data to only include element-related rows + element_rows = [] + for left, mid, right in step_data: + mid_lower = mid.strip().lower().replace(" ", "") + if any(x in mid_lower for x in [ + "elementparameter", "parentparameter", "childparameter", + "siblingparameter", "uniqueparameter", "precedingparameter", + "followingparameter" + ]): + element_rows.append((left, mid, right)) + + if not element_rows: + return None + + # Use existing query builder + # Temporarily set driver_type to selenium for xpath generation + original_driver_type = getattr(LocateElement, 'driver_type', None) + LocateElement.driver_type = "selenium" + + try: + xpath, query_type = LocateElement._construct_query(element_rows) + if xpath and query_type in ("xpath", "css"): + return xpath + finally: + if original_driver_type is not None: + LocateElement.driver_type = original_driver_type + + except Exception as e: + CommonUtil.ExecLog( + "_build_xpath_from_params", + f"Error building xpath: {e}", + 2 + ) + + return None + + +def handle_shadow_dom(page, shadow_params, element_params): + """ + Handle Shadow DOM element location. + + Playwright supports automatic shadow DOM piercing with the >> selector. + + Args: + page: Playwright Page object + shadow_params: List of shadow root parameters + element_params: Final element parameters + + Returns: + Locator for element inside shadow DOM + """ + sModuleInfo = "handle_shadow_dom" + + try: + # Build a chain of selectors using Playwright's shadow-piercing >> + # Sort shadow params by their index (sr 1, sr 2, etc.) + sorted_params = sorted(shadow_params, key=lambda x: _extract_sr_index(x[1])) + + selector_parts = [] + + for left, mid, right in sorted_params: + left_lower = left.lower() + if left_lower == "tag": + selector_parts.append(right) + elif left_lower == "id": + selector_parts.append(f"#{right}") + elif left_lower == "class": + selector_parts.append(f".{right}") + else: + selector_parts.append(f"[{left}='{right}']") + + # Add final element + for left, right in element_params: + left_lower = left.lower() + if left_lower == "tag": + selector_parts.append(right) + elif left_lower == "id": + selector_parts.append(f"#{right}") + elif left_lower == "class": + selector_parts.append(f".{right}") + else: + selector_parts.append(f"[{left}='{right}']") + + # Join with >> for shadow DOM piercing + full_selector = " >> ".join(selector_parts) + CommonUtil.ExecLog(sModuleInfo, f"Shadow DOM selector: {full_selector}", 5) + + return page.locator(full_selector) + + except Exception: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +def _extract_sr_index(mid_value): + """Extract index from 'sr N element parameter' format.""" + try: + parts = mid_value.lower().split() + for i, part in enumerate(parts): + if part == "sr" and i + 1 < len(parts): + return int(parts[i + 1]) + except (ValueError, IndexError): + pass + return 1 + + +def wait_for_element(step_data, page, state="visible", timeout=None): + """ + Wait for element to reach a specific state. + + Args: + step_data: Standard step data format + page: Playwright Page object + state: One of "attached", "detached", "visible", "hidden" + timeout: Timeout in milliseconds + + Returns: + "passed" | "zeuz_failed" + """ + sModuleInfo = "wait_for_element" + + try: + locator = Get_Element(step_data, page) + if locator == "zeuz_failed": + return "zeuz_failed" + + if timeout is None: + default_wait = sr.Get_Shared_Variables("element_wait") + if default_wait not in failed_tag_list: + timeout = int(float(default_wait) * 1000) + else: + timeout = 10000 + + locator.wait_for(state=state, timeout=timeout) + CommonUtil.ExecLog(sModuleInfo, f"Element reached state: {state}", 1) + return "passed" + + except Exception as e: + CommonUtil.ExecLog(sModuleInfo, f"Wait for element failed: {e}", 3) + return "zeuz_failed"