implement relative locator for find_element (#9902)
Improve near relative locator behavior (#11290)
其他都是檔案、異常資訊方面的處理
D:\selenium\demo\relative.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>relative</title>
</head>
<body>
DATE:<input id="date" type="text">
USER:<input id="username" type="text"><br>
CODE:<input id="code" type="text">
PASS:<input id="password" type="text">
</body>
</html>
如下介面
範例程式碼
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with
from selenium import webdriver
from time import sleep
driver = webdriver.Chrome()
driver.get(r'D:\selenium\demo\relative.html')
ele_date = driver.find_element('id','date')
ele_code = driver.find_element('id','code')
ele_user = driver.find_element('id','username')
ele_password = driver.find_element('id','password')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").above(ele_code)).send_keys('code aboe')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").below(ele_user)).send_keys('user below')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").to_left_of(ele_password)).send_keys('pass left')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").to_right_of(ele_date)).send_keys('date right')
driver.find_element(locate_with(By.CSS_SELECTOR, "input").near(ele_code)).send_keys('code near')
執行效果
在find_element的原始碼中有這麼一段
def find_element(self, by=By.ID, value=None) -> WebElement:
if isinstance(by, RelativeBy):
elements = self.find_elements(by=by, value=value)
if not elements:
raise NoSuchElementException(f"Cannot locate relative element with: {by.root}")
return elements[0]
也就是說你傳入的by不僅僅可以是下面這8個,還可以是RelativeBy物件
class By:
"""
Set of supported locator strategies.
"""
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
那如果是RelativeBy物件的話,會去呼叫find_elements,self.find_elements(by=by, value=value)
def find_elements(self, by=By.ID, value=None) -> List[WebElement]:
if isinstance(by, RelativeBy):
_pkg = '.'.join(__name__.split('.')[:-1])
raw_function = pkgutil.get_data(_pkg, 'findElements.js').decode('utf8')
find_element_js = f"return ({raw_function}).apply(null, arguments);"
return self.execute_script(find_element_js, by.to_dict())
if語句下的2行程式碼就是在載入findElements.js
最後兩句就是構造一個js然後去執行它,細節就不追究了
這個class位於selenium\webdriver\support\relative_locator.py
class RelativeBy:
def __init__(self, root: Dict[By, str] = None, filters: List = None):
self.root = root
self.filters = filters or []
def above(self, element_or_locator: Union[WebElement, Dict] = None) -> "RelativeBy":
if not element_or_locator:
raise WebDriverException("Element or locator must be given when calling above method")
self.filters.append({"kind": "above", "args": [element_or_locator]})
return self
這個類提供了6個實體方法:above
below
to_left_of
to_right_of
near
可以看到RelativeBy物件的範例化需要2個引數,一個是root:dict型別,一個是filters : 列表型別
可以看到above這樣的方法其實沒做啥,關鍵是對self.filters的一個處理,增加一個對應kind(與方法同名)和args,這個args操作你要去參考的元素的定位器或WebElement
在範例程式碼中,我們用到了locate_with這個函數,這個函數跟RelativeBy在同一個檔案中
程式碼如下
def locate_with(by: By, using: str) -> "RelativeBy":
assert by is not None, "Please pass in a by argument"
assert using is not None, "Please pass in a using argument"
return RelativeBy({by: using})
可以看到它確實是返回了一個RelativeBy的範例物件
而它的用法跟我們的find_element就一致了,唯一的不同就是引數名,這邊是using,find_element是value
為何用它的另一方面原因是在RelativeBy的doc中這樣的一段描述
Example:
lowest = driver.find_element(By.ID, "below")
elements = driver.find_elements(locate_with(By.CSS_SELECTOR, "p").above(lowest))
這東西我在工作中沒有用過,因為它出生後我就進入了...
使用過一些常見去測試它的效果,並不理想,不過是在早期的版本中做的,現在不清楚是否好用一些
溯源的話應該可以追溯到js中吧,有空找下,或者哪個大佬知道的可以指點下