From 45316e537f04c3ea594d10edf2a436e3c3412355 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Feb 2026 12:02:17 +0000 Subject: [PATCH] Add Playwright support as alternative web automation driver Implements complete Playwright integration allowing users to switch from Selenium to Playwright by changing "selenium action" to "playwright action" in test steps while keeping all element parameters identical. Key features: - Native Playwright Locator API for faster execution with auto-wait - Full support for all existing element parameters (parent, child, sibling, etc.) - Parameter compatibility: use js -> force, delay -> type delay, etc. - Playwright-specific features: tracing, network interception - Shared xpath/css query building with Playwright-native execution New files: - Web/Playwright/BuiltInFunctions.py - All action implementations - Web/Playwright/locator.py - Element location with native Playwright API - action_declarations/playwright.py - Action declarations https://claude.ai/code/session_01VibqKxxfPc8Npuk5tXfvCf --- .../action_declarations/info.py | 2 + .../action_declarations/playwright.py | 101 + .../Sequential_Actions/sequential_actions.py | 5 + .../Web/Playwright/BuiltInFunctions.py | 2566 +++++++++++++++++ .../Web/Playwright/__init__.py | 20 + .../Web/Playwright/locator.py | 507 ++++ 6 files changed, 3201 insertions(+) create mode 100644 Framework/Built_In_Automation/Sequential_Actions/action_declarations/playwright.py create mode 100644 Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py create mode 100644 Framework/Built_In_Automation/Web/Playwright/__init__.py create mode 100644 Framework/Built_In_Automation/Web/Playwright/locator.py 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"