remove useless select support legacy (#863)
This commit is contained in:
@@ -313,21 +313,6 @@ class ElementIsNotLabel(SkyvernException):
|
|||||||
super().__init__(f"<{tag_name}> element is not <label>")
|
super().__init__(f"<{tag_name}> element is not <label>")
|
||||||
|
|
||||||
|
|
||||||
class ElementIsNotSelect2Dropdown(SkyvernException):
|
|
||||||
def __init__(self, element_id: str, element: dict):
|
|
||||||
super().__init__(f"element[{element}] is not select2 dropdown. element_id={element_id}")
|
|
||||||
|
|
||||||
|
|
||||||
class ElementIsNotReactSelectDropdown(SkyvernException):
|
|
||||||
def __init__(self, element_id: str, element: dict):
|
|
||||||
super().__init__(f"element[{element}] is not react select dropdown. element_id={element_id}")
|
|
||||||
|
|
||||||
|
|
||||||
class ElementIsNotComboboxDropdown(SkyvernException):
|
|
||||||
def __init__(self, element_id: str, element: dict):
|
|
||||||
super().__init__(f"element[{element}] is not combobox dropdown. element_id={element_id}")
|
|
||||||
|
|
||||||
|
|
||||||
class NoneFrameError(SkyvernException):
|
class NoneFrameError(SkyvernException):
|
||||||
def __init__(self, frame_id: str):
|
def __init__(self, frame_id: str):
|
||||||
super().__init__(f"frame content is none. frame_id={frame_id}")
|
super().__init__(f"frame content is none. frame_id={frame_id}")
|
||||||
@@ -411,23 +396,6 @@ class NoSelectableElementFound(SkyvernException):
|
|||||||
super().__init__(f"No selectable elements found in the children list. element_id={element_id}")
|
super().__init__(f"No selectable elements found in the children list. element_id={element_id}")
|
||||||
|
|
||||||
|
|
||||||
class NoDropdownAnchorErr(SkyvernException):
|
|
||||||
def __init__(self, dropdowm_type: str, element_id: str):
|
|
||||||
super().__init__(f"No {dropdowm_type} dropdown found. element_id={element_id}")
|
|
||||||
|
|
||||||
|
|
||||||
class MultipleDropdownAnchorErr(SkyvernException):
|
|
||||||
def __init__(self, dropdowm_type: str, element_id: str):
|
|
||||||
super().__init__(f"Multiple {dropdowm_type} dropdown found. element_id={element_id}")
|
|
||||||
|
|
||||||
|
|
||||||
class FailedToGetCurrentValueOfDropdown(SkyvernException):
|
|
||||||
def __init__(self, dropdowm_type: str, element_id: str, fail_reason: str):
|
|
||||||
super().__init__(
|
|
||||||
f"Failed to get current value of {dropdowm_type} dropdown. element_id={element_id}, failure_reason={fail_reason}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HttpException(SkyvernException):
|
class HttpException(SkyvernException):
|
||||||
def __init__(self, status_code: int, url: str, msg: str | None = None) -> None:
|
def __init__(self, status_code: int, url: str, msg: str | None = None) -> None:
|
||||||
super().__init__(f"HTTP Exception, status_code={status_code}, url={url}" + (f", msg={msg}" if msg else ""))
|
super().__init__(f"HTTP Exception, status_code={status_code}, url={url}" + (f", msg={msg}" if msg else ""))
|
||||||
|
|||||||
@@ -745,151 +745,6 @@ function getSelectOptions(element) {
|
|||||||
return [selectOptions, removeMultipleSpaces(selectedOption.textContent)];
|
return [selectOptions, removeMultipleSpaces(selectedOption.textContent)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getListboxOptions(element) {
|
|
||||||
// get all the elements with role="option" under the element
|
|
||||||
var optionElements = element.querySelectorAll('[role="option"]');
|
|
||||||
let selectOptions = [];
|
|
||||||
for (var i = 0; i < optionElements.length; i++) {
|
|
||||||
let ele = optionElements[i];
|
|
||||||
|
|
||||||
selectOptions.push({
|
|
||||||
optionIndex: i,
|
|
||||||
text: removeMultipleSpaces(getVisibleText(ele)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return selectOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSelect2OptionElements(element) {
|
|
||||||
let optionList = [];
|
|
||||||
const document = element.getRootNode();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
oldOptionCount = optionList.length;
|
|
||||||
let newOptionList = document.querySelectorAll("[id='select2-drop'] ul li");
|
|
||||||
if (newOptionList.length === oldOptionCount) {
|
|
||||||
console.log("no more options loaded, wait 5s to query again");
|
|
||||||
// sometimes need more time to load the options, so sleep 10s and try again
|
|
||||||
await globalSleep(5000); // wait 5s
|
|
||||||
newOptionList = document.querySelectorAll("[id='select2-drop'] ul li");
|
|
||||||
console.log(newOptionList.length, " options found, after 5s");
|
|
||||||
}
|
|
||||||
|
|
||||||
optionList = newOptionList;
|
|
||||||
if (optionList.length === 0 || optionList.length === oldOptionCount) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastOption = optionList[optionList.length - 1];
|
|
||||||
if (!lastOption.className.toString().includes("select2-more-results")) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lastOption.scrollIntoView();
|
|
||||||
}
|
|
||||||
|
|
||||||
return optionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSelect2Options(element) {
|
|
||||||
const optionList = await getSelect2OptionElements(element);
|
|
||||||
|
|
||||||
let selectOptions = [];
|
|
||||||
for (let i = 0; i < optionList.length; i++) {
|
|
||||||
let ele = optionList[i];
|
|
||||||
if (ele.className.toString().includes("select2-more-results")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectOptions.push({
|
|
||||||
optionIndex: i,
|
|
||||||
text: removeMultipleSpaces(ele.textContent),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getReactSelectOptionElements(element) {
|
|
||||||
var scrollLeft = window.scrollX;
|
|
||||||
var scrollTop = window.scrollY;
|
|
||||||
|
|
||||||
let optionList = [];
|
|
||||||
// wait for 2s until the element is updated with `aria-controls`
|
|
||||||
console.log("wait 2s for the dropdown being updated.");
|
|
||||||
await globalSleep(2000);
|
|
||||||
|
|
||||||
dropdownId = element.getAttribute("aria-controls");
|
|
||||||
if (!dropdownId) {
|
|
||||||
return optionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
const document = element.getRootNode();
|
|
||||||
dropdownDiv = document.querySelector(`div[id="${dropdownId}"]`);
|
|
||||||
let previousOptionCount = null;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
// sometimes need more time to load the options
|
|
||||||
console.log("wait 5s to load all options");
|
|
||||||
await globalSleep(5000); // wait 5s
|
|
||||||
optionList = dropdownDiv.querySelectorAll("div[class*='select__option']");
|
|
||||||
if (optionList.length === 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
previousOptionCount !== null &&
|
|
||||||
previousOptionCount == optionList.length
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
previousOptionCount = optionList.length;
|
|
||||||
|
|
||||||
lastOption = optionList[optionList.length - 1];
|
|
||||||
lastOption.scrollIntoView({ behavior: "instant" });
|
|
||||||
|
|
||||||
lastOption.dispatchEvent(
|
|
||||||
new WheelEvent("wheel", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
deltaX: 0,
|
|
||||||
deltaY: -20,
|
|
||||||
deltaZ: 0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
lastOption.dispatchEvent(
|
|
||||||
new WheelEvent("wheel", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
deltaX: 0,
|
|
||||||
deltaY: 20,
|
|
||||||
deltaZ: 0,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// scroll back to the original place
|
|
||||||
window.scroll({
|
|
||||||
top: scrollTop,
|
|
||||||
left: scrollLeft,
|
|
||||||
behavior: "instant",
|
|
||||||
});
|
|
||||||
return optionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getReactSelectOptions(element) {
|
|
||||||
const optionList = await getReactSelectOptionElements(element);
|
|
||||||
|
|
||||||
let selectOptions = [];
|
|
||||||
for (let i = 0; i < optionList.length; i++) {
|
|
||||||
let ele = optionList[i];
|
|
||||||
selectOptions.push({
|
|
||||||
optionIndex: i,
|
|
||||||
text: removeMultipleSpaces(ele.textContent),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return selectOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDOMElementBySkyvenElement(elementObj) {
|
function getDOMElementBySkyvenElement(elementObj) {
|
||||||
// if element has shadowHost set, we need to find the shadowHost element first then find the element
|
// if element has shadowHost set, we need to find the shadowHost element first then find the element
|
||||||
if (elementObj.shadowHost) {
|
if (elementObj.shadowHost) {
|
||||||
@@ -1674,10 +1529,6 @@ function scrollToElementTop(element) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function globalSleep(ms) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method for debugging
|
// Helper method for debugging
|
||||||
function findNodeById(arr, targetId, path = []) {
|
function findNodeById(arr, targetId, path = []) {
|
||||||
for (let i = 0; i < arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
import typing
|
import typing
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
from random import uniform
|
from random import uniform
|
||||||
|
|
||||||
@@ -12,25 +11,18 @@ from playwright.async_api import ElementHandle, Frame, FrameLocator, Locator, Pa
|
|||||||
|
|
||||||
from skyvern.constants import SKYVERN_ID_ATTR
|
from skyvern.constants import SKYVERN_ID_ATTR
|
||||||
from skyvern.exceptions import (
|
from skyvern.exceptions import (
|
||||||
ElementIsNotComboboxDropdown,
|
|
||||||
ElementIsNotLabel,
|
ElementIsNotLabel,
|
||||||
ElementIsNotReactSelectDropdown,
|
|
||||||
ElementIsNotSelect2Dropdown,
|
|
||||||
FailedToGetCurrentValueOfDropdown,
|
|
||||||
MissingElement,
|
MissingElement,
|
||||||
MissingElementDict,
|
MissingElementDict,
|
||||||
MissingElementInCSSMap,
|
MissingElementInCSSMap,
|
||||||
MissingElementInIframe,
|
MissingElementInIframe,
|
||||||
MultipleDropdownAnchorErr,
|
|
||||||
MultipleElementsFound,
|
MultipleElementsFound,
|
||||||
NoDropdownAnchorErr,
|
|
||||||
NoElementBoudingBox,
|
NoElementBoudingBox,
|
||||||
NoneFrameError,
|
NoneFrameError,
|
||||||
SkyvernException,
|
SkyvernException,
|
||||||
)
|
)
|
||||||
from skyvern.forge.sdk.settings_manager import SettingsManager
|
from skyvern.forge.sdk.settings_manager import SettingsManager
|
||||||
from skyvern.webeye.scraper.scraper import IncrementalScrapePage, ScrapedPage, json_to_html, trim_element
|
from skyvern.webeye.scraper.scraper import IncrementalScrapePage, ScrapedPage, json_to_html, trim_element
|
||||||
from skyvern.webeye.utils.page import SkyvernFrame
|
|
||||||
|
|
||||||
LOG = structlog.get_logger()
|
LOG = structlog.get_logger()
|
||||||
|
|
||||||
@@ -276,27 +268,6 @@ class SkyvernElement:
|
|||||||
assert handler is not None
|
assert handler is not None
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
async def get_select2_dropdown(self) -> Select2Dropdown:
|
|
||||||
if not await self.is_select2_dropdown():
|
|
||||||
raise ElementIsNotSelect2Dropdown(self.get_id(), self.__static_element)
|
|
||||||
|
|
||||||
frame = await SkyvernFrame.create_instance(self.get_frame())
|
|
||||||
return Select2Dropdown(frame, self)
|
|
||||||
|
|
||||||
async def get_react_select_dropdown(self) -> ReactSelectDropdown:
|
|
||||||
if not await self.is_react_select_dropdown():
|
|
||||||
raise ElementIsNotReactSelectDropdown(self.get_id(), self.__static_element)
|
|
||||||
|
|
||||||
frame = await SkyvernFrame.create_instance(self.get_frame())
|
|
||||||
return ReactSelectDropdown(frame, self)
|
|
||||||
|
|
||||||
async def get_combobox_dropdown(self) -> ComboboxDropdown:
|
|
||||||
if not await self.is_combobox_dropdown():
|
|
||||||
raise ElementIsNotComboboxDropdown(self.get_id(), self.__static_element)
|
|
||||||
|
|
||||||
frame = await SkyvernFrame.create_instance(self.get_frame())
|
|
||||||
return ComboboxDropdown(frame, self)
|
|
||||||
|
|
||||||
def find_element_id_in_label_children(self, element_type: InteractiveElement) -> str | None:
|
def find_element_id_in_label_children(self, element_type: InteractiveElement) -> str | None:
|
||||||
tag_name = self.get_tag_name()
|
tag_name = self.get_tag_name()
|
||||||
if tag_name != "label":
|
if tag_name != "label":
|
||||||
@@ -540,212 +511,3 @@ class DomUtil:
|
|||||||
raise MultipleElementsFound(num=num_elements, selector=css, element_id=element_id)
|
raise MultipleElementsFound(num=num_elements, selector=css, element_id=element_id)
|
||||||
|
|
||||||
return SkyvernElement(locator, frame_content, element)
|
return SkyvernElement(locator, frame_content, element)
|
||||||
|
|
||||||
|
|
||||||
class AbstractSelectDropdown(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def name(self) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def open(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def close(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def get_current_value(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def get_options(
|
|
||||||
self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> typing.List[SkyvernOptionType]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def select_by_index(
|
|
||||||
self, index: int, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Select2Dropdown(AbstractSelectDropdown):
|
|
||||||
def __init__(self, skyvern_frame: SkyvernFrame, skyvern_element: SkyvernElement) -> None:
|
|
||||||
self.skyvern_element = skyvern_element
|
|
||||||
self.skyvern_frame = skyvern_frame
|
|
||||||
|
|
||||||
async def __find_anchor(self, timeout: float) -> Locator:
|
|
||||||
locator = self.skyvern_element.get_frame().locator("[id='select2-drop']")
|
|
||||||
await locator.wait_for(state="visible", timeout=timeout)
|
|
||||||
cnt = await locator.count()
|
|
||||||
if cnt == 0:
|
|
||||||
raise NoDropdownAnchorErr(self.name(), self.skyvern_element.get_id())
|
|
||||||
if cnt > 1:
|
|
||||||
raise MultipleDropdownAnchorErr(self.name(), self.skyvern_element.get_id())
|
|
||||||
return locator
|
|
||||||
|
|
||||||
def name(self) -> str:
|
|
||||||
return "select2"
|
|
||||||
|
|
||||||
async def open(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
await self.skyvern_element.get_locator().click(timeout=timeout)
|
|
||||||
await self.__find_anchor(timeout=timeout)
|
|
||||||
|
|
||||||
async def close(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
anchor = await self.__find_anchor(timeout=timeout)
|
|
||||||
await anchor.press("Escape", timeout=timeout)
|
|
||||||
|
|
||||||
async def get_current_value(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> str:
|
|
||||||
tag_name = self.skyvern_element.get_tag_name()
|
|
||||||
if tag_name == "input":
|
|
||||||
# TODO: this is multiple options case, we haven't fully supported it yet.
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# check SkyvernElement.is_select2_dropdown() method, only <a> and <span> element left
|
|
||||||
# we should make sure the locator is on <a>, so we're able to find the [class="select2-chosen"] child
|
|
||||||
locator = self.skyvern_element.get_locator()
|
|
||||||
if tag_name == "span":
|
|
||||||
locator = locator.locator("..")
|
|
||||||
elif tag_name == "a":
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise FailedToGetCurrentValueOfDropdown(
|
|
||||||
self.name(), self.skyvern_element.get_id(), "invalid element of select2"
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return await locator.locator("span[class='select2-chosen']").text_content(timeout=timeout)
|
|
||||||
except Exception as e:
|
|
||||||
raise FailedToGetCurrentValueOfDropdown(self.name(), self.skyvern_element.get_id(), repr(e))
|
|
||||||
|
|
||||||
async def get_options(
|
|
||||||
self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> typing.List[SkyvernOptionType]:
|
|
||||||
anchor = await self.__find_anchor(timeout=timeout)
|
|
||||||
element_handler = await anchor.element_handle(timeout=timeout)
|
|
||||||
options = await self.skyvern_frame.get_select2_options(element_handler)
|
|
||||||
return typing.cast(typing.List[SkyvernOptionType], options)
|
|
||||||
|
|
||||||
async def select_by_index(
|
|
||||||
self, index: int, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> None:
|
|
||||||
anchor = await self.__find_anchor(timeout=timeout)
|
|
||||||
options = anchor.locator("ul").locator("li")
|
|
||||||
await options.nth(index).click(timeout=timeout)
|
|
||||||
|
|
||||||
|
|
||||||
class ReactSelectDropdown(AbstractSelectDropdown):
|
|
||||||
def __init__(self, skyvern_frame: SkyvernFrame, skyvern_element: SkyvernElement) -> None:
|
|
||||||
self.skyvern_element = skyvern_element
|
|
||||||
self.skyvern_frame = skyvern_frame
|
|
||||||
|
|
||||||
def __find_input_locator(self) -> Locator:
|
|
||||||
tag_name = self.skyvern_element.get_tag_name()
|
|
||||||
locator = self.skyvern_element.get_locator()
|
|
||||||
|
|
||||||
if tag_name == InteractiveElement.BUTTON:
|
|
||||||
return locator.locator("..").locator("..").locator("input[class*='select__input']")
|
|
||||||
|
|
||||||
return locator
|
|
||||||
|
|
||||||
async def __find_anchor(self, timeout: float) -> Locator:
|
|
||||||
input_locator = self.__find_input_locator()
|
|
||||||
anchor_id = await input_locator.get_attribute("aria-controls", timeout=timeout)
|
|
||||||
|
|
||||||
locator = self.skyvern_element.get_frame().locator(f"div[id='{anchor_id}']")
|
|
||||||
await locator.wait_for(state="visible", timeout=timeout)
|
|
||||||
cnt = await locator.count()
|
|
||||||
if cnt == 0:
|
|
||||||
raise NoDropdownAnchorErr(self.name(), self.skyvern_element.get_id())
|
|
||||||
if cnt > 1:
|
|
||||||
raise MultipleDropdownAnchorErr(self.name(), self.skyvern_element.get_id())
|
|
||||||
return locator
|
|
||||||
|
|
||||||
def name(self) -> str:
|
|
||||||
return "react-select"
|
|
||||||
|
|
||||||
async def open(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
await self.skyvern_element.get_locator().focus(timeout=timeout)
|
|
||||||
await self.skyvern_element.get_locator().press(key="ArrowDown", timeout=timeout)
|
|
||||||
await self.__find_anchor(timeout=timeout)
|
|
||||||
|
|
||||||
async def close(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
await self.__find_anchor(timeout=timeout)
|
|
||||||
await self.skyvern_element.get_locator().press(key="Escape", timeout=timeout)
|
|
||||||
|
|
||||||
async def get_current_value(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> str:
|
|
||||||
input_locator = self.__find_input_locator()
|
|
||||||
# TODO: only support single value now
|
|
||||||
value_locator = input_locator.locator("..").locator("..").locator("div[class*='select__single-value']")
|
|
||||||
if await value_locator.count() == 0:
|
|
||||||
return ""
|
|
||||||
try:
|
|
||||||
return await value_locator.text_content(timeout=timeout)
|
|
||||||
except Exception as e:
|
|
||||||
raise FailedToGetCurrentValueOfDropdown(self.name(), self.skyvern_element.get_id(), repr(e))
|
|
||||||
|
|
||||||
async def get_options(
|
|
||||||
self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> typing.List[SkyvernOptionType]:
|
|
||||||
input_locator = self.__find_input_locator()
|
|
||||||
element_handler = await input_locator.element_handle(timeout=timeout)
|
|
||||||
options = await self.skyvern_frame.get_react_select_options(element_handler)
|
|
||||||
return typing.cast(typing.List[SkyvernOptionType], options)
|
|
||||||
|
|
||||||
async def select_by_index(
|
|
||||||
self, index: int, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> None:
|
|
||||||
anchor = await self.__find_anchor(timeout=timeout)
|
|
||||||
options = anchor.locator("div[class*='select__option']")
|
|
||||||
await options.nth(index).click(timeout=timeout)
|
|
||||||
|
|
||||||
|
|
||||||
class ComboboxDropdown(AbstractSelectDropdown):
|
|
||||||
def __init__(self, skyvern_frame: SkyvernFrame, skyvern_element: SkyvernElement) -> None:
|
|
||||||
self.skyvern_element = skyvern_element
|
|
||||||
self.skyvern_frame = skyvern_frame
|
|
||||||
|
|
||||||
async def __find_anchor(self, timeout: float) -> Locator:
|
|
||||||
control_id = await self.skyvern_element.get_attr("aria-controls", timeout=timeout)
|
|
||||||
locator = self.skyvern_element.get_frame().locator(f"[id='{control_id}']")
|
|
||||||
await locator.wait_for(state="visible", timeout=timeout)
|
|
||||||
cnt = await locator.count()
|
|
||||||
if cnt == 0:
|
|
||||||
raise NoDropdownAnchorErr(self.name(), self.skyvern_element.get_id())
|
|
||||||
if cnt > 1:
|
|
||||||
raise MultipleDropdownAnchorErr(self.name(), self.skyvern_element.get_id())
|
|
||||||
return locator
|
|
||||||
|
|
||||||
def name(self) -> str:
|
|
||||||
return "combobox"
|
|
||||||
|
|
||||||
async def open(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
await self.skyvern_element.get_locator().click(timeout=timeout)
|
|
||||||
await self.__find_anchor(timeout=timeout)
|
|
||||||
|
|
||||||
async def close(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> None:
|
|
||||||
await self.skyvern_element.get_locator().press("Tab", timeout=timeout)
|
|
||||||
|
|
||||||
async def get_current_value(self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS) -> str:
|
|
||||||
try:
|
|
||||||
return await self.skyvern_element.get_attr("value", dynamic=True, timeout=timeout)
|
|
||||||
except Exception as e:
|
|
||||||
raise FailedToGetCurrentValueOfDropdown(self.name(), self.skyvern_element.get_id(), repr(e))
|
|
||||||
|
|
||||||
async def get_options(
|
|
||||||
self, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> typing.List[SkyvernOptionType]:
|
|
||||||
anchor = await self.__find_anchor(timeout=timeout)
|
|
||||||
element_handler = await anchor.element_handle()
|
|
||||||
options = await self.skyvern_frame.get_combobox_options(element_handler)
|
|
||||||
return typing.cast(typing.List[SkyvernOptionType], options)
|
|
||||||
|
|
||||||
async def select_by_index(
|
|
||||||
self, index: int, timeout: float = SettingsManager.get_settings().BROWSER_ACTION_TIMEOUT_MS
|
|
||||||
) -> None:
|
|
||||||
anchor = await self.__find_anchor(timeout=timeout)
|
|
||||||
options = anchor.locator("li[role='option']")
|
|
||||||
await options.nth(index).click(timeout=timeout)
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from typing import Any, Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from playwright._impl._errors import TimeoutError
|
from playwright._impl._errors import TimeoutError
|
||||||
@@ -168,21 +168,6 @@ class SkyvernFrame:
|
|||||||
js_script = "(element) => scrollToElementTop(element)"
|
js_script = "(element) => scrollToElementTop(element)"
|
||||||
return await self.frame.evaluate(js_script, element)
|
return await self.frame.evaluate(js_script, element)
|
||||||
|
|
||||||
async def get_select2_options(self, element: ElementHandle) -> List[Dict[str, Any]]:
|
|
||||||
await self.frame.evaluate(JS_FUNCTION_DEFS)
|
|
||||||
js_script = "async (element) => await getSelect2Options(element)"
|
|
||||||
return await self.frame.evaluate(js_script, element)
|
|
||||||
|
|
||||||
async def get_react_select_options(self, element: ElementHandle) -> List[Dict[str, Any]]:
|
|
||||||
await self.frame.evaluate(JS_FUNCTION_DEFS)
|
|
||||||
js_script = "async (element) => await getReactSelectOptions(element)"
|
|
||||||
return await self.frame.evaluate(js_script, element)
|
|
||||||
|
|
||||||
async def get_combobox_options(self, element: ElementHandle) -> List[Dict[str, Any]]:
|
|
||||||
await self.frame.evaluate(JS_FUNCTION_DEFS)
|
|
||||||
js_script = "async (element) => await getListboxOptions(element)"
|
|
||||||
return await self.frame.evaluate(js_script, element)
|
|
||||||
|
|
||||||
async def parse_element_from_html(self, frame: str, element: ElementHandle, interactable: bool) -> Dict:
|
async def parse_element_from_html(self, frame: str, element: ElementHandle, interactable: bool) -> Dict:
|
||||||
js_script = "([frame, element, interactable]) => buildElementObject(frame, element, interactable)"
|
js_script = "([frame, element, interactable]) => buildElementObject(frame, element, interactable)"
|
||||||
return await self.frame.evaluate(js_script, [frame, element, interactable])
|
return await self.frame.evaluate(js_script, [frame, element, interactable])
|
||||||
|
|||||||
Reference in New Issue
Block a user