From b60764814fea10ca877ec5347021b09c07ed8303 Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 11:57:02 +0600 Subject: [PATCH 01/10] Add utility functions for browser sessions --- Framework/Built_In_Automation/Web/utils.py | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Framework/Built_In_Automation/Web/utils.py diff --git a/Framework/Built_In_Automation/Web/utils.py b/Framework/Built_In_Automation/Web/utils.py new file mode 100644 index 00000000..7678824a --- /dev/null +++ b/Framework/Built_In_Automation/Web/utils.py @@ -0,0 +1,69 @@ +from Framework.Built_In_Automation.Shared_Resources import ( + BuiltInFunctionSharedResources as sr, +) + + + +def initialize_browser_sessions(): + """ + Checks if `browser_sessions` shared variable is already initialized. + If not, initializes it as an empty dictionary. + """ + + if sr.Test_Shared_Variables("browser_sessions") == False: + sr.Set_Shared_Variables("browser_sessions", {}) + + +def create_browser_session( + session_name: str = "default", + selenium_driver: WebDriver | None = None, + playwright_page: Page | None = None, + playwright_browser: Browser | None = None, + playwright_context: BrowserContext | None = None, + playwright_frame: Frame | None = None +) -> dict: + """ + Creates a new browser session with the given parameters. + Replaces the session if it already exists with the given name. + + Args: + session_name (str): The name of the session. + selenium_driver (WebDriver): The Selenium WebDriver instance. + playwright_page (Page): The Playwright Page instance. + playwright_browser (Browser): The Playwright Browser instance. + playwright_context (BrowserContext): The Playwright BrowserContext instance. + playwright_frame (Frame): The Playwright Frame instance. + """ + + if sr.Test_Shared_Variables("browser_sessions") == False: + initialize_browser_sessions() + + browser_sessions = sr.Get_Shared_Variables("browser_sessions") + browser_sessions[session_name] = { + "selenium_driver": selenium_driver, + "playwright_page": playwright_page, + "playwright_browser": playwright_browser, + "playwright_context": playwright_context, + "playwright_frame": playwright_frame + } + sr.Set_Shared_Variables("browser_sessions", browser_sessions) + + return browser_sessions[session_name] + + +def get_browser_session(session_name: str) -> dict: + """ + Returns the browser session with the given name. + + Args: + session_name (str): The name of the session. + + Returns: + dict: The browser session with the given name. + """ + + if sr.Test_Shared_Variables("browser_sessions") == False: + initialize_browser_sessions() + + browser_sessions = sr.Get_Shared_Variables("browser_sessions") + return browser_sessions.get(session_name, {}) From a9b1df513448fc9d181d005cad4f3fb748c55db5 Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 12:28:57 +0600 Subject: [PATCH 02/10] Fix type checking --- Framework/Built_In_Automation/Web/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Framework/Built_In_Automation/Web/utils.py b/Framework/Built_In_Automation/Web/utils.py index 7678824a..8329b25c 100644 --- a/Framework/Built_In_Automation/Web/utils.py +++ b/Framework/Built_In_Automation/Web/utils.py @@ -1,6 +1,8 @@ from Framework.Built_In_Automation.Shared_Resources import ( BuiltInFunctionSharedResources as sr, ) +from playwright.async_api import Browser, BrowserContext, Frame, Page +from selenium.webdriver import Chrome, Firefox, Edge, Safari @@ -16,7 +18,7 @@ def initialize_browser_sessions(): def create_browser_session( session_name: str = "default", - selenium_driver: WebDriver | None = None, + selenium_driver: Chrome | Firefox | Edge | Safari | None = None, playwright_page: Page | None = None, playwright_browser: Browser | None = None, playwright_context: BrowserContext | None = None, @@ -28,7 +30,7 @@ def create_browser_session( Args: session_name (str): The name of the session. - selenium_driver (WebDriver): The Selenium WebDriver instance. + selenium_driver (Chrome | Firefox | Edge | Safari): The Selenium WebDriver instance. playwright_page (Page): The Playwright Page instance. playwright_browser (Browser): The Playwright Browser instance. playwright_context (BrowserContext): The Playwright BrowserContext instance. From dab66f3f4b2183d32192783e8bc60821b561732e Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 12:30:01 +0600 Subject: [PATCH 03/10] Add session creation in Selenium open browser action --- .../Web/Selenium/BuiltInFunctions.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index 49b578b8..f376c67d 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -671,11 +671,11 @@ async def connect_playwright_to_selenium(port=9222): Shared_Resources.Set_Shared_Variables("playwright_browser", browser) Shared_Resources.Set_Shared_Variables("playwright_page", page) - return "passed" + return browser, context, page @logger -async def Open_Browser(browser, browser_options: BrowserOptions): +async def Open_Browser(browser, browser_options: BrowserOptions, session_name: str = "default"): """Launch browser from options and service object""" try: global selenium_driver @@ -778,12 +778,27 @@ async def Open_Browser(browser, browser_options: BrowserOptions): return "zeuz_failed" # Connect Playwright to Selenium via CDP + playwright_browser = None + playwright_context = None + playwright_page = None try: - await connect_playwright_to_selenium(port=9222) + playwright_browser, playwright_context, playwright_page = await connect_playwright_to_selenium(port=9222) CommonUtil.ExecLog(sModuleInfo, "Connected Playwright to Selenium", 1) except Exception as e: CommonUtil.ExecLog(sModuleInfo, f"Failed to connect Playwright to Selenium: {e}", 3) + # Create browser session + from Framework.Built_In_Automation.Web.utils import create_browser_session + session = create_browser_session( + session_name=session_name, + selenium_driver=selenium_driver, + playwright_page=playwright_page, + playwright_browser=playwright_browser, + playwright_context=playwright_context, + playwright_frame=None + ) + CommonUtil.ExecLog(sModuleInfo, f"Created browser session: {session_name=}", 5) + CommonUtil.ExecLog(sModuleInfo, f"Started {browser} browser", 1) Shared_Resources.Set_Shared_Variables("selenium_driver", selenium_driver) CommonUtil.set_screenshot_vars(Shared_Resources.Shared_Variable_Export()) @@ -1018,6 +1033,7 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: chrome_version = None chrome_channel = None debug_port = False + session_name = "default" for left, mid, right in dataset: left = left.replace(" ", "").replace("_", "").replace("-", "").lower() @@ -1037,6 +1053,8 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: window_size_Y = int(resolution[1]) elif left == "chrome:version": chrome_version = right.strip() + elif left == "session": + session_name = right.strip() # Capabilities are WebDriver attribute common across different browser elif mid.strip().lower() == "shared capability": @@ -1145,7 +1163,7 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: sModuleInfo, "Browser not previously opened, doing so now", 1 ) - if await Open_Browser(dependency["Browser"], browser_options) == "zeuz_failed": + if await Open_Browser(dependency["Browser"], browser_options, session_name) == "zeuz_failed": return "zeuz_failed" if ConfigModule.get_config_value( @@ -1219,7 +1237,7 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: # If the browser is closed but selenium instance is on, relaunch selenium_driver if Shared_Resources.Test_Shared_Variables("dependency"): dependency = Shared_Resources.Get_Shared_Variables("dependency") - result = await Open_Browser(dependency["Browser"], browser_options) + result = await Open_Browser(dependency["Browser"], browser_options, session_name) else: result = "zeuz_failed" From 422a0abd3e086abedf0144d2b1bfe7044636d3d1 Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 12:44:35 +0600 Subject: [PATCH 04/10] Add type checking for `selenium_driver` --- .../Web/Selenium/BuiltInFunctions.py | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index f376c67d..53253d9f 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -777,27 +777,30 @@ async def Open_Browser(browser, browser_options: BrowserOptions, session_name: s ) return "zeuz_failed" - # Connect Playwright to Selenium via CDP - playwright_browser = None - playwright_context = None - playwright_page = None - try: - playwright_browser, playwright_context, playwright_page = await connect_playwright_to_selenium(port=9222) - CommonUtil.ExecLog(sModuleInfo, "Connected Playwright to Selenium", 1) - except Exception as e: - CommonUtil.ExecLog(sModuleInfo, f"Failed to connect Playwright to Selenium: {e}", 3) - - # Create browser session - from Framework.Built_In_Automation.Web.utils import create_browser_session - session = create_browser_session( - session_name=session_name, - selenium_driver=selenium_driver, - playwright_page=playwright_page, - playwright_browser=playwright_browser, - playwright_context=playwright_context, - playwright_frame=None - ) - CommonUtil.ExecLog(sModuleInfo, f"Created browser session: {session_name=}", 5) + # If selenium_driver is of type Webdriver + from selenium.webdriver import Chrome, Firefox, Edge, Safari + if isinstance(selenium_driver, (Chrome, Firefox, Edge, Safari)): + # Connect Playwright to Selenium via CDP + playwright_browser = None + playwright_context = None + playwright_page = None + try: + playwright_browser, playwright_context, playwright_page = await connect_playwright_to_selenium(port=9222) + CommonUtil.ExecLog(sModuleInfo, "Connected Playwright to Selenium", 1) + except Exception as e: + CommonUtil.ExecLog(sModuleInfo, f"Failed to connect Playwright to Selenium: {e}", 3) + + # Create browser session + from Framework.Built_In_Automation.Web.utils import create_browser_session + session = create_browser_session( + session_name=session_name, + selenium_driver=selenium_driver, + playwright_page=playwright_page, + playwright_browser=playwright_browser, + playwright_context=playwright_context, + playwright_frame=None + ) + CommonUtil.ExecLog(sModuleInfo, f"Created browser session: {session_name=}", 5) CommonUtil.ExecLog(sModuleInfo, f"Started {browser} browser", 1) Shared_Resources.Set_Shared_Variables("selenium_driver", selenium_driver) From ec2751bfc03ba375ad3440e5b133ecff5447cf2e Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 13:20:23 +0600 Subject: [PATCH 05/10] Add session creation in Playwright open browser action --- .../Web/Playwright/BuiltInFunctions.py | 21 +++++++++++++++---- .../Web/Selenium/BuiltInFunctions.py | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py index bfca34b7..78e297e2 100644 --- a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py @@ -77,7 +77,7 @@ def connect_selenium_to_playwright(port=9222): sr.Set_Shared_Variables("selenium_driver", driver) CommonUtil.ExecLog("connect_selenium_to_playwright", "Connected Selenium to Playwright", 1) - return "passed" + return driver except Exception as e: CommonUtil.ExecLog("connect_selenium_to_playwright", f"Failed to connect Selenium to Playwright: {e}", 3) @@ -168,8 +168,6 @@ async def Open_Browser(step_data): 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": @@ -199,6 +197,8 @@ async def Open_Browser(step_data): color_scheme = right_v elif left_l == "permission": permissions.append(right_v) + elif left_l in ("driver id", "page id", "driver tag", "session"): + page_id = right_v elif mid_l == "shared capability": # Handle Selenium-style capabilities where possible @@ -293,7 +293,20 @@ async def Open_Browser(step_data): CommonUtil.set_screenshot_vars(sr.Shared_Variable_Export()) # Connect Selenium to Playwright via CDP - connect_selenium_to_playwright(port=9222) + selenium_driver = connect_selenium_to_playwright(port=9222) + + # Create browser session + from Framework.Built_In_Automation.Web.utils import create_browser_session + + create_browser_session( + session_name=page_id, + selenium_driver=selenium_driver, + playwright_page=current_page, + playwright_browser=browser, + playwright_context=context, + playwright_frame=None + ) + CommonUtil.ExecLog(sModuleInfo, f"Created browser session: {page_id}", 5) CommonUtil.ExecLog(sModuleInfo, f"Browser opened successfully (page_id: {page_id})", 1) return "passed" diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index 53253d9f..73e05280 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -792,7 +792,7 @@ async def Open_Browser(browser, browser_options: BrowserOptions, session_name: s # Create browser session from Framework.Built_In_Automation.Web.utils import create_browser_session - session = create_browser_session( + create_browser_session( session_name=session_name, selenium_driver=selenium_driver, playwright_page=playwright_page, From 816928d47123142b0c4d1c3539ba443e4d7a24fd Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 15:58:16 +0600 Subject: [PATCH 06/10] Add session checking in Playwright go to link action --- .../Web/Playwright/BuiltInFunctions.py | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py index 78e297e2..b96c794a 100644 --- a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py @@ -332,7 +332,59 @@ async def Go_To_Link(step_data): global current_page try: - if current_page is None: + # Parse session parameter first + session_name = 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" and left_l == "session": + session_name = right_v + break + + # Check if session exists and use it + if session_name: + from Framework.Built_In_Automation.Web.utils import get_browser_session + existing_session = get_browser_session(session_name) + + if existing_session and existing_session.get("playwright_page"): + # Session exists, use existing browser + current_page = existing_session["playwright_page"] + context = existing_session["playwright_context"] + browser = existing_session["playwright_browser"] + current_page_id = session_name + + # Update globals + globals().update({ + 'current_page': current_page, + 'context': context, + 'browser': browser, + 'current_page_id': current_page_id + }) + + # Update shared variables + sr.Set_Shared_Variables("playwright_page", current_page) + sr.Set_Shared_Variables("playwright_context", context) + sr.Set_Shared_Variables("playwright_browser", browser) + + CommonUtil.ExecLog(sModuleInfo, f"Using existing browser session: {session_name}", 1) + else: + # Session doesn't exist, open new browser with session name + CommonUtil.ExecLog(sModuleInfo, f"Session '{session_name}' not found. Opening new browser.", 2) + + # Add session parameter to step_data for Open_Browser + step_data_with_session = step_data.copy() + if not any(left.strip().lower() == "session" and mid.strip().lower() == "optional parameter" for left, mid, right in step_data_with_session): + step_data_with_session.append(("session", "optional parameter", session_name)) + + result = await Open_Browser(step_data_with_session) + if result == "zeuz_failed": + CommonUtil.ExecLog(sModuleInfo, "Failed to open browser for new session", 3) + return "zeuz_failed" + + elif current_page is None: + # No session specified and no browser open CommonUtil.ExecLog(sModuleInfo, "No browser open. Opening browser with default settings.", 2) result = await Open_Browser(step_data) if result == "zeuz_failed": @@ -351,7 +403,10 @@ async def Go_To_Link(step_data): 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 time"): + if left_l == "session": + # Skip session parameter - already processed above + continue + elif left_l in ("wait until", "wait_until", "waituntil", "wait time"): wait_until = right_v.lower() elif left_l == "timeout": timeout = int(float(right_v) * 1000) From 13653a4fe4338d123fe1be442ce16272bbceee9b Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 21:09:24 +0600 Subject: [PATCH 07/10] Add session checking in Selenium go to link action --- .../Web/Selenium/BuiltInFunctions.py | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index 73e05280..775324b1 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -72,6 +72,7 @@ from .utils import ChromeForTesting, ChromeExtensionDownloader from playwright.async_api import async_playwright +from Framework.Built_In_Automation.Web.utils import get_browser_session, create_browser_session ######################### # # @@ -466,7 +467,12 @@ def Open_Electron_App(data_set): opts = Options() opts.binary_location = desktop_app_path - opts.add_argument("--remote-debugging-port=9222") + # Generate unique port for Electron app based on driver_id + import hashlib + port_hash = int(hashlib.md5((driver_id or "electron").encode()).hexdigest(), 16) + electron_port = 9230 + (port_hash % 90) # Range 9230-9320 to avoid conflicts with browser sessions + opts.add_argument(f"--remote-debugging-port={electron_port}") + CommonUtil.ExecLog(sModuleInfo, f"Using remote debugging port {electron_port} for Electron app", 1) # service = Service(executable_path=electron_chrome_path) arch = platform.machine().lower() if platform.system() == "Darwin" and arch == "arm64": @@ -705,8 +711,13 @@ async def Open_Browser(browser, browser_options: BrowserOptions, session_name: s options = generate_options(browser, browser_options) - # Enable remote debugging / CDP - options.add_argument("--remote-debugging-port=9222") + # Enable remote debugging / CDP with unique port per session + import hashlib + # Generate unique port based on session name (range 9222-9322 to avoid conflicts) + port_hash = int(hashlib.md5(session_name.encode()).hexdigest(), 16) + unique_port = 9222 + (port_hash % 100) + options.add_argument(f"--remote-debugging-port={unique_port}") + CommonUtil.ExecLog(sModuleInfo, f"Using remote debugging port {unique_port} for session '{session_name}'", 1) if browser in ("android", "chrome", "chromeheadless"): from selenium.webdriver.chrome.service import Service @@ -785,13 +796,12 @@ async def Open_Browser(browser, browser_options: BrowserOptions, session_name: s playwright_context = None playwright_page = None try: - playwright_browser, playwright_context, playwright_page = await connect_playwright_to_selenium(port=9222) + playwright_browser, playwright_context, playwright_page = await connect_playwright_to_selenium(port=unique_port) CommonUtil.ExecLog(sModuleInfo, "Connected Playwright to Selenium", 1) except Exception as e: CommonUtil.ExecLog(sModuleInfo, f"Failed to connect Playwright to Selenium: {e}", 3) # Create browser session - from Framework.Built_In_Automation.Web.utils import create_browser_session create_browser_session( session_name=session_name, selenium_driver=selenium_driver, @@ -1043,7 +1053,7 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: if left == "gotolink": web_link = right.strip() elif left == "driverid": - driver_id = right.strip() + session_name = driver_id = right.strip() elif left in ("waittimetoappearelement", "waitforelement"): Shared_Resources.Set_Shared_Variables( "element_wait", float(right.strip()) @@ -1057,7 +1067,7 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: elif left == "chrome:version": chrome_version = right.strip() elif left == "session": - session_name = right.strip() + session_name = driver_id = right.strip() # Capabilities are WebDriver attribute common across different browser elif mid.strip().lower() == "shared capability": @@ -1169,6 +1179,8 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: if await Open_Browser(dependency["Browser"], browser_options, session_name) == "zeuz_failed": return "zeuz_failed" + selenium_driver = get_browser_session(session_name)["selenium_driver"] + if ConfigModule.get_config_value( "RunDefinition", "window_size_x" ) and ConfigModule.get_config_value("RunDefinition", "window_size_y"): @@ -1193,7 +1205,7 @@ async def Go_To_Link(dataset: Dataset) -> ReturnType: "remote-debugging-port": debug_port } else: - selenium_driver = selenium_details[driver_id]["driver"] + selenium_driver = get_browser_session(session_name)["selenium_driver"] Shared_Resources.Set_Shared_Variables("selenium_driver", selenium_driver) current_driver_id = driver_id except Exception: @@ -4622,16 +4634,23 @@ def drag_and_drop(dataset): def playwright(dataset): sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME global selenium_driver + global selenium_details + global current_driver_id try: from playwright.sync_api import sync_playwright + # Get the correct remote debugging port for current driver + debug_port = 9222 # fallback + if current_driver_id and current_driver_id in selenium_details: + debug_port = selenium_details[current_driver_id].get("remote-debugging-port", 9222) + devtools_url = ( selenium_driver.command_executor._url.replace("http://", "ws://") + "/devtools/browser" ) with sync_playwright() as p: # browser = p.chromium.connect(browserURL=devtools_url) - browser = p.chromium.connect_over_cdp("http://localhost:9222") + browser = p.chromium.connect_over_cdp(f"http://localhost:{debug_port}") page = browser.contexts[0].pages[0] # source = page.locator("//div[contains(text(), 'abcd')]") From ea06e997ba5611e4af36ad0dff38830b00202be5 Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 21:15:41 +0600 Subject: [PATCH 08/10] Add CDP port hashing to Playwright open browser action Similar to Selenium open browser action --- .../Web/Playwright/BuiltInFunctions.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py index b96c794a..7c76b886 100644 --- a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py @@ -220,8 +220,13 @@ async def Open_Browser(step_data): "devtools": devtools, } - # Add remote debugging port for CDP connection - all_args = args + ["--remote-debugging-port=9222"] + # Add remote debugging port for CDP connection with unique port per session + import hashlib + # Generate unique port based on page_id (range 9222-9322 to avoid conflicts) + port_hash = int(hashlib.md5(page_id.encode()).hexdigest(), 16) + unique_port = 9222 + (port_hash % 100) + all_args = args + [f"--remote-debugging-port={unique_port}"] + CommonUtil.ExecLog(sModuleInfo, f"Using remote debugging port {unique_port} for session '{page_id}'", 1) if all_args: launch_options["args"] = all_args if downloads_path: @@ -293,7 +298,7 @@ async def Open_Browser(step_data): CommonUtil.set_screenshot_vars(sr.Shared_Variable_Export()) # Connect Selenium to Playwright via CDP - selenium_driver = connect_selenium_to_playwright(port=9222) + selenium_driver = connect_selenium_to_playwright(port=unique_port) # Create browser session from Framework.Built_In_Automation.Web.utils import create_browser_session From 0b255b1ecfb2463be10e9c8a590a4bbffd96709c Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 21:32:20 +0600 Subject: [PATCH 09/10] Add session checking to commonly used actions in Playwright and Selenium --- .../Web/Playwright/BuiltInFunctions.py | 77 +++++++++++++++++++ .../Web/Selenium/BuiltInFunctions.py | 70 +++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py index 7c76b886..12f7444d 100644 --- a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py @@ -112,6 +112,69 @@ def connect_selenium_to_playwright(port=9222): # # ######################### +def _handle_playwright_session(step_data): + """ + Helper function to handle session parameter for Playwright actions. + + Args: + step_data: The step data containing potential session parameter + + Returns: + tuple: (session_name, current_page, current_page_id, context, browser) + - session_name: The session name found or None + - current_page: The appropriate page instance + - current_page_id: The current page ID + - context: The browser context + - browser: The browser instance + """ + global current_page, current_page_id, context, browser + + # Parse session parameter + session_name = 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" and left_l == "session": + session_name = right_v + break + + # If session parameter is provided, switch to that session + if session_name: + from Framework.Built_In_Automation.Web.utils import get_browser_session + existing_session = get_browser_session(session_name) + + if existing_session and existing_session.get("playwright_page"): + # Session exists, use existing browser + current_page = existing_session["playwright_page"] + context = existing_session["playwright_context"] + browser = existing_session["playwright_browser"] + current_page_id = session_name + + # Update globals + globals().update({ + 'current_page': current_page, + 'context': context, + 'browser': browser, + 'current_page_id': current_page_id + }) + + # Update shared variables + sr.Set_Shared_Variables("playwright_page", current_page) + sr.Set_Shared_Variables("playwright_context", context) + sr.Set_Shared_Variables("playwright_browser", browser) + + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + CommonUtil.ExecLog(sModuleInfo, f"Using existing browser session: {session_name}", 1) + else: + # Session doesn't exist + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + CommonUtil.ExecLog(sModuleInfo, f"Session '{session_name}' not found. Using current browser.", 2) + + return session_name, current_page, current_page_id, context, browser + + @logger async def Open_Browser(step_data): """ @@ -590,6 +653,9 @@ async def Click_Element(step_data): global current_page try: + # Handle session parameter + session_name, current_page, current_page_id, context, browser = _handle_playwright_session(step_data) + if current_page is None: CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) return "zeuz_failed" @@ -609,6 +675,10 @@ async def Click_Element(step_data): mid_l = mid.strip().lower() right_v = right.strip() + # Skip session parameter - already handled above + if mid_l == "optional parameter" and left_l == "session": + continue + if mid_l == "optional parameter": if left_l == "use js": use_js = right_v.lower() in ("true", "yes", "1") @@ -796,6 +866,9 @@ async def Enter_Text_In_Text_Box(step_data): global current_page try: + # Handle session parameter + session_name, current_page, current_page_id, context, browser = _handle_playwright_session(step_data) + if current_page is None: CommonUtil.ExecLog(sModuleInfo, "No browser open", 3) return "zeuz_failed" @@ -810,6 +883,10 @@ async def Enter_Text_In_Text_Box(step_data): left_l = left.strip().lower() mid_l = mid.strip().lower() + # Skip session parameter - already handled above + if mid_l == "optional parameter" and left_l == "session": + continue + if mid_l == "action": text_value = right # Don't strip - preserve whitespace elif mid_l == "optional parameter": diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index 775324b1..95594f3d 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -150,6 +150,50 @@ ReturnType = Literal["passed", "zeuz_failed"] +def _handle_selenium_session(step_data): + """ + Helper function to handle session parameter for Selenium actions. + + Args: + step_data: The step data containing potential session parameter + + Returns: + tuple: (session_name, selenium_driver, current_driver_id) + - session_name: The session name found or None + - selenium_driver: The appropriate selenium driver instance + - current_driver_id: The current driver ID + """ + global selenium_driver, current_driver_id + + # Parse session parameter + session_name = None + for left, mid, right in step_data: + left = left.replace(" ", "").replace("_", "").replace("-", "").lower() + if left == "session" and mid.strip().lower() == "optional parameter": + session_name = right.strip() + break + + # If session parameter is provided, switch to that session + if session_name: + from Framework.Built_In_Automation.Web.utils import get_browser_session + existing_session = get_browser_session(session_name) + + if existing_session and existing_session.get("selenium_driver"): + # Session exists, use existing driver + selenium_driver = existing_session["selenium_driver"] + current_driver_id = session_name + Shared_Resources.Set_Shared_Variables("selenium_driver", selenium_driver) + + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + CommonUtil.ExecLog(sModuleInfo, f"Using existing browser session: {session_name}", 1) + else: + # Session doesn't exist + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + CommonUtil.ExecLog(sModuleInfo, f"Session '{session_name}' not found. Using current browser.", 2) + + return session_name, selenium_driver, current_driver_id + + class DefaultChromiumArguments(TypedDict): add_argument: list[str] add_experimental_option: dict[str, Any] @@ -1645,6 +1689,10 @@ def Enter_Text_In_Text_Box(step_data): use_js = False clear = True global selenium_driver + + # Handle session parameter + session_name, selenium_driver, current_driver_id = _handle_selenium_session(step_data) + Element = LocateElement.Get_Element(step_data, selenium_driver) if Element == "zeuz_failed": CommonUtil.ExecLog( @@ -1654,6 +1702,9 @@ def Enter_Text_In_Text_Box(step_data): for left, mid, right in step_data: mid = mid.strip().lower() left = left.strip().lower() + # Skip session parameter - already handled above + if left == "session" and mid == "optional parameter": + continue if mid == "action": text_value = right elif left == "delay": @@ -2088,8 +2139,14 @@ def Click_Element(data_set, retry=0): global selenium_driver use_js = False # Use js to click on element? try: + # Handle session parameter + session_name, selenium_driver, current_driver_id = _handle_selenium_session(data_set) + location = "" for row in data_set: + # Skip session parameter - already handled above + if row[0].lower().replace(" ", "").replace("_", "").replace("-", "") == "session" and row[1].strip().lower() == "optional parameter": + continue if row[0] == "offset" and row[1] == "optional parameter": location = row[ 2 @@ -3035,6 +3092,9 @@ def Validate_Text(step_data): sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME global selenium_driver try: + # Handle session parameter + session_name, selenium_driver, current_driver_id = _handle_selenium_session(step_data) + Element = LocateElement.Get_Element(step_data, selenium_driver) ignore_case = False zeuz_ai = None @@ -3044,6 +3104,9 @@ def Validate_Text(step_data): ) return "zeuz_failed" for each_step_data_item in step_data: + # Skip session parameter - already handled above + if each_step_data_item[0].lower().replace(" ", "").replace("_", "").replace("-", "") == "session" and each_step_data_item[1].strip().lower() == "optional parameter": + continue if each_step_data_item[1] == "action": expected_text_data = each_step_data_item[2] validation_type = each_step_data_item[0] @@ -3149,6 +3212,9 @@ def Scroll(step_data): sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME global selenium_driver try: + # Handle session parameter + session_name, selenium_driver, current_driver_id = _handle_selenium_session(step_data) + Element = None get_element = False scroll_direction = "" @@ -3156,6 +3222,10 @@ def Scroll(step_data): pixel = 750 for left, mid, right in step_data: mid = mid.strip().lower() + left_clean = left.replace(" ", "").replace("_", "").replace("-", "").lower() + # Skip session parameter - already handled above + if left_clean == "session" and mid == "optional parameter": + continue if "action" in mid: scroll_direction = right.strip().lower() elif mid == "element parameter": From e8e79d1b040ebaddca24fd6556553eb7f31022fa Mon Sep 17 00:00:00 2001 From: Nasif Bin Zafar Date: Thu, 5 Mar 2026 22:16:38 +0600 Subject: [PATCH 10/10] Add session support in tear down browser actions --- .../Web/Playwright/BuiltInFunctions.py | 153 +++++++++++++----- .../Web/Selenium/BuiltInFunctions.py | 56 ++++++- 2 files changed, 170 insertions(+), 39 deletions(-) diff --git a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py index 12f7444d..c59268ac 100644 --- a/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Playwright/BuiltInFunctions.py @@ -507,57 +507,136 @@ async def Tear_Down_Playwright(step_data=None): Example: Field Sub Field Value tear down playwright action tear down + + Example with session: + Field Sub Field Value + session optional parameter my_session + 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(): + # Parse session parameter + session_name = None + if step_data: + 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 == "session": + session_name = right_v + break + + # Handle session-specific teardown + if session_name: + from Framework.Built_In_Automation.Web.utils import get_browser_session + existing_session = get_browser_session(session_name) + + if existing_session and existing_session.get("playwright_page"): + try: + # Close the specific session's page and context + session_page = existing_session["playwright_page"] + session_context = existing_session["playwright_context"] + session_browser = existing_session["playwright_browser"] + + if session_page: + await session_page.close() + if session_context: + await session_context.close() + + CommonUtil.ExecLog(sModuleInfo, f"Teared down session '{session_name}'", 1) + except Exception as e: + errMsg = f"Unable to tear down session '{session_name}'. may already been killed" + CommonUtil.ExecLog(sModuleInfo, errMsg, 2) + + # Remove session from browser_sessions + browser_sessions = sr.Get_Shared_Variables("browser_sessions", {}) + if session_name in browser_sessions: + del browser_sessions[session_name] + sr.Set_Shared_Variables("browser_sessions", browser_sessions) + + # Remove from playwright_details if present + if session_name in playwright_details: + del playwright_details[session_name] + + # If this was the current session, clear globals + if current_page_id == session_name: + current_page = None + context = None + browser = None + current_page_id = None + + # Try to switch to another available session + if playwright_details: + for page_id, details in playwright_details.items(): + current_page = details["page"] + context = details["context"] + browser = details["browser"] + current_page_id = page_id + + # Update shared variables + sr.Set_Shared_Variables("playwright_page", current_page) + sr.Set_Shared_Variables("playwright_context", context) + sr.Set_Shared_Variables("playwright_browser", browser) + + CommonUtil.ExecLog(sModuleInfo, f"Switched to session '{page_id}'", 1) + break + else: + CommonUtil.ExecLog(sModuleInfo, f"Session '{session_name}' not found. Nothing to tear down.", 2) + + # Handle full teardown (backwards compatibility) + else: + # Close all tracked pages/contexts + for page_id, details in playwright_details.items(): + try: + if details.get("page"): + await details["page"].close() + if details.get("context"): + await details["context"].close() + except Exception: + pass + + # Close main instances try: - if details.get("page"): - await details["page"].close() - if details.get("context"): - await details["context"].close() + if current_page and current_page not in [d.get("page") for d in playwright_details.values()]: + await current_page.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()]: - await current_page.close() - except Exception: - pass - - try: - if context: - await context.close() - except Exception: - pass + try: + if context: + await context.close() + except Exception: + pass - try: - if browser: - await browser.close() - except Exception: - pass + try: + if browser: + await browser.close() + except Exception: + pass - try: - if playwright_instance: - await playwright_instance.stop() - except Exception: - pass + try: + if playwright_instance: + await 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 + # Reset all globals + current_page = None + context = None + browser = None + playwright_instance = None + playwright_details = {} + current_page_id = None + + # Clear all browser sessions + sr.Set_Shared_Variables("browser_sessions", {}) - CommonUtil.ExecLog(sModuleInfo, "Browser closed successfully", 1) - return "passed" + CommonUtil.ExecLog(sModuleInfo, "Browser closed successfully", 1) + return "passed" except Exception: return CommonUtil.Exception_Handler(sys.exc_info()) diff --git a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py index 95594f3d..2772f25a 100644 --- a/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py +++ b/Framework/Built_In_Automation/Web/Selenium/BuiltInFunctions.py @@ -3502,12 +3502,62 @@ def Tear_Down_Selenium(step_data=[]): global current_driver_id try: driver_id = "" + session_name = None + + # Parse both driverid (legacy) and session (new) parameters for left, mid, right in step_data: left = left.replace(" ", "").replace("_", "").replace("-", "").lower() if left == "driverid": driver_id = right.strip() - - if not driver_id: + elif left == "session" and mid.strip().lower() == "optional parameter": + session_name = right.strip() + # For backward compatibility, treat session_name as driver_id + driver_id = session_name + + # Handle session-specific teardown + if session_name: + from Framework.Built_In_Automation.Web.utils import get_browser_session + existing_session = get_browser_session(session_name) + + if existing_session and existing_session.get("selenium_driver"): + try: + # Close the specific session's browser + session_driver = existing_session["selenium_driver"] + session_driver.quit() + CommonUtil.ExecLog(sModuleInfo, f"Teared down session '{session_name}'", 1) + except Exception as e: + errMsg = f"Unable to tear down session '{session_name}'. may already been killed" + CommonUtil.ExecLog(sModuleInfo, errMsg, 2) + CommonUtil.Exception_Handler(sys.exc_info(), None, errMsg) + + # Remove session from browser_sessions + browser_sessions = Shared_Resources.Get_Shared_Variables("browser_sessions", {}) + if session_name in browser_sessions: + del browser_sessions[session_name] + Shared_Resources.Set_Shared_Variables("browser_sessions", browser_sessions) + + # Remove from selenium_details if present + if session_name in selenium_details: + del selenium_details[session_name] + + # If this was the current driver, switch to another or clear + if current_driver_id == session_name: + if selenium_details: + for driver in selenium_details: + selenium_driver = selenium_details[driver]["driver"] + Shared_Resources.Set_Shared_Variables("selenium_driver", selenium_driver) + CommonUtil.ExecLog(sModuleInfo, f"Current driver switched to driver_id='{driver}'", 1) + current_driver_id = driver + break + else: + Shared_Resources.Remove_From_Shared_Variables("selenium_driver") + selenium_driver = None + current_driver_id = None + else: + CommonUtil.ExecLog(sModuleInfo, f"Session '{session_name}' not found. Nothing to tear down.", 2) + + # Handle existing driver_id logic (backwards compatibility) + elif not driver_id: CommonUtil.Join_Thread_and_Return_Result( "screenshot" ) # Let the capturing screenshot end in thread @@ -3525,6 +3575,8 @@ def Tear_Down_Selenium(step_data=[]): CommonUtil.ExecLog(sModuleInfo, errMsg, 2) CommonUtil.Exception_Handler(sys.exc_info(), None, errMsg) Shared_Resources.Remove_From_Shared_Variables("selenium_driver") + # Clear all browser sessions + Shared_Resources.Set_Shared_Variables("browser_sessions", {}) selenium_details = {} selenium_driver = None