Python_Selenium2Library源碼分析

I. Introduction

Selenium2Library是robot framework中主流的測試網頁功能的庫, 它的本質是對webdriver的二次封裝, 以適應robot框架. 百度上一堆Selenium2Library的介紹, 這裏再也不炒剩飯. 可是源碼分析的資料, 少之又少. 因此本文就從源碼角度介紹Selenium2Library. 一方面可以瞭解robot framework是如何對原有庫或驅動進行二次封裝, 另外一方面能增強對python的瞭解和使用.javascript

Selenium2Library包括4個package:css

  • keywords
  • locators
  • resources
  • utils

keywords 即關鍵字集; locators從字面上理解是定位, 從監測到網頁的元素到應該執行的方法之間也須要"定位", 經過locator做爲橋樑, 傳遞方法\屬性\值; resources裏只放了和firefox文件相關的資源; utils裏是一些基本的web初始化操做, 如打開瀏覽器, 關閉瀏覽器. 最後整個Library還調用了WebDriver的驅動, 同時得到了WebDriver提供的Monkey Patch功能.html

II. Module: keywords

 全部關鍵字封裝, 還包含了對關鍵字的"技能加成":java

  • __init__.py
  • _browsermanagement.py
  • _cookie.py
  • _element.py
  • _formelement.py
  • _javascript.py
  • _logging.py
  • _runonfailure.py 運行失敗的異常處理封裝
  • _screenshot.py
  • _selectelement.py
  • _tableelement.py
  • _waiting.py 各類條件的等待
  • keywordgroup.py

2.1 __init__.py

每一個module都須要__init__.py文件,用於啓動, 配置, 描述對外接口. 這裏只把keywords的__init__.py拿出來解讀:python

 1 # from modula import * 表示默認導入modula中全部不如下劃線開頭的成員
 2 from _logging import _LoggingKeywords
 3 from _runonfailure import _RunOnFailureKeywords
 4 from _browsermanagement import _BrowserManagementKeywords
 5 from _element import _ElementKeywords
 6 from _tableelement import _TableElementKeywords
 7 from _formelement import _FormElementKeywords
 8 from _selectelement import _SelectElementKeywords
 9 from _javascript import _JavaScriptKeywords
10 from _cookie import _CookieKeywords
11 from _screenshot import _ScreenshotKeywords
12 from _waiting import _WaitingKeywords
13 
14 # 定義了__all__後, 表示只導出如下列表中的成員
15 __all__ = [
16     "_LoggingKeywords",
17     "_RunOnFailureKeywords",
18     "_BrowserManagementKeywords",
19     "_ElementKeywords",
20     "_TableElementKeywords",
21     "_FormElementKeywords",
22     "_SelectElementKeywords",
23     "_JavaScriptKeywords",
24     "_CookieKeywords",
25     "_ScreenshotKeywords",
26     "_WaitingKeywords"
27 ]

 

2.2 _waiting.py

每一個wait函數, 除了wait條件不一樣, 須要調用self.***函數進行前置條件判斷外, 最終實現都是調用該類兩個內部函數之一的_wait_until_no_error(self, timeout, wait_func, *args). 而含有條件的等待, 相比sleep函數在每次執行後強制等待固定時間, 能夠有效節省執行時間, 也能儘早拋出異常jquery

_wait_until_no_error(self, timeout, wait_func, *args)

說明: 等待, 直到傳入的函數wait_func(*args)有返回, 或者超時. 底層實現, 邏輯覆蓋徹底, 參數最齊全, 最抽象.web

參數:瀏覽器

  timeout: 超時時間cookie

  wait_func: 函數做爲參數傳遞框架

返回:

  None\error(Timeout or 其它)

1     def _wait_until_no_error(self, timeout, wait_func, *args): 2 timeout = robot.utils.timestr_to_secs(timeout) if timeout is not None else self._timeout_in_secs 3 maxtime = time.time() + timeout 4 while True: 5 timeout_error = wait_func(*args) 6 if not timeout_error: return #若是wait_func()無返回,進行超時判斷;不然返回wait_func()執行結果 7 if time.time() > maxtime: #超時強制拋出timeout_error異常 8 raise AssertionError(timeout_error) 9 time.sleep(0.2)

 

_wait_until(self, timeout, error, function, *args)

說明: 等待, 直到傳入的函數function(*args)有返回, 或者超時

參數:

  error: 初始化爲超時異常, 是對_wait_until_no_error的又一層功能刪減版封裝, 使得error有且僅有一種error: timeout

  function: 條件判斷, 返回True or False  

返回:

  None\error(Timeout)

1 def _wait_until(self, timeout, error, function, *args): 2 error = error.replace('<TIMEOUT>', self._format_timeout(timeout)) 3 def wait_func(): 4 return None if function(*args) else error 5 self._wait_until_no_error(timeout, wait_func)

 

wait_for_condition(self, condition, timout=None, error=None)

說明: 等待, 直到知足condition條件或者超時

備註: 傳入函數參數時使用了python的lambda語法, 簡單來講就是函數定義的代碼簡化版. 知乎上有篇關於Lambda的Q&A很是棒: Lambda表達式有何用處?如何使用?-Python-知乎. 因此分析源碼時又插入了抽象函數邏輯, 高階函數學習的小插曲, 腦補了Syntanic Sugar, 真是...抓不住西瓜芝麻掉一地...

1     def wait_for_condition(self, condition, timeout=None, error=None): 2 if not error: 3 error = "Condition '%s' did not become true in <TIMEOUT>" % condition 4  self._wait_until(timeout, error, lambda: self._current_browser().execute_script(condition) == True)

 

調用條件判斷self._is_text_present(text)系列

wait_until_page_contains(self, text, timeout=None, error=None)

wait_until_page_does_not_contain(self, text, timeout=None, error=None)

 

調用條件判斷self._is_element_present(locator)系列

wait_until_page_contains_element(self, locator, timeout=None, error=None)

wait_until_page_does_not_contain_element(self, locator, timeout=None, error=None)

 

調用條件判斷self._is_visible(locator)系列

wait_until_element_is_visible(self, locator, timeout=None, error=None)

wait_until_element_is_not_visible(self, locator, timeout=None, error=None)

 

調用條件判斷self._element_find(locator, True, True)系列

wait_until_element_is_enabled(self, locator, timeout=None, error=None)

wait_until_element_contains(self, locator, text, timeout=None, error=None)

wait_until_element_does_not_contain(self, locator, text, timeout=None, error=None)

2.3 keywordgroup.py

keywordgroup裏的兩個類和一個內部方法很好理解, 就是爲每一個關鍵字加上 _run_on_failure_decorator 的"技能",  用到了python的decorator語法, 這也是繼上文的lambda語法糖後遇到的另外一種小技巧.

和默認傳統類的類型不一樣, KeywordGroup的元類屬性被重定義爲KeywordGroupMetaClass, 爲何要從新定義元類? 看源碼能夠發現, 元類的構造器被重定義了, 全部該類的派生對象都會在構造時判斷是否添加_run_on_failure_decorator 方法:

1 class KeywordGroupMetaClass(type):
2     def __new__(cls, clsname, bases, dict):
3         if decorator:
4             for name, method in dict.items():
5                 if not name.startswith('_') and inspect.isroutine(method):
6                     dict[name] = decorator(_run_on_failure_decorator, method) 7         return type.__new__(cls, clsname, bases, dict)

在keywordgroup.py裏爲每一個傳入的關鍵字加上了decorator, 那麼這些關鍵字在定義後又是如何傳入keywordgroup的類構造器中的呢? _runonfailure.py中給出了答案:

 1     def register_keyword_to_run_on_failure(self, keyword):
 2         old_keyword = self._run_on_failure_keyword
 3         old_keyword_text = old_keyword if old_keyword is not None else "No keyword"
 4 
 5         new_keyword = keyword if keyword.strip().lower() != "nothing" else None
 6         new_keyword_text = new_keyword if new_keyword is not None else "No keyword"
 7 
 8         self._run_on_failure_keyword = new_keyword
 9         self._info('%s will be run on failure.' % new_keyword_text)
10 
11         return old_keyword_text

上面的代碼做用是當一個Selenium2Library中的關鍵字執行失敗後, 執行指定的keyword. 默認失敗後執行"Capture Page Screenshot". 

 

III. Module: locators

Selenium中提供了多種元素定位策略, 在locators中實現. 其實這些定位方法都是對WebDriver的元素定位接口的封裝.

3.1 elementfinder.py

Web元素定位有不少策略, 如經過id, name, xpath等屬性定位, 這些不一樣定位策略的最終實現是經過find(self, browser, locator, tag=None)方法. 傳入瀏覽器對象browser和定位元素對象locator, 經過解析locator, 獲得兩個信息: 前綴prefix, 定位規則criteria. 前綴即不一樣策略, 可是解析前綴的這句strategy = self._strategies.get(prefix)很差理解, 如何從prefix獲得對應的定位strategy?

其實在整個ElementFinder類__init__()的時候, 就初始化了這個self._strategies對象:

 1     def __init__(self):
 2         strategies = {
 3             'identifier': self._find_by_identifier,
 4             'id': self._find_by_id,
 5             'name': self._find_by_name,
 6             'xpath': self._find_by_xpath,
 7             'dom': self._find_by_dom,
 8             'link': self._find_by_link_text,
 9             'partial link': self._find_by_partial_link_text,
10             'css': self._find_by_css_selector,
11             'jquery': self._find_by_sizzle_selector,
12             'sizzle': self._find_by_sizzle_selector,
13             'tag': self._find_by_tag_name,
14             'scLocator': self._find_by_sc_locator,
15             'default': self._find_by_default
16         }
17         self._strategies = NormalizedDict(initial=strategies, caseless=True, spaceless=True)
18         self._default_strategies = strategies.keys()

看到"定位元素":"定位策略"的一串列表, 恍然大悟. 無論這個在robot.utils中的自定義字典類NormalizedDict的具體實現, 該類型的對象self._strategies基本用途就是: 全部元素定位策略的方法列表經過key值查詢. 這樣一來就好理解了, find()方法最終也只是起到了派發的做用, 給strategy對象賦予了屬於它的定位方法.--->再往下接着跟self._find_*方法心好累, 下次吧......

接下來就能夠看find(self, browser, locator, tag=None)源碼了, 理解了strategy就沒什麼特別的地方了:

 1     def find(self, browser, locator, tag=None):
 2         assert browser is not None
 3         assert locator is not None and len(locator) > 0
 4         (prefix, criteria) = self._parse_locator(locator) # 從locator對象中提取prefix和criteria
 5         prefix = 'default' if prefix is None else prefix
 6         strategy = self._strategies.get(prefix) # self._strategies對象的屬性石robot.util中的NormalizeDict(標準化字典)
 7         if strategy is None:
 8             raise ValueError("Element locator with prefix '" + prefix + "' is not supported")
 9         (tag, constraints) = self._get_tag_and_constraints(tag)
10         return strategy(browser, criteria, tag, constraints) # 返回strategy的find結果

 

VI. Module: resources

Selenium只提供了和firefox相關的資源文件, 受到 Selenium webdriver 學習總結-元素定位 文章的啓發, 我的覺着應該是firefox提供了豐富全面的組件, 可以集成selenium的緣故吧. 不是作Web開發不瞭解, 但firefox的功能強大豐富是一直有所耳聞. 因此咱們不妨推測, resources的內容就是對firefox的組件和方法的描述?

相關文章
相關標籤/搜索