@@ -3569,6 +3569,15 @@ def switch_window_or_tab(step_data):
35693569 elif left == "playwright" :
35703570 playwright_enabled = right .lower () == "true"
35713571
3572+ # Validate that at least one switching condition was provided
3573+ if not window_title_condition and not window_index_condition :
3574+ CommonUtil .ExecLog (
3575+ sModuleInfo ,
3576+ "Unable to switch window/tab: Neither 'window title'/'tab title' nor 'window index'/'tab index' was provided. Please provide either a title (exact or partial with *) or an index to switch to." ,
3577+ 3 ,
3578+ )
3579+ return "zeuz_failed"
3580+
35723581 except Exception :
35733582 CommonUtil .ExecLog (
35743583 sModuleInfo ,
@@ -3581,70 +3590,121 @@ def switch_window_or_tab(step_data):
35813590 import time # Import time for both Playwright and Selenium paths
35823591
35833592 if playwright_enabled :
3584- CommonUtil .ExecLog (sModuleInfo , "Playwright is enabled" , 1 )
3585- from playwright .sync_api import sync_playwright
3593+ CommonUtil .ExecLog (sModuleInfo , "Playwright is enabled (using async API)" , 1 )
3594+ import asyncio
3595+ from playwright .async_api import async_playwright
3596+
3597+ # Async function to handle Playwright operations
3598+ async def run_playwright_switch ():
3599+ async with async_playwright () as p :
3600+ debug_port = selenium_details [current_driver_id ][
3601+ "remote-debugging-port"
3602+ ]
3603+ browser = await p .chromium .connect_over_cdp (f"http://localhost:{ debug_port } " )
3604+ context = browser .contexts [0 ]
3605+ pages = context .pages
35863606
3587- with sync_playwright () as p :
3588- debug_port = selenium_details [current_driver_id ][
3589- "remote-debugging-port"
3590- ]
3591- browser = p .chromium .connect_over_cdp (f"http://localhost:{ debug_port } " )
3592- context = browser .contexts [0 ]
3593- pages = context .pages
3594-
3595- # Handle title-based tab switch
3596- if window_title_condition :
3597- for i , page in enumerate (pages ):
3598- page_title = page .title ()
3607+ result_data = {"status" : None , "target_url" : None , "error" : None }
3608+
3609+ # Handle title-based tab switch
3610+ if window_title_condition :
3611+ for page in pages :
3612+ page_title = await page .title ()
3613+ if (
3614+ partial_match
3615+ and switch_by_title .lower () in page_title .lower ()
3616+ ) or (
3617+ not partial_match
3618+ and switch_by_title .lower () == page_title .lower ()
3619+ ):
3620+ # Step 1: Use Playwright to switch tabs
3621+ await page .bring_to_front ()
3622+ await asyncio .sleep (1 )
3623+
3624+ # Store target URL for Selenium alignment
3625+ result_data ["target_url" ] = page .url
3626+ result_data ["status" ] = "found"
3627+ return result_data
3628+
3629+ result_data ["status" ] = "not_found"
3630+ result_data ["error" ] = f"Playwright: No tab with title '{ switch_by_title } ' found"
3631+ return result_data
3632+
3633+ # Index-based switching not supported with Playwright due to CDP ordering inconsistency
3634+ elif window_index_condition :
3635+ result_data ["status" ] = "not_supported"
3636+ result_data ["error" ] = "Index-based tab switching is not supported with Playwright. Use title-based switching instead."
3637+ return result_data
3638+
3639+ return result_data
3640+
3641+ # Run async Playwright code from sync context
3642+ try :
3643+ # We're always called from async context, so we need to run in a new thread with new event loop
3644+ loop = asyncio .get_running_loop ()
3645+ from concurrent .futures import ThreadPoolExecutor
3646+
3647+ def run_in_thread ():
3648+ new_loop = asyncio .new_event_loop ()
3649+ asyncio .set_event_loop (new_loop )
3650+ try :
3651+ return new_loop .run_until_complete (run_playwright_switch ())
3652+ finally :
3653+ new_loop .close ()
3654+
3655+ with ThreadPoolExecutor (max_workers = 1 ) as executor :
3656+ future = executor .submit (run_in_thread )
3657+ playwright_result = future .result (timeout = 30 )
3658+
3659+ if playwright_result ["status" ] == "found" :
3660+ # Step 3: Re-align Selenium to match the target tab
3661+ target_url = playwright_result ["target_url" ]
3662+ for handle in selenium_driver .window_handles :
3663+ selenium_driver .switch_to .window (handle )
35993664 if (
3600- partial_match
3601- and switch_by_title .lower () in page_title .lower ()
3602- ) or (
3603- not partial_match
3604- and switch_by_title .lower () == page_title .lower ()
3665+ selenium_driver .current_url == target_url
3666+ or target_url in selenium_driver .title
36053667 ):
3606- # Step 1: Use Playwright to switch tabs
3607- page .bring_to_front ()
3608- time .sleep (1 )
3609-
3610- # Step 3: Re-align Selenium to match the target tab
3611- target_url = page .url
3612- for handle in selenium_driver .window_handles :
3613- selenium_driver .switch_to .window (handle )
3614- if (
3615- selenium_driver .current_url == target_url
3616- or target_url in selenium_driver .title
3617- ):
3618- CommonUtil .ExecLog (
3619- sModuleInfo ,
3620- f"Selenium aligned to: { selenium_driver .title } " ,
3621- 1 ,
3622- )
3623- return "passed"
3624-
36253668 CommonUtil .ExecLog (
36263669 sModuleInfo ,
3627- "Failed to align Selenium with target tab " ,
3628- 3 ,
3670+ f" Selenium aligned to: { selenium_driver . title } " ,
3671+ 1 ,
36293672 )
3630- return "zeuz_failed"
3673+ return "passed"
3674+
36313675 CommonUtil .ExecLog (
36323676 sModuleInfo ,
3633- f"Playwright: No tab with title ' { switch_by_title } ' found " ,
3677+ "Failed to align Selenium with target tab " ,
36343678 3 ,
36353679 )
36363680 return "zeuz_failed"
3637-
3638- # Index-based switching not supported with Playwright due to CDP ordering inconsistency
3639- elif window_index_condition :
3681+
3682+ elif playwright_result ["status" ] == "not_found" :
36403683 CommonUtil .ExecLog (
36413684 sModuleInfo ,
3642- "Index-based tab switching is not supported with Playwright. Use title-based switching instead." ,
3685+ playwright_result [ "error" ] ,
36433686 3 ,
36443687 )
36453688 return "zeuz_failed"
3689+
3690+ elif playwright_result ["status" ] == "not_supported" :
3691+ CommonUtil .ExecLog (
3692+ sModuleInfo ,
3693+ playwright_result ["error" ],
3694+ 3 ,
3695+ )
3696+ return "zeuz_failed"
3697+
3698+ except Exception as e :
3699+ CommonUtil .ExecLog (
3700+ sModuleInfo ,
3701+ f"Playwright tab switching failed: { e } . Falling back to Selenium" ,
3702+ 2 ,
3703+ )
3704+ playwright_enabled = False
3705+ # Continue with Selenium fallback
36463706
3647- else :
3707+ if not playwright_enabled :
36483708 # --- Selenium tab switching ---
36493709 CommonUtil .ExecLog (sModuleInfo , "Using Selenium for tab switching" , 1 )
36503710 if window_title_condition :
@@ -3735,6 +3795,8 @@ def close_tab(step_data):
37353795 """
37363796 sModuleInfo = inspect .currentframe ().f_code .co_name + " : " + MODULE_NAME
37373797 global selenium_driver
3798+ global selenium_details
3799+ global current_driver_id
37383800 try :
37393801 close_tabs = []
37403802 playwright_enabled = False
@@ -3766,34 +3828,33 @@ def close_tab(step_data):
37663828 try :
37673829 if playwright_enabled :
37683830 # --- Playwright tab closing ---
3769- print ("Using Playwright for tab closing" )
3770- from playwright .sync_api import sync_playwright
3831+ CommonUtil .ExecLog (sModuleInfo , "Using Playwright for tab closing (async API)" , 1 )
3832+ import asyncio
3833+ from playwright .async_api import async_playwright
37713834
3772- try :
3773- with sync_playwright () as p :
3835+ # Async function to handle Playwright operations
3836+ async def run_playwright_close ():
3837+ async with async_playwright () as p :
37743838 debug_port = selenium_details [current_driver_id ]["remote-debugging-port" ]
3775- browser = p .chromium .connect_over_cdp (f"http://localhost:{ debug_port } " )
3839+ browser = await p .chromium .connect_over_cdp (f"http://localhost:{ debug_port } " )
37763840 context = browser .contexts [0 ]
37773841 pages = context .pages
37783842
3843+ result_data = {"status" : None , "error" : None , "page_title" : None }
3844+
37793845 if tab_title :
37803846 # Close tab by title
37813847 for page in pages :
3782- page_title = page .title ()
3848+ page_title = await page .title ()
37833849 if tab_title .lower () in page_title .lower ():
3784- page .close ()
3785- CommonUtil .ExecLog (
3786- sModuleInfo ,
3787- f"Playwright: Tab closed '{ page_title } '" ,
3788- 1 ,
3789- )
3790- return "passed"
3791- CommonUtil .ExecLog (
3792- sModuleInfo ,
3793- f"Playwright: No tab with title '{ tab_title } ' found" ,
3794- 3 ,
3795- )
3796- return "zeuz_failed"
3850+ await page .close ()
3851+ result_data ["status" ] = "closed"
3852+ result_data ["page_title" ] = page_title
3853+ return result_data
3854+
3855+ result_data ["status" ] = "not_found"
3856+ result_data ["error" ] = f"Playwright: No tab with title '{ tab_title } ' found"
3857+ return result_data
37973858
37983859 elif tab_index is not None :
37993860 # Close tab by index using visual order
@@ -3820,41 +3881,26 @@ def close_tab(step_data):
38203881 # Find the page with matching URL
38213882 for page in pages :
38223883 if page .url == desired_url :
3823- page .close ()
3824- CommonUtil .ExecLog (
3825- sModuleInfo ,
3826- f"Playwright: Tab closed at index { idx } " ,
3827- 1 ,
3828- )
3829- return "passed"
3830-
3831- CommonUtil .ExecLog (
3832- sModuleInfo ,
3833- f"Playwright: Tab at visual index { idx } not found in context" ,
3834- 3 ,
3835- )
3836- return "zeuz_failed"
3884+ await page .close ()
3885+ result_data ["status" ] = "closed"
3886+ result_data ["page_title" ] = f"Tab at index { idx } "
3887+ return result_data
3888+
3889+ result_data ["status" ] = "not_found"
3890+ result_data ["error" ] = f"Playwright: Tab at visual index { idx } not found in context"
3891+ return result_data
38373892 else :
3838- CommonUtil .ExecLog (
3839- sModuleInfo ,
3840- f"Playwright: Invalid index { idx } . Only { len (target_urls )} tabs open." ,
3841- 3 ,
3842- )
3843- return "zeuz_failed"
3893+ result_data ["status" ] = "invalid_index"
3894+ result_data ["error" ] = f"Playwright: Invalid index { idx } . Only { len (target_urls )} tabs open."
3895+ return result_data
38443896 except ValueError :
3845- CommonUtil .ExecLog (
3846- sModuleInfo ,
3847- f"Playwright: Invalid tab index '{ tab_index } '" ,
3848- 3 ,
3849- )
3850- return "zeuz_failed"
3897+ result_data ["status" ] = "invalid_index"
3898+ result_data ["error" ] = f"Playwright: Invalid tab index '{ tab_index } '"
3899+ return result_data
38513900 except Exception as e :
3852- CommonUtil .ExecLog (
3853- sModuleInfo ,
3854- f"Playwright: Failed to get visual tab order: { e } " ,
3855- 3 ,
3856- )
3857- return "zeuz_failed"
3901+ result_data ["status" ] = "error"
3902+ result_data ["error" ] = f"Playwright: Failed to get visual tab order: { e } "
3903+ return result_data
38583904
38593905 else :
38603906 # Close current active tab
@@ -3864,7 +3910,8 @@ def close_tab(step_data):
38643910 for page in pages :
38653911 try :
38663912 # Check if this page is currently active
3867- if page .evaluate ("document.hasFocus()" ):
3913+ has_focus = await page .evaluate ("document.hasFocus()" )
3914+ if has_focus :
38683915 current_page = page
38693916 break
38703917 except Exception :
@@ -3903,20 +3950,54 @@ def close_tab(step_data):
39033950 # Ultimate fallback to first page in context
39043951 current_page = pages [0 ]
39053952
3906- page_title = current_page .title ()
3907- current_page .close ()
3908- CommonUtil .ExecLog (
3909- sModuleInfo ,
3910- f"Playwright: Current tab closed '{ page_title } '" ,
3911- 1 ,
3912- )
3913- return "passed"
3953+ page_title = await current_page .title ()
3954+ await current_page .close ()
3955+ result_data ["status" ] = "closed"
3956+ result_data ["page_title" ] = page_title
3957+ return result_data
39143958 else :
3915- CommonUtil .ExecLog (
3916- sModuleInfo , "Playwright: No tabs found to close" , 3
3917- )
3918- return "zeuz_failed"
3959+ result_data ["status" ] = "no_tabs"
3960+ result_data ["error" ] = "Playwright: No tabs found to close"
3961+ return result_data
39193962
3963+ # Run async Playwright code from sync context
3964+ try :
3965+ # We're always called from async context, so we need to run in a new thread with new event loop
3966+ loop = asyncio .get_running_loop ()
3967+ from concurrent .futures import ThreadPoolExecutor
3968+
3969+ def run_in_thread ():
3970+ new_loop = asyncio .new_event_loop ()
3971+ asyncio .set_event_loop (new_loop )
3972+ try :
3973+ return new_loop .run_until_complete (run_playwright_close ())
3974+ finally :
3975+ new_loop .close ()
3976+
3977+ with ThreadPoolExecutor (max_workers = 1 ) as executor :
3978+ future = executor .submit (run_in_thread )
3979+ playwright_result = future .result (timeout = 30 )
3980+
3981+ if playwright_result ["status" ] == "closed" :
3982+ CommonUtil .ExecLog (
3983+ sModuleInfo ,
3984+ f"Playwright: Tab closed '{ playwright_result ['page_title' ]} '" ,
3985+ 1 ,
3986+ )
3987+ return "passed"
3988+ else :
3989+ # For errors that should fall back to Selenium, don't return yet
3990+ if playwright_result ["status" ] in ["not_found" , "invalid_index" , "error" , "no_tabs" ]:
3991+ CommonUtil .ExecLog (
3992+ sModuleInfo ,
3993+ playwright_result ["error" ],
3994+ 3 ,
3995+ )
3996+ # Don't return here - let it fall back to Selenium
3997+ playwright_enabled = False
3998+ else :
3999+ return "zeuz_failed"
4000+
39204001 except Exception as e :
39214002 CommonUtil .ExecLog (
39224003 sModuleInfo ,
0 commit comments