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的组件和方法的描述?

Python_Selenium2Library源码分析的更多相关文章

  1. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  4. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  8. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

  9. ABP源码分析三:ABP Module

    Abp是一种基于模块化设计的思想构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modu ...

随机推荐

  1. SVN版本冲突解决详解

    SVN版本冲突解决详解 分类: SVN(SubVersion)2009-11-23 15:45 27014人阅读 评论(12) 收藏 举报 svnsubversion服务器文档工作c 版本冲突原因: ...

  2. Lak3 Counting(POJ No.2386)

    问题描述: 有个大小为N*M的园子,雨后积起了水.八连通的积水被认为是连接在一起的.求出园子里总共有多少水洼. N, M <= 100 输入例: : 问题分析: 八连通即:上.左上.左,左下,下 ...

  3. MongoDB学习之--增删改查(2)

    昨天简单介绍了官方驱动操作MongoDB进行增删查操作的,今天继续介绍更新操作..... 方法简介 官方驱动中更新操作使用collection的Update方法,有泛型和非泛型两个版本: 其签名如下( ...

  4. C#中易混淆的知识点

    C#中易混淆的知识点 一.引言 今天在论坛中看到一位朋友提出这样的一个问题,问题大致(问题的链接为:http://social.msdn.microsoft.com/Forums/zh-CN/52e6 ...

  5. Java学习笔记——Java程序运行超时后退出或进行其他操作的实现

    当程序进入死循环或者由于其他原因无法自行终止的时候,就需要强制退出程序了. 对于开发软件 Eclipse ,在程序执行超时后,可以点击 Terminate 按钮强制退出. 那么,我们可不可以通过程序设 ...

  6. MS SQL SERVER 2008 使用OBJECT_ID判断临时表是否存在

    MS SQL SERVER 2008 使用OBJECT_ID判断临时表是否存在 我们在写sql 脚本的时候经常会用到临时表,有时间也需要根据临时表是否存在做一些逻辑处理.还好sql server已经集 ...

  7. Remobjects使用经验

    RemObjects RemObjects提示:我们相信本文是正确的,但我们不做任何保证.在此感谢Henrick 写的文章,很高兴在此发表. 介绍RemObjects是功能强大可扩展的远程框架;但是当 ...

  8. android openGl视频

    链接如下:http://download.csdn.net/detail/jltxgcy/5667345

  9. 从零开始学C++之构造函数与析构函数(三):深拷贝与浅拷贝、空类

    一.深拷贝与浅拷贝 说得简单点,假设一个类有指针成员,如果在拷贝的时候顺带连指针指向的内存也分配了,就称为深拷贝:如果只是分配指针本身的内存,那就是浅拷贝.浅拷贝造成的问题是有两个指针指向同块内存,d ...

  10. bzoj1588: [HNOI2002]营业额统计 splay瞎写

    最近各种瞎写数论题,感觉需要回顾一下数据结构 写一发splay冷静一下(手速过慢,以后要多练练) 用splay是最直接的方法,但我感觉离散一波应该可以做出来(没仔细想过) 现在没有很追求代码优美,感觉 ...