@@ -43,14 +43,13 @@ def __init__(self, headless=False):
4343
4444 self ._chrome_launcher = None
4545 self ._devtools_service = None
46- self ._tab = None
47- self ._first_tab = True
4846
4947 self ._page = None
5048 self ._network = None
5149 self ._input = None
5250 self ._run = None
53- self ._tabs = []
51+
52+ self ._dialog = None
5453
5554 self ._last_clipboard = None
5655
@@ -135,6 +134,26 @@ def stop_browser(self):
135134 # Likely the connection as interrupted already or it timed-out
136135 pass
137136 self ._chrome_launcher .shutdown ()
137+ self ._reset ()
138+
139+ def _reset (self ):
140+ self ._chrome_launcher = None
141+ self ._devtools_service = None
142+
143+ self ._page = None
144+ self ._network = None
145+ self ._input = None
146+ self ._run = None
147+
148+ def _parse_all_messages (self , messages ):
149+ """
150+ This method inspect all messages emitted by the browser to lookup for information.
151+ """
152+ for m in messages :
153+ # Checking for dialogs that were opened
154+ if m .get ('method' ) == 'Page.javascriptDialogOpening' :
155+ self ._dialog = m .get ('params' )
156+ return self ._dialog
138157
139158 def set_download_folder (self , path = None ):
140159 """
@@ -148,12 +167,13 @@ def set_download_folder(self, path=None):
148167 self ._download_folder_path = path
149168 if not self ._devtools_service :
150169 return
151- self ._devtools_service .Browser .setDownloadBehavior (
170+ res , msgs = self ._devtools_service .Browser .setDownloadBehavior (
152171 behavior = "allow" ,
153172 browserContextId = None ,
154173 downloadPath = self ._download_folder_path ,
155174 eventsEnabled = True
156175 )
176+ self ._parse_all_messages (msgs )
157177
158178 def set_screen_resolution (self , width = None , height = None ):
159179 """
@@ -170,8 +190,10 @@ def set_screen_resolution(self, width=None, height=None):
170190 "left" : 0 , "top" : 0 , "width" : width , "height" : height
171191 }
172192 window_id = self .get_window_id ()
173- self ._devtools_service .Browser .setWindowBounds (windowId = window_id , bounds = bounds )
174- self ._devtools_service .Emulation .setVisibleSize (width = width , height = height )
193+ res , msgs = self ._devtools_service .Browser .setWindowBounds (windowId = window_id , bounds = bounds )
194+ self ._parse_all_messages (msgs )
195+ res , msgs = self ._devtools_service .Emulation .setVisibleSize (width = width , height = height )
196+ self ._parse_all_messages (msgs )
175197
176198 ##########
177199 # Display
@@ -190,18 +212,11 @@ def get_screen_image(self, region=None):
190212 """
191213 if not region :
192214 region = (0 , 0 , 0 , 0 )
193- layout_metrics = self ._page .getLayoutMetrics ()[0 ]
194- if layout_metrics :
195- content_size = layout_metrics ['result' ]['contentSize' ]
196- x = region [0 ] or 0
197- y = region [1 ] or 0
198- width = region [2 ] or content_size ['width' ]
199- height = region [3 ] or content_size ['height' ]
200- else :
201- x = 0
202- y = 0
203- width = self ._dimensions [0 ]
204- height = self ._dimensions [1 ]
215+
216+ x = region [0 ] or 0
217+ y = region [1 ] or 0
218+ width = region [2 ] or self ._dimensions [0 ]
219+ height = region [3 ] or self ._dimensions [1 ]
205220 viewport = dict (x = x , y = y , width = width , height = height , scale = 1 )
206221 data = self ._page .captureScreenshot (format = "png" , quality = 100 , clip = viewport ,
207222 fromSurface = True , captureBeyondViewport = False )
@@ -279,7 +294,7 @@ def find_multiple(self, labels, x=None, y=None, width=None, height=None, *,
279294 def _to_dict (lbs , elems ):
280295 return {k : v for k , v in zip (lbs , elems )}
281296
282- screen_w , screen_h = self .get_viewport_size ()
297+ screen_w , screen_h = self ._dimensions
283298 x = x or 0
284299 y = y or 0
285300 w = width or screen_w
@@ -307,7 +322,7 @@ def _to_dict(lbs, elems):
307322 return _to_dict (labels , results )
308323
309324 haystack = self .screenshot ()
310- helper = functools .partial (self .__find_multiple_helper , haystack , region , matching , grayscale )
325+ helper = functools .partial (self ._find_multiple_helper , haystack , region , matching , grayscale )
311326
312327 with multiprocessing .Pool (processes = n_cpus ) as pool :
313328 results = pool .map (helper , paths )
@@ -318,7 +333,7 @@ def _to_dict(lbs, elems):
318333 else :
319334 return _to_dict (labels , results )
320335
321- def __find_multiple_helper (self , haystack , region , confidence , grayscale , needle ):
336+ def _find_multiple_helper (self , haystack , region , confidence , grayscale , needle ):
322337 ele = cv2find .locate_all_opencv (
323338 needle , haystack , region = region , confidence = confidence , grayscale = grayscale
324339 )
@@ -378,7 +393,7 @@ def find_until(self, label, x=None, y=None, width=None, height=None, *,
378393 element (NamedTuple): The element coordinates. None if not found.
379394 """
380395 self .state .element = None
381- screen_w , screen_h = self .get_viewport_size ()
396+ screen_w , screen_h = self ._dimensions
382397 x = x or 0
383398 y = y or 0
384399 w = width or screen_w
@@ -455,8 +470,7 @@ def display_size(self):
455470 Returns:
456471 size (Tuple): The screen dimension (width and height) in pixels.
457472 """
458- screen_size = self .get_viewport_size ()
459- return screen_size .width , screen_size .height
473+ return self ._dimensions
460474
461475 def screenshot (self , filepath = None , region = None ):
462476 """
@@ -500,11 +514,11 @@ def screen_cut(self, x, y, width=None, height=None):
500514 Returns:
501515 Image: The screenshot Image object
502516 """
503- screen_size = self .get_viewport_size ()
517+ screen_size = self ._dimensions
504518 x = x or 0
505519 y = y or 0
506- width = width or screen_size . width
507- height = height or screen_size . height
520+ width = width or screen_size [ 0 ]
521+ height = height or screen_size [ 1 ]
508522 img = self .screenshot (region = (x , y , width , height ))
509523 return img
510524
@@ -537,11 +551,11 @@ def get_element_coords(self, label, x=None, y=None, width=None, height=None, mat
537551 coords (Tuple): A tuple containing the x and y coordinates for the element.
538552 """
539553 self .state .element = None
540- screen_size = self .get_viewport_size ()
554+ screen_size = self ._dimensions
541555 x = x or 0
542556 y = y or 0
543- width = width or screen_size . width
544- height = height or screen_size . height
557+ width = width or screen_size [ 0 ]
558+ height = height or screen_size [ 1 ]
545559 region = (x , y , width , height )
546560
547561 if not best :
@@ -639,6 +653,63 @@ def get_window_id(self):
639653 """
640654 return self ._devtools_service .Browser .getWindowForTarget ()[0 ]['result' ]['windowId' ]
641655
656+ def handle_js_dialog (self , accept = True , prompt_text = None ):
657+ """
658+ Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).
659+ This also cleans the dialog information in the local buffer.
660+
661+ Args:
662+ accept (bool): Whether to accept or dismiss the dialog.
663+ prompt_text (str): The text to enter into the dialog prompt before accepting.
664+ Used only if this is a prompt dialog.
665+ """
666+ kwargs = {'accept' : accept }
667+ if prompt_text is not None :
668+ kwargs ['promptText' ] = prompt_text
669+ self ._devtools_service .Page .handleJavaScriptDialog (** kwargs )
670+ self ._dialog = None
671+
672+ def get_js_dialog (self ):
673+ """
674+ Return the last found dialog. Invoke first the `find_js_dialog` method to look up.
675+
676+ Returns:
677+ dialog (dict): The dialog information or None if not available.
678+ See https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening
679+ """
680+ if not self ._dialog :
681+ return self ._find_js_dialog ()
682+ return self ._dialog
683+
684+ def _find_js_dialog (self ):
685+ """
686+ Find the JavaScript dialog that is currently open and return its information.
687+
688+ Returns:
689+ dialog (dict): The dialog information.
690+ See https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening
691+ """
692+ messages = self ._devtools_service .pop_messages ()
693+ print ('find_js_dialog -> messages: ' , messages )
694+ for m in messages :
695+ if m .get ('method' ) == 'Page.javascriptDialogOpening' :
696+ self ._dialog = m .get ('params' )
697+ return self ._dialog
698+
699+ def get_tabs (self ):
700+ ...
701+
702+ def create_tab (self , url ):
703+ ...
704+
705+ def close_tab (self , tab = None ):
706+ # If tab is None, close current tab
707+ ...
708+
709+ def activate_tab (self , tab ):
710+ ...
711+
712+
642713 #######
643714 # Mouse
644715 #######
@@ -684,7 +755,8 @@ def mouse_move(self, x, y):
684755 """
685756 self ._x = x
686757 self ._y = y
687- self ._input .dispatchMouseEvent (type = "mouseMoved" , x = x , y = y )
758+ res , msgs = self ._input .dispatchMouseEvent (type = "mouseMoved" , x = x , y = y )
759+ self ._parse_all_messages (msgs )
688760
689761 def click_at (self , x , y , * , clicks = 1 , interval_between_clicks = 0 , button = 'left' ):
690762 """
@@ -702,12 +774,14 @@ def click_at(self, x, y, *, clicks=1, interval_between_clicks=0, button='left'):
702774 self ._x = x
703775 self ._y = y
704776 for i in range (clicks ):
705- self ._input .dispatchMouseEvent (
777+ res , msgs = self ._input .dispatchMouseEvent (
706778 type = "mousePressed" , x = x , y = y , button = button , buttons = idx , clickCount = 1
707779 )
708- self ._input .dispatchMouseEvent (
780+ self ._parse_all_messages (msgs )
781+ res , msgs = self ._input .dispatchMouseEvent (
709782 type = "mouseReleased" , x = x , y = y , button = button , buttons = idx , clickCount = 1
710783 )
784+ self ._parse_all_messages (msgs )
711785 self .sleep (interval_between_clicks )
712786
713787 @only_if_element
@@ -805,9 +879,11 @@ def scroll_down(self, clicks):
805879 clicks (int): Number of times to scroll down.
806880 """
807881 for i in range (clicks ):
808- self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["ScrollLineDown" ])
882+ res , msgs = self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["ScrollLineDown" ])
883+ self ._parse_all_messages (msgs )
809884 self .sleep (200 )
810- self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["ScrollLineDown" ])
885+ res , msgs = self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["ScrollLineDown" ])
886+ self ._parse_all_messages (msgs )
811887
812888 def scroll_up (self , clicks ):
813889 """
@@ -817,9 +893,11 @@ def scroll_up(self, clicks):
817893 clicks (int): Number of times to scroll up.
818894 """
819895 for i in range (clicks ):
820- self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["ScrollLineUp" ])
896+ res , msgs = self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["ScrollLineUp" ])
897+ self ._parse_all_messages (msgs )
821898 self .sleep (200 )
822- self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["ScrollLineUp" ])
899+ res , msgs = self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["ScrollLineUp" ])
900+ self ._parse_all_messages (msgs )
823901
824902 def move_to (self , x , y ):
825903 """
@@ -831,7 +909,8 @@ def move_to(self, x, y):
831909 """
832910 self ._x = x
833911 self ._y = y
834- self ._input .dispatchMouseEvent (type = "mouseMoved" , x = x , y = y )
912+ res , msgs = self ._input .dispatchMouseEvent (type = "mouseMoved" , x = x , y = y )
913+ self ._parse_all_messages (msgs )
835914
836915 @only_if_element
837916 def move (self ):
@@ -917,16 +996,20 @@ def _dispatch_key_event(self, *, event_type="keyDown", text=None, key=None, virt
917996 kwargs = {
918997 "type" : event_type
919998 }
999+ if self ._shift_hold :
1000+ kwargs .update ({"modifiers" : 8 })
9201001 if text :
9211002 kwargs .update ({"text" : text })
9221003 if virtual_kc is not None :
9231004 kwargs .update ({"windowsVirtualKeyCode" : virtual_kc , "nativeVirtualKeyCode" : virtual_kc })
9241005 if key is not None :
9251006 kwargs .update ({"key" : key })
9261007
927- self ._input .dispatchKeyEvent (** kwargs )
1008+ res , msgs = self ._input .dispatchKeyEvent (** kwargs )
1009+ self ._parse_all_messages (msgs )
9281010 if execute_up :
929- self ._input .dispatchKeyEvent (type = "keyUp" )
1011+ res , msgs = self ._input .dispatchKeyEvent (type = "keyUp" )
1012+ self ._parse_all_messages (msgs )
9301013
9311014 def kb_type (self , text , interval = 0 ):
9321015 """
@@ -1095,7 +1178,8 @@ def maximize_window(self):
10951178 """
10961179
10971180 bounds = dict (left = 0 , top = 0 , width = 0 , height = 0 , windowState = "maximized" )
1098- self ._devtools_service .Browser .setWindowBounds (windowId = self .get_window_id (), bounds = bounds )
1181+ res , msgs = self ._devtools_service .Browser .setWindowBounds (windowId = self .get_window_id (), bounds = bounds )
1182+ self ._parse_all_messages (msgs )
10991183 self .sleep (1000 )
11001184
11011185 def type_keys_with_interval (self , interval , keys ):
@@ -1154,8 +1238,10 @@ def control_c(self, wait=0):
11541238 "document.getElementById('clipboardTransferText').value = text;"
11551239 )
11561240 self .execute_javascript (cmd )
1157- self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["Copy" ])
1158- self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["Copy" ])
1241+ res , msgs = self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["Copy" ])
1242+ self ._parse_all_messages (msgs )
1243+ res , msgs = self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["Copy" ])
1244+ self ._parse_all_messages (msgs )
11591245 delay = max (0 , wait or config .DEFAULT_SLEEP_AFTER_ACTION )
11601246 self .sleep (delay )
11611247
@@ -1167,8 +1253,10 @@ def control_v(self, wait=0):
11671253 wait (int, optional): Wait interval (ms) after task
11681254
11691255 """
1170- self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["Paste" ])
1171- self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["Paste" ])
1256+ res , msgs = self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["Paste" ])
1257+ self ._parse_all_messages (msgs )
1258+ res , msgs = self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["Paste" ])
1259+ self ._parse_all_messages (msgs )
11721260 delay = max (0 , wait or config .DEFAULT_SLEEP_AFTER_ACTION )
11731261 self .sleep (delay )
11741262
@@ -1180,8 +1268,10 @@ def control_a(self, wait=0):
11801268 wait (int, optional): Wait interval (ms) after task
11811269
11821270 """
1183- self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["SelectAll" ])
1184- self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["SelectAll" ])
1271+ res , msgs = self ._input .dispatchKeyEvent (type = "keyDown" , commands = ["SelectAll" ])
1272+ self ._parse_all_messages (msgs )
1273+ res , msgs = self ._input .dispatchKeyEvent (type = "keyUp" , commands = ["SelectAll" ])
1274+ self ._parse_all_messages (msgs )
11851275 delay = max (0 , wait or config .DEFAULT_SLEEP_AFTER_ACTION )
11861276 self .sleep (delay )
11871277
0 commit comments