I. Introduction

Selenium2Library是robot framework中主流的测试网页功能的库, 它的本质是对webdriver的二次封装, 以适应robot框架. 百度上一堆Selenium2Library的介绍, 这里不再炒剩饭. 但是源码分析的资料, 少之又少. 所以本文就从源码角度介绍Selenium2Library. 一方面能够了解robot framework是如何对原有库或驱动进行二次封装, 另一方面能加强对python的了解和使用.

Selenium2Library包括4个package:

  • keywords
  • locators
  • resources
  • utils

keywords 即关键字集; locators从字面上理解是定位, 从监测到网页的元素到应该执行的方法之间也需要"定位", 通过locator作为桥梁, 传递方法\属性\值; resources里只放了和firefox文件相关的资源; utils里是一些基本的web初始化操作, 如打开浏览器, 关闭浏览器. 最后整个Library还调用了WebDriver的驱动, 同时获得了WebDriver提供的Monkey Patch功能.

II. Module: keywords

所有关键字封装, 还包含了对关键字的"技能加成":

  • __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拿出来解读:

 # from modula import * 表示默认导入modula中所有不以下划线开头的成员
from _logging import _LoggingKeywords
from _runonfailure import _RunOnFailureKeywords
from _browsermanagement import _BrowserManagementKeywords
from _element import _ElementKeywords
from _tableelement import _TableElementKeywords
from _formelement import _FormElementKeywords
from _selectelement import _SelectElementKeywords
from _javascript import _JavaScriptKeywords
from _cookie import _CookieKeywords
from _screenshot import _ScreenshotKeywords
from _waiting import _WaitingKeywords # 定义了__all__后, 表示只导出以下列表中的成员
__all__ = [
"_LoggingKeywords",
"_RunOnFailureKeywords",
"_BrowserManagementKeywords",
"_ElementKeywords",
"_TableElementKeywords",
"_FormElementKeywords",
"_SelectElementKeywords",
"_JavaScriptKeywords",
"_CookieKeywords",
"_ScreenshotKeywords",
"_WaitingKeywords"
]

2.2 _waiting.py

每个wait函数, 除了wait条件不同, 需要调用self.***函数进行前置条件判断外, 最终实现都是调用该类两个内部函数之一的_wait_until_no_error(self, timeout, wait_func, *args). 而含有条件的等待, 相比sleep函数在每次执行后强制等待固定时间, 可以有效节省执行时间, 也能尽早抛出异常

_wait_until_no_error(self, timeout, wait_func, *args)

说明: 等待, 直到传入的函数wait_func(*args)有返回, 或者超时. 底层实现, 逻辑覆盖完全, 参数最齐全, 最抽象.

参数:

  timeout: 超时时间

  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 方法:

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

在keywordgroup.py里为每个传入的关键字加上了decorator, 那么这些关键字在定义后又是如何传入keywordgroup的类构造器中的呢? _runonfailure.py中给出了答案:

     def register_keyword_to_run_on_failure(self, keyword):
old_keyword = self._run_on_failure_keyword
old_keyword_text = old_keyword if old_keyword is not None else "No keyword" new_keyword = keyword if keyword.strip().lower() != "nothing" else None
new_keyword_text = new_keyword if new_keyword is not None else "No keyword" self._run_on_failure_keyword = new_keyword
self._info('%s will be run on failure.' % new_keyword_text) 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对象:

     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
}
self._strategies = NormalizedDict(initial=strategies, caseless=True, spaceless=True)
self._default_strategies = strategies.keys()

看到"定位元素":"定位策略"的一串列表, 恍然大悟. 不管这个在robot.utils中的自定义字典类NormalizedDict的具体实现, 该类型的对象self._strategies基本用途就是: 所有元素定位策略的方法列表通过key值查询. 这样一来就好理解了, find()方法最终也只是起到了派发的作用, 给strategy对象赋予了属于它的定位方法.--->再往下接着跟self._find_*方法心好累, 下次吧......

接下来就可以看find(self, browser, locator, tag=None)源码了, 理解了strategy就没什么特别的地方了:

     def find(self, browser, locator, tag=None):
assert browser is not None
assert locator is not None and len(locator) > 0
(prefix, criteria) = self._parse_locator(locator) # 从locator对象中提取prefix和criteria
prefix = 'default' if prefix is None else prefix
strategy = self._strategies.get(prefix) # self._strategies对象的属性石robot.util中的NormalizeDict(标准化字典)
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) # 返回strategy的find结果

VI. Module: resources

Selenium只提供了和firefox相关的资源文件, 受到 Selenium webdriver 学习总结-元素定位 文章的启发, 个人觉着应该是firefox提供了丰富全面的组件, 能够集成selenium的缘故吧. 不是做Web开发不了解, 但firefox的功能强大丰富是一直有所耳闻. 所以我们不妨推测, resources的内容就是对firefox的组件和方法的描述?

最新文章

  1. MongoDB Replica Set 选举过程
  2. Oralce中SQL删除重复数据只保留一条(转)
  3. 视觉中的深度学习方法CVPR 2012 Tutorial Deep Learning Methods for Vision
  4. jquery mobile backbone
  5. Hdu3812-Sea Sky(深搜+剪枝)
  6. HLS 协议
  7. NDepend 3.0已与Visual Studio集成
  8. As3.0 Interface 与类的使用
  9. js中uuid不被识别
  10. 背水一战 Windows 10 (116) - 后台任务: 前台程序激活后台任务
  11. Lodop连续打印内容逐渐偏移怎么办
  12. js call() 笔记
  13. BZOJ3561 DZY Loves Math VI 数论 快速幂 莫比乌斯反演
  14. redis学习 (key)键,Python操作redis 键 (二)
  15. post/get in console of JSarray/js 数组详细操作方法及解析合集
  16. 1. K近邻算法(KNN)
  17. Css3不透明
  18. shell习题第6题:监听80端口
  19. Spring多个数据源问题:DataSourceAutoConfiguration required a single bean, but * were found
  20. 删除数据库时提示数据库正在被使用,无法删除(Cannot drop database databasename because it is currently in use)的问题

热门文章

  1. QuickWebApi2:使用Lambda方式,完成对WebApi的开发和调用-文档的生成
  2. windows phone 8环境搭建
  3. JavaScript 跨域方法总结
  4. 关于在Java EE 下开发web,出现项目中的外部包没有tomcat的包的原因
  5. Javascript 封装问题
  6. Arduino 各种模块篇 motor shield
  7. set 类型
  8. struts2中token的令牌机制
  9. [置顶] JAVA从零单排4-----继承、封装和多态详解
  10. 2013Esri全球用户大会之互操作和标准