一直覺得Selenium2Library對selenium的封裝很贊,最近模拟它的結構封裝給一個同僚寫了個C# selenium的demo,過程中看了細看了一部分源碼。加上之前封裝的内容,分享一波。
注1:以下涉及到RF的腳本全未加延時sleep,如需調試驗證,請自行添加;
擴充包位址:
https://github.com/daassh/SeleniumExtend
使用方法:
不再導入Selenium2Library,而是導入此檔案。
_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.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 關于同城
${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:
:: : INFO : Opening url 'http://www.58.com'
:: : INFO : Clicking element 'link=防網絡詐騙'.
:: : INFO : Clicking element 'link=關于我們'.
:: : INFO : ${now_title} = 【同城 com】南昌分類資訊 - 本地 免費 高效
:: : INFO : ${handle_main} = CDwindow-AEEE--C--CECB2AC34EBE
:: : INFO : ${now_title} = 打擊網絡詐騙
:: : INFO : ${handle_cheat} = CDwindow--EE2D-C4-BFD2-BA43EB426544
:: : INFO : ${now_title} = 關于同城
:: : INFO : ${handle_about} = CDwindow-D0B5-A85---E308EFA
:: : INFO : ${now_title} = 【同城 com】南昌分類資訊 - 本地 免費 高效
:: : 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) == :
raise ValueError("Element locator '" + locator + "' did not match any elements.")
if first_only:
if len(elements) == : return None
return elements[]
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) >
#在這裡把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’一樣了。舉例如下:
通過如下方法都可以點選此連結
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()
Click Element /html/body/div[]/p[]/a[]
推薦使用前2種方法,強烈不推薦使用後面這2種方法,一是可讀性差,二是後續開發更新網站結構變動,代碼就執行出錯了。
使用javascript解決疑難雜症
Click Element不是萬能的,如下網站
http://shenzhen.jjshome.com/esf/
如果想在“更多”裡面選擇“樓齡”為“2年以下”的房子,可能會寫如下腳本:
#點選樓齡
Click Element css=.ll-choosen
#點選2年以下
Click Element css=.ll>:nth-child()
但會發現,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) > and locator_css[:].lower() == u"css=":
locator_css = locator_css[:]
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()
擴充方法二,使用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()
另外,針對一些非标準下拉框也适用此方法,如下:
<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