談談selenium4.0中的相對定位

2023-10-20 12:01:02

相對定位歷史

  • 2021-10-13 釋出的 selenium 4.0 開始引入,selenium 3.X是沒有的
implement relative locator for find_element (#9902)
  • 4.10維護了下
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

在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然後去執行它,細節就不追究了

RelativeBy

這個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

在範例程式碼中,我們用到了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中吧,有空找下,或者哪個大佬知道的可以指點下