1. 程式人生 > >Selenium2Library原始碼解析與擴充套件(一)

Selenium2Library原始碼解析與擴充套件(一)

一直覺得Selenium2Library對selenium的封裝很贊,最近模擬它的結構封裝給一個同事寫了個C# selenium的demo,過程中看了細看了一部分原始碼。加上之前封裝的內容,分享一波。

注1:以下涉及到RF的指令碼全未加延時sleep,如需除錯驗證,請自行新增;

_browsermanagement.py

主要是處理瀏覽器的一些初始化操作,如瀏覽器開啟、關閉等。

create_webdriver()

建立一個webdriver。如需載入使用者當前瀏覽器配置,則可用下面這方法(路徑對應修改):

SuiteSetup
    [Documentation]    初始化
    ${
chrome_options}= Evaluate sys.modules['selenium.webdriver'].ChromeOptions() sys, selenium.webdriver Call Method ${chrome_options} add_argument --user-data-dir\=C:/Users/dassh/AppData/Local/Google/Chrome/User Data Create Webdriver Chrome chrome_options=${chrome_options} Maximize
Browser Window

select_window()

切換至指定標籤頁,返回切換前頁面的控制代碼。相信很多人用得稀裡糊塗,以下是標準用法示例:

Go To   http://www.58.com   
Click Element   link=防網路詐騙  
Click Element   link=關於我們   
${now_title}=  Get Title   #當前title仍是'58首頁'
${handle_main}=    Select Window   打擊網路詐騙
${now_title}=  Get Title   #當前title變為'打擊網路詐騙'
${handle_cheat}= Select Window 關於58同城 ${now_title}= Get Title #當前title變為'關於58同城' ${handle_about}= Select Window ${handle_main} ${now_title}= Get Title #當前title變為'58首頁' Input Text id=keyword 測試 #'58首頁'搜尋框輸入'測試'

Robot Framework log:

20160410 19:38:20.077 :  INFO : Opening url 'http://www.58.com'
20160410 19:38:22.668 :  INFO : Clicking element 'link=防網路詐騙'.
20160410 19:38:23.395 :  INFO : Clicking element 'link=關於我們'.
20160410 19:38:25.405 :  INFO : ${now_title} = 【58同城 58.com】南昌分類資訊 - 本地 免費 高效
20160410 19:38:41.237 :  INFO : ${handle_main} = CDwindow-2131AEEE-68E1-414C-9347-CECB2AC34EBE
20160410 19:38:41.247 :  INFO : ${now_title} = 打擊網路詐騙
20160410 19:38:41.276 :  INFO : ${handle_cheat} = CDwindow-18737670-EE2D-46C4-BFD2-BA43EB426544
20160410 19:38:41.283 :  INFO : ${now_title} = 關於58同城
20160410 19:38:41.297 :  INFO : ${handle_about} = CDwindow-59E9D0B5-6A85-42E4-9166-809E4E308EFA
20160410 19:38:41.306 :  INFO : ${now_title} = 【58同城 58.com】南昌分類資訊 - 本地 免費 高效
20160410 19:38:41.309 :  INFO : Typing text '測試' into text field 'id=keyword'.

注意,select_window()切換網頁標籤並不會反應到瀏覽器上,也就是說“頁面一”用了select_window()到“頁面二”,瀏覽器本身並不會切換到“頁面二”,但你之後的所有操作都是針對“頁面二”的。

title_should_be()

校驗當前標題。但當標題過長時使用不便,做如下擴充套件:

def title_should_contain(self, *title_piece):
    """Verifies that current title contains `title_piece`."""

    title = self.get_title()
    for expected in title_piece:
        if None == re.search(expected, title):
            raise AssertionError("Title should contain '%s' but was '%s'" % (expected, title))
    self._info("Page title is '%s'." % title)  

注:後續講到_waiting.py,還會對此方法進行擴充套件。

從而支援部分校驗,如:

Title Should Contain    一下    百度    知道    你就

_element.py _formelement.py _selectelement.py

常用的操作都在三個這裡了,點選、輸入、下拉框、校驗等。

locator解析原理:

基本上所有的定位操作都呼叫了_element_find方法:

# @locator: 定位器
# @first_only: 指定是否返回第一個匹配或者全部匹配(列表)
# @required: 匹配元素為0時是否報錯
# @tag: 指定標籤過濾
def _element_find(self, locator, first_only, required, tag=None):
    browser = self._current_browser()
    if isstr(locator):
        #這裡呼叫了locators/elementfinder.py 的find方法
        elements = self._element_finder.find(browser, locator, tag)
        if required and len(elements) == 0:
            raise ValueError("Element locator '" + locator + "' did not match any elements.")
        if first_only:
            if len(elements) == 0: return None
            return elements[0]
    elif isinstance(locator, WebElement):
        elements = locator
    return elements

再看locators/elementfinder.py :

# @browser: 瀏覽器
# @locator: 定位器
# @tag: 指定標籤過濾
def find(self, browser, locator, tag=None):
    assert browser is not None
    assert locator is not None and len(locator) > 0
    #在這裡把locator解析成字首prefix(如id、css,xpath等),和方法
    (prefix, criteria) = self._parse_locator(locator)
    prefix = 'default' if prefix is None else prefix
    #根據標籤獲取方法名,參考下面__init__
    strategy = self._strategies.get(prefix)
    if strategy is None:
        raise ValueError("Element locator with prefix '" + prefix + "' is not supported")
    #解析標籤
    (tag, constraints) = self._get_tag_and_constraints(tag)
    #呼叫具體方法
    return strategy(browser, criteria, tag, constraints)

支援的查詢元素方法如下

def __init__(self):
    strategies = {
        'identifier': self._find_by_identifier,
        'id': self._find_by_id,
        'name': self._find_by_name,
        'xpath': self._find_by_xpath,
        'dom': self._find_by_dom,
        'link': self._find_by_link_text,
        'partial link': self._find_by_partial_link_text,
        'css': self._find_by_css_selector,
        'jquery': self._find_by_sizzle_selector,
        'sizzle': self._find_by_sizzle_selector,
        'tag': self._find_by_tag_name,
        'scLocator': self._find_by_sc_locator,
        'default': self._find_by_default
    }
    ...

常用的id、name、xpath、css、link、partial link、tag大家都很熟悉,’jquery’寫法和css類似,但是底層通過execute_script匹配元素;’identifier’就是即通過id也通過name匹配元素;’default’就是當你未指定使用哪種方法匹配元素(或者指定的不在上面這些方式內)使用的,如果標籤過濾tag為None,做用就和’identifier’一樣了。舉例如下:

<a href="xxxxx" name="x", id="y" >

通過如下方法都可以點選此連結

Click Element    name=x    #呼叫_find_by_name
Click Element    id=x    #呼叫_find_by_id
Click Element    identifier=x    #呼叫_find_by_identifier
Click Element    x    #呼叫_find_by_default
Click Element    y    #呼叫_find_by_default

推薦使用前三種方法,提升指令碼易讀性。說到這裡順便說下關於locator的寫法,有些人喜歡直接審查元素再右鍵複製一長串xpath、css,如下頁面:

<html>
    <body>
        <div>
            <p class="m-lt-nav" data-opt="leftnavcontainer">
                <a class="lst" data-nid="1"</a>
                <a class="lst" data-nid="2"</a>
                <a class="lst" data-nid="3"</a>
                <a class="lst" data-nid="4"</a>
            </p>
        </div>
    </body>
</html>

通過下面幾種方法都可以點選元素:

Click Element    css=a[data-nid='3']
Click Element    //a[data-nid='3']
Click Element    body > div > p > a:nthchild(3)
Click Element    /html/body/div[0]/p[0]/a[2]

推薦使用前2種方法,強烈不推薦使用後面這2種方法,一是可讀性差,二是後續開發升級網站結構變動,程式碼就執行出錯了。

使用javascript解決疑難雜症

Click Element不是萬能的,如下網站
http://shenzhen.jjshome.com/esf/
如果想在“更多”裡面選擇“樓齡”為“2年以下”的房子,可能會寫如下指令碼:

#點選樓齡
Click Element   css=.ll-choosen
#點選2年以下
Click Element   css=.ll>:nth-child(2)

但會發現,10次難成功1次,因為你執行第二步操作時,下拉框已經沒了,F12觀察下拉框出現/消失前後的變化會發現,出現前的class為“select-choosen fang-ll ll-choosen”,出現後的class為“select-choosen fang-ll ll-choosen hover”,於是擴充套件如下方法一:

def js_set_attr(self, locator_css, attribute, value):
    """Set attribute's value for element what locator_css located."""

    locator_css = self._format_css(locator_css)
    js = "document.querySelector('%s').setAttribute('%s','%s')" % (locator_css, attribute, value)
    return self.execute_javascript(js)

def _format_css(self, locator_css):
    if len(locator_css) > 4 and locator_css[:4].lower() == u"css=":
        locator_css = locator_css[4:]
    return locator_css

_format_css是用來統一css格式的,之後使用指令碼:

Js Set Attr    css=.ll-choosen    class    select-choosen fang-ll ll-choosen hover
Click Element   css=.ll>:nth-child(2)       

擴充套件方法二,使用javascript click:

def click_element_js(self, locator_css):
    """Try JavaScript click element identified by `locator_css`."""

    locator_css = self._format_css(locator_css)
    code = 'document.querySelector("'+ locator_css + '").click()'
    self._info("JavaScript clicking element '%s'" % locator_css)
    return self.execute_javascript(code)

注:後續講到_waiting.py,還會對此方法進行擴充套件。

然後指令碼一句搞定:

Click Element Js    css=.ll>:nth-child(2)   

另外,針對一些非標準下拉框也適用此方法,如下:

<span class="f-ib" data-type="province">
    <span class="cbb-opt-inner" tabindex="0" style="overflow-y: hidden; outline: none;">
        <a class="lst" title="北京" data-pid="0" data-id="1">北京</a>
        <a class="lst" title="上海" data-pid="0" data-id="18">上海</a>
        <a class="lst" title="天津" data-pid="0" data-id="38">天津</a>
        <a class="lst" title="重慶" data-pid="0" data-id="57">重慶</a>
        <a class="lst" title="河北" data-pid="0" data-id="98">河北</a>
        <a class="lst" title="山西" data-pid="0" data-id="134">山西</a>
        <a class="lst" title="內蒙古自治區" data-pid="0" data-id="158">內蒙古自治區</a>
        <a class="lst" title="江西" data-pid="0" data-id="373">江西</a>
        ...
    </span>
</span>

效果如下圖:

這種情況下所有’Select From List’系列全部無效,於是指令碼這樣寫:

Click Element    css=span[data-value='province']
Click Element    css=a[title='天津']

可以執行,但注意,這裡的’天津’在上圖是可以看到的,如果把’天津’換成’江西’,就會發現到這些時找不到元素了。換用上面的click_element_js:

Click Element Js    css=a[title='江西']

這樣就可以點選到了,注意,使用此方法無需先點出下拉框。其它任何使用Click Element不穩定的地方都可嘗試此方法。

第一節就先說這麼多,下一節說_waiting.py及其擴充套件。

by dassh