1515# specific language governing permissions and limitations 
1616# under the License. 
1717
18+ import  datetime 
19+ import  math 
1820from  dataclasses  import  dataclass 
1921from  typing  import  Any , Optional 
2022
23+ from  selenium .common .exceptions  import  WebDriverException 
2124from  selenium .webdriver .common .bidi .common  import  command_builder 
2225
2326from  .log  import  LogEntryAdded 
@@ -238,12 +241,15 @@ class Script:
238241        "realm_destroyed" : "script.realmDestroyed" ,
239242    }
240243
241-     def  __init__ (self , conn ):
244+     def  __init__ (self , conn ,  driver = None ):
242245        self .conn  =  conn 
246+         self .driver  =  driver 
243247        self .log_entry_subscribed  =  False 
244248        self .subscriptions  =  {}
245249        self .callbacks  =  {}
246250
251+     # High-level APIs for SCRIPT module 
252+ 
247253    def  add_console_message_handler (self , handler ):
248254        self ._subscribe_to_log_entries ()
249255        return  self .conn .add_callback (LogEntryAdded , self ._handle_log_entry ("console" , handler ))
@@ -258,6 +264,122 @@ def remove_console_message_handler(self, id):
258264
259265    remove_javascript_error_handler  =  remove_console_message_handler 
260266
267+     def  pin (self , script : str ) ->  str :
268+         """Pins a script to the current browsing context. 
269+ 
270+         Parameters: 
271+         ----------- 
272+             script: The script to pin. 
273+ 
274+         Returns: 
275+         ------- 
276+             str: The ID of the pinned script. 
277+         """ 
278+         return  self ._add_preload_script (script )
279+ 
280+     def  unpin (self , script_id : str ) ->  None :
281+         """Unpins a script from the current browsing context. 
282+ 
283+         Parameters: 
284+         ----------- 
285+             script_id: The ID of the pinned script to unpin. 
286+         """ 
287+         self ._remove_preload_script (script_id )
288+ 
289+     def  execute (self , script : str , * args ) ->  dict :
290+         """Executes a script in the current browsing context. 
291+ 
292+         Parameters: 
293+         ----------- 
294+             script: The script function to execute. 
295+             *args: Arguments to pass to the script function. 
296+ 
297+         Returns: 
298+         ------- 
299+             dict: The result value from the script execution. 
300+ 
301+         Raises: 
302+         ------ 
303+             WebDriverException: If the script execution fails. 
304+         """ 
305+ 
306+         if  self .driver  is  None :
307+             raise  WebDriverException ("Driver reference is required for script execution" )
308+         browsing_context_id  =  self .driver .current_window_handle 
309+ 
310+         # Convert arguments to the format expected by BiDi call_function (LocalValue Type) 
311+         arguments  =  []
312+         for  arg  in  args :
313+             arguments .append (self .__convert_to_local_value (arg ))
314+ 
315+         target  =  {"context" : browsing_context_id }
316+ 
317+         result  =  self ._call_function (
318+             function_declaration = script , await_promise = True , target = target , arguments = arguments  if  arguments  else  None 
319+         )
320+ 
321+         if  result .type  ==  "success" :
322+             return  result .result 
323+         else :
324+             error_message  =  "Error while executing script" 
325+             if  result .exception_details :
326+                 if  "text"  in  result .exception_details :
327+                     error_message  +=  f": { result .exception_details ['text' ]}  
328+                 elif  "message"  in  result .exception_details :
329+                     error_message  +=  f": { result .exception_details ['message' ]}  
330+ 
331+             raise  WebDriverException (error_message )
332+ 
333+     def  __convert_to_local_value (self , value ) ->  dict :
334+         """ 
335+         Converts a Python value to BiDi LocalValue format. 
336+         """ 
337+         if  value  is  None :
338+             return  {"type" : "null" }
339+         elif  isinstance (value , bool ):
340+             return  {"type" : "boolean" , "value" : value }
341+         elif  isinstance (value , (int , float )):
342+             if  isinstance (value , float ):
343+                 if  math .isnan (value ):
344+                     return  {"type" : "number" , "value" : "NaN" }
345+                 elif  math .isinf (value ):
346+                     if  value  >  0 :
347+                         return  {"type" : "number" , "value" : "Infinity" }
348+                     else :
349+                         return  {"type" : "number" , "value" : "-Infinity" }
350+                 elif  value  ==  0.0  and  math .copysign (1.0 , value ) <  0 :
351+                     return  {"type" : "number" , "value" : "-0" }
352+ 
353+             JS_MAX_SAFE_INTEGER  =  9007199254740991 
354+             if  isinstance (value , int ) and  (value  >  JS_MAX_SAFE_INTEGER  or  value  <  - JS_MAX_SAFE_INTEGER ):
355+                 return  {"type" : "bigint" , "value" : str (value )}
356+ 
357+             return  {"type" : "number" , "value" : value }
358+ 
359+         elif  isinstance (value , str ):
360+             return  {"type" : "string" , "value" : value }
361+         elif  isinstance (value , datetime .datetime ):
362+             # Convert Python datetime to JavaScript Date (ISO 8601 format) 
363+             return  {"type" : "date" , "value" : value .isoformat () +  "Z"  if  value .tzinfo  is  None  else  value .isoformat ()}
364+         elif  isinstance (value , datetime .date ):
365+             # Convert Python date to JavaScript Date 
366+             dt  =  datetime .datetime .combine (value , datetime .time .min ).replace (tzinfo = datetime .timezone .utc )
367+             return  {"type" : "date" , "value" : dt .isoformat ()}
368+         elif  isinstance (value , set ):
369+             return  {"type" : "set" , "value" : [self .__convert_to_local_value (item ) for  item  in  value ]}
370+         elif  isinstance (value , (list , tuple )):
371+             return  {"type" : "array" , "value" : [self .__convert_to_local_value (item ) for  item  in  value ]}
372+         elif  isinstance (value , dict ):
373+             return  {
374+                 "type" : "object" ,
375+                 "value" : [
376+                     [self .__convert_to_local_value (k ), self .__convert_to_local_value (v )] for  k , v  in  value .items ()
377+                 ],
378+             }
379+         else :
380+             # For other types, convert to string 
381+             return  {"type" : "string" , "value" : str (value )}
382+ 
261383    # low-level APIs for script module 
262384    def  _add_preload_script (
263385        self ,
0 commit comments