From 55bc6bd367d72c84a43c59139aecdcb29b0396f6 Mon Sep 17 00:00:00 2001 From: LawyZheng Date: Wed, 20 Aug 2025 14:28:01 +0800 Subject: [PATCH] helper function for wait animation (#3240) --- skyvern/webeye/actions/handler.py | 116 +++--------------------------- skyvern/webeye/scraper/scraper.py | 18 +---- skyvern/webeye/utils/page.py | 35 ++++----- 3 files changed, 32 insertions(+), 137 deletions(-) diff --git a/skyvern/webeye/actions/handler.py b/skyvern/webeye/actions/handler.py index f43f60b0..2cec67d1 100644 --- a/skyvern/webeye/actions/handler.py +++ b/skyvern/webeye/actions/handler.py @@ -974,18 +974,7 @@ async def handle_input_text_action( action=action, ) - try: - await skyvern_frame.get_frame().wait_for_load_state("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Timeout to wait for the frame to load, ignore the timeout and continue to execute the action", - task_id=task.task_id, - step_id=step.step_id, - element_id=skyvern_element.get_id(), - action=action, - ) - + await skyvern_frame.safe_wait_for_animation_end() incremental_element = await incremental_scraped.get_incremental_element_tree( clean_and_remove_element_tree_factory( task=task, step=step, check_filter_funcs=[check_existed_but_not_option_element_in_dom_factory(dom)] @@ -1126,18 +1115,7 @@ async def handle_input_text_action( return [ActionFailure(InvalidElementForTextInput(element_id=action.element_id, tag_name=tag_name))] # wait for blocking element to show up - try: - await skyvern_frame.get_frame().wait_for_load_state("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Timeout to wait for the frame to load, ignore the timeout and continue to execute the action", - task_id=task.task_id, - step_id=step.step_id, - element_id=skyvern_element.get_id(), - action=action, - ) - + await skyvern_frame.safe_wait_for_animation_end() try: blocking_element, exist = await skyvern_element.find_blocking_element( dom=dom, incremental_page=incremental_scraped @@ -1538,15 +1516,7 @@ async def handle_select_option_action( await skyvern_element.click(page=page, dom=dom, timeout=timeout) # wait for options to load - await asyncio.sleep(0.5) - try: - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the frame to load, ignore the timeout and continue to get incremental element tree", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) incremental_element = await incremental_scraped.get_incremental_element_tree( clean_and_remove_element_tree_factory( @@ -1564,15 +1534,7 @@ async def handle_select_option_action( await skyvern_element.scroll_into_view() await skyvern_element.press_key("ArrowDown") # wait for options to load - await asyncio.sleep(0.5) - try: - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the frame to load, ignore the timeout and continue to get incremental element tree", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) incremental_element = await incremental_scraped.get_incremental_element_tree( clean_and_remove_element_tree_factory( task=task, step=step, check_filter_funcs=[check_existed_but_not_option_element_in_dom_factory(dom)] @@ -1669,15 +1631,7 @@ async def handle_select_option_action( await skyvern_element.scroll_into_view() await skyvern_element.press_key("ArrowDown") - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the frame to load, ignore the exception and continue", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) is_open = True result = await select_from_dropdown_by_value( @@ -2200,15 +2154,7 @@ async def choose_auto_completion_dropdown( try: await skyvern_element.press_fill(text) # wait for new elemnts to load - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_load_state("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.warning( - "Failed to wait for load state or animation end after input the value, will continue to get incremental element tree", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) incremental_element = await incremental_scraped.get_incremental_element_tree( clean_and_remove_element_tree_factory( task=task, step=step, check_filter_funcs=[check_existed_but_not_option_element_in_dom_factory(dom)] @@ -2608,15 +2554,7 @@ async def sequentially_select_from_dropdown( select_history.append(single_select_result) values.append(single_select_result.value) # wait 1s until DOM finished updating - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the animation to end, ignore the exception and continue", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) if await single_select_result.is_done(): return single_select_result @@ -2637,15 +2575,7 @@ async def sequentially_select_from_dropdown( step_id=step.step_id, ) # wait to load new options - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the animation to end, ignore the exception and continue", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) check_filter_funcs.append( check_disappeared_element_id_in_incremental_factory(incremental_scraped=incremental_scraped) @@ -3356,29 +3286,13 @@ async def scroll_down_to_load_all_options( else: await skyvern_frame.scroll_to_element_bottom(dropdown_menu_element_handle, page_by_page) # wait until animation ends, otherwise the scroll operation could be overwritten - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the animation to end, ignore the exception and continue", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) # scroll a little back and scroll down to trigger the loading await page.mouse.wheel(0, -1e-5) await page.mouse.wheel(0, 1e-5) # wait for while to load new options - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the animation to end, ignore the exception and continue", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) current_num = await incremental_scraped.get_incremental_elements_num() LOG.info( @@ -3403,15 +3317,7 @@ async def scroll_down_to_load_all_options( await page.mouse.wheel(0, -scroll_pace) else: await skyvern_frame.scroll_to_element_top(dropdown_menu_element_handle) - try: - await asyncio.sleep(0.5) - await skyvern_frame.get_frame().wait_for_event("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.info( - "Failed to wait for the animation to end, ignore the exception and continue", - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end(before_wait_sec=0.5) async def normal_select( diff --git a/skyvern/webeye/scraper/scraper.py b/skyvern/webeye/scraper/scraper.py index 44d14f0d..c17e89f0 100644 --- a/skyvern/webeye/scraper/scraper.py +++ b/skyvern/webeye/scraper/scraper.py @@ -555,12 +555,8 @@ async def scrape_web_unsafe( if url == "about:blank" and not support_empty_page: raise ScrapingFailedBlankPage() - try: - await page.wait_for_load_state("load", timeout=3000) - skyvern_frame = await SkyvernFrame.create_instance(page) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.warning("Failed to wait for load state, will continue scraping", exc_info=True) + skyvern_frame = await SkyvernFrame.create_instance(page) + await skyvern_frame.safe_wait_for_animation_end() if wait_seconds > 0: LOG.info(f"Waiting for {wait_seconds} seconds before scraping the website.", wait_seconds=wait_seconds) @@ -689,15 +685,7 @@ async def add_frame_interactable_elements( return elements, element_tree skyvern_frame = await SkyvernFrame.create_instance(frame) - try: - await skyvern_frame.get_frame().wait_for_load_state("load", timeout=3000) - await skyvern_frame.safe_wait_for_animation_end() - except Exception: - LOG.warning( - "Failed to wait for load state or animation end for the frame, will continue scraping", - frame_id=unique_id, - exc_info=True, - ) + await skyvern_frame.safe_wait_for_animation_end() frame_elements, frame_element_tree = await skyvern_frame.build_tree_from_body( frame_name=unique_id, frame_index=frame_index diff --git a/skyvern/webeye/utils/page.py b/skyvern/webeye/utils/page.py index 122bb108..9b981db0 100644 --- a/skyvern/webeye/utils/page.py +++ b/skyvern/webeye/utils/page.py @@ -517,22 +517,23 @@ class SkyvernFrame: frame=self.frame, expression=js_script, timeout_ms=timeout_ms, arg=[starter, frame, full_tree] ) - async def safe_wait_for_animation_end(self, timeout_ms: float = 3000) -> None: + async def safe_wait_for_animation_end(self, before_wait_sec: float = 0, timeout_ms: float = 3000) -> None: try: - async with asyncio.timeout(timeout_ms / 1000): - while True: - try: - is_finished = await self.evaluate( - frame=self.frame, - expression="() => isAnimationFinished()", - timeout_ms=timeout_ms, - ) - if is_finished: - return - await asyncio.sleep(0.1) - except Exception: - LOG.warning("Failed to wait for animation end, but ignore it", exc_info=True) - return - except asyncio.TimeoutError: - LOG.debug("Timeout while waiting for animation end, but ignore it", exc_info=True) + await asyncio.sleep(before_wait_sec) + await self.frame.wait_for_load_state("load", timeout=timeout_ms) + await self.wait_for_animation_end(timeout_ms=timeout_ms) + except Exception: + LOG.info("Failed to wait for animation end, but ignore it", exc_info=True) return + + async def wait_for_animation_end(self, timeout_ms: float = 3000) -> None: + async with asyncio.timeout(timeout_ms / 1000): + while True: + is_finished = await self.evaluate( + frame=self.frame, + expression="() => isAnimationFinished()", + timeout_ms=timeout_ms, + ) + if is_finished: + return + await asyncio.sleep(0.1)