天天看点

Selenium2Library源码解析与扩展(一)_browsermanagement.py_element.py _formelement.py _selectelement.py

一直觉得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>
           

效果如下图:

Selenium2Library源码解析与扩展(一)_browsermanagement.py_element.py _formelement.py _selectelement.py

这种情况下所有’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

继续阅读