diff --git a/skyvern/exceptions.py b/skyvern/exceptions.py index 1f944267..a120b543 100644 --- a/skyvern/exceptions.py +++ b/skyvern/exceptions.py @@ -297,3 +297,10 @@ class MissingElementDict(SkyvernException): class MissingElementInIframe(SkyvernException): def __init__(self, element_id: str) -> None: super().__init__(f"Found no iframe includes the element. element_id={element_id}") + + +class InputActionOnSelect2Dropdown(SkyvernException): + def __init__(self, element_id: str): + super().__init__( + f"Input action on a select element, please try to use select action on this element. element_id={element_id}" + ) diff --git a/skyvern/webeye/actions/handler.py b/skyvern/webeye/actions/handler.py index 1881e0e3..ba782503 100644 --- a/skyvern/webeye/actions/handler.py +++ b/skyvern/webeye/actions/handler.py @@ -11,6 +11,7 @@ from playwright.async_api import Locator, Page, TimeoutError from skyvern.constants import INPUT_TEXT_TIMEOUT, REPO_ROOT_DIR from skyvern.exceptions import ( ImaginaryFileUrl, + InputActionOnSelect2Dropdown, InvalidElementForTextInput, MissingElement, MissingFileUrl, @@ -41,7 +42,7 @@ from skyvern.webeye.actions.actions import ( from skyvern.webeye.actions.responses import ActionFailure, ActionResult, ActionSuccess from skyvern.webeye.browser_factory import BrowserState from skyvern.webeye.scraper.scraper import ScrapedPage -from skyvern.webeye.utils.dom import resolve_locator +from skyvern.webeye.utils.dom import DomUtil, InteractiveElement, Select2Dropdown, resolve_locator LOG = structlog.get_logger() TEXT_INPUT_DELAY = 10 # 10ms between each character input @@ -241,6 +242,11 @@ async def handle_input_text_action( task: Task, step: Step, ) -> list[ActionResult]: + dom = DomUtil(scraped_page, page) + skyvern_element = await dom.get_skyvern_element_by_id(action.element_id) + if await skyvern_element.is_select2_dropdown(): + return [ActionFailure(InputActionOnSelect2Dropdown(element_id=action.element_id))] + xpath, frame = await validate_actions_in_dom(action, page, scraped_page) locator = resolve_locator(scraped_page, page, frame, xpath) @@ -392,6 +398,9 @@ async def handle_select_option_action( task: Task, step: Step, ) -> list[ActionResult]: + dom = DomUtil(scraped_page, page) + skyvern_element = await dom.get_skyvern_element_by_id(action.element_id) + xpath, frame = await validate_actions_in_dom(action, page, scraped_page) locator = resolve_locator(scraped_page, page, frame, xpath) @@ -428,17 +437,23 @@ async def handle_select_option_action( # check if the element is an a tag first. If yes, click it instead of selecting the option if tag_name == "label": - # TODO: this is a hack to handle the case where the label is the only thing that's clickable - # it's a label, look for the anchor tag - child_anchor_xpath = get_anchor_to_click(scraped_page, action.element_id) - if child_anchor_xpath: - LOG.info( - "SelectOptionAction is a label tag. Clicking the anchor tag instead of selecting the option", - action=action, - child_anchor_xpath=child_anchor_xpath, - ) - click_action = ClickAction(element_id=action.element_id) - return await chain_click(task, scraped_page, page, click_action, child_anchor_xpath, frame) + # label pointed to select2 element + select2_element_id: str | None = None + # search anchor first and then search anchor + select2_element_id = skyvern_element.find_element_id_in_label_children(InteractiveElement.A) + if select2_element_id is None: + select2_element_id = skyvern_element.find_element_id_in_label_children(InteractiveElement.INPUT) + + if select2_element_id is not None: + select2_skyvern_element = await dom.get_skyvern_element_by_id(element_id=select2_element_id) + if await select2_skyvern_element.is_select2_dropdown(): + LOG.info( + "SelectOptionAction is on