02
09

核心实验:Selenium WebDriver->对象识别机制(二)

本节实验就主要来探讨关于findElement()系列API的详细用法,帮助大家更清楚的理解具体的实现机制,以便于在真实项目中更加有针对性的对该系列API进行灵活的运用,达到精准定位Web页面元素的目的。

 实 验 简 介



在前面的实验中,我们一直在使用findElement()方法结合By对象完成Web页面元素的识别,这也是WebDriver最为核心的API之一。


本节实验就主要来探讨关于findElement()系列API的详细用法,帮助大家更清楚的理解具体的实现机制,以便于在真实项目中更加有针对性的对该系列API进行灵活的运用,达到精准定位Web页面元素的目的。


 

 实 验 目 的 

(1)理解Web页面的DOM操作接口规范及元素定位机制。

(2)理解findElement()和findElements()的区别及用法。

(3)理解By对象的linkText()和partialLinkText()方法的实现原理。

(4)理解By对象的cssSelector()方法的实现机制。

(5)理解By对象的xpath()方法的实现机制及XPath的定位原理。

(6)能够充分应用上述对象识别机制结合具体实例进行灵活的对象处理。




 实 验 流 程 

6.class定位方式


class属性出现在HTML中,是用于CSS定位或者JavaScript操作。那么,很可能会出现多个标签class属性的值相同,如果使用driver.find_element_byx_class()的方式来查找元素就直接抛出异常。所以,在不能保证当前查找的元素的class属性唯一的情况下,可以使用elelist = driver.find_elements_byx_class()的方式,将所有找到的页面元素放入一个列表中。要使用列表中某个元素,可以用elelist[index]即索引的方式,也可以通过for循环遍历的方式。视使用场景而定。

from selenium import webdriver

import time


driver = webdriver.Firefox(firefox_binary=r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe")

driver..get(‘http://www.baidu.com’)

 

time.sleep(2)          #让程序等来2秒

#在输入框内填写‘蜗牛学院’,保证HTML中class属性唯一

driver..find_element_by_class_name(‘s_ipt’).send_keys(‘蜗牛学院’)

 

如果不能保证class属性的唯一性,那么可以通过查找所有相同class属性的方式来获取,如何查看是否唯一或如何确定是列表中第几个元素。方式很多,这里提供一个简单的方法。直接使用浏览器自带的F12开发人员工具,识别网页中对应的元素即可。

 

当class属性有多个元素相同时,代码可以这样编写:

 

from selenium import webdriver

import time

 

driver = webdriver.Firefox(firefox_binary=r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe")

driver..get(‘http://www.baidu.com’)

 

time.sleep(2)          #让程序等来2秒

#在输入框内填写‘蜗牛学院’,保证HTML中class属性唯一

element_list = driver..find_elements_by_class_name(‘s_ipt’)

element_list[0].send_keys(‘蜗牛学院’)

#或者使用遍历的方式:

‘’’

for i in element_list:

if i. get_attribute(‘autocomplete’) == ‘off’ :

    i.send_keys(‘蜗牛学院’)

    break

‘’’

 

7.linkText和partialLinkText定位方式


在Web页面中,超链接可以说是最为重要的一个元素类型,这也是为什么我们的超链接的标签直接用一个字母“a”来表示。所以,对于WebDriver来说,专门针对超链接对象封装两个专门的识别方法linkText和partialLinkText,是值得的。两个方法的用法相对来说很容易理解:


(1) By.linkText():直接提供一个完全匹配的超链接文本,即可直接定位到该超链接。

(2) By.partialLinkText():利用模糊匹配的方式,只要提供一部分超链接包含的文本,即可定位到该超链接。


比如我们要对上述静态页面中的两个超链接进行查找,可以使用以下几种方式:


# 直接通过完全匹配的方式查找到超链接文本内容为“蜗牛学院”的那个元素driver.find_element_by_link_text("蜗牛学院")).click();

# 直接利用模糊匹配的方式查找到超链接文本内容包含“蜗牛创想”的那个元素

driver.find_element_by_partial_link_text("蜗牛创想"));

# 利用findElements查找到两个包含“蜗牛”的超链接元素

links = driver.find_elements_by_partial_link_text("蜗牛"));

 

上述的用法没什么特别之处,理解起来也是非常容易的。但是,在此笔者想跟大家探讨一下其原理,这一原理对于后续我们无法通过WebDriver提供的现成API来识别对象是非常有价值的。让我们先回到本实验最开始的地方,笔者为大家讲解了如何利用JavaScript通过标签选择器遍历和文本对比对比的方式来定位一个超链接元素。其实在WebDriver中的实现原理是一致的。

 

8.利用CSS选择器实现定位


CSS选择器在Web前端开发中应用面非常广,比如我们可以先来看看倒计时程序中,是如何利用CSS选择器来完成对页面元素进行样式设置的。现截取部分代码片段供大家参考:


<style>

/* 标签选择器,直接作用于某个标签上 */
    body {
        background-image: url('timer/background.jpg');
        margin-top: 20px;
        color: #f0f0f0;
        font-family: 微软雅黑;
    }

/* 类选择器,针对class=top的元素生效,前面加.表示类选择器 */
    .top {
        border: solid 1px #ff3300;
        border-radius: 10px;
        width: 900px;
        height: 150px;
        margin: 0 auto;
        padding-top: 20px;
    }

/* 层次组合选择器,按照父子层次进行元素定位,中间有空格分隔开 */
    .top .logo {
        float: left;
        width: 300px;
        text-align: center;
    }

/* 组合选择器:包含类选择器,标签选择器 */
    .top .logo img {

        width: 130px;

}

/* ID选择器,前面加#号 */
    #total {
        width: 150px;
        height: 45px;
        border: solid 1px #f0f0f0;
        border-radius: 8px;
        font-size: 32px;
        text-align: center;
    }

</style>

 

事实上,大家注意看上述CSS样式中有一个比较特别的组合选择器“.top .logo img”,对照我们的源代码会看到,这个选择器可以直接定位到显示Logo的那个图片元素上,而且是唯一的。所以,在WebDriver中,我们也提供了类似这样的定位方式。总结如下:


(1)利用标签+类名的方式:比如在对Agileone的需求提案进行内容的输入时,我们需要先定位到元素“代码”这个小按钮上切换编辑器模式,便使用了CSS选择器:“img.ke-common-icon.ke -icon-source”,意味着去寻找标签名称为“img”,并且由两个类“.ke-common-icon和.ke -icon-source”确认的那个元素。当然,这个识别属性是由Selenium IDE帮助我们完成的。事实上,类似这种情况,我们也可以使用By.className来完成定位。


(2)利用组合层次选择器:比如在倒计时程序中,我们可以使用CSS选择器,对减号这个按钮图像元素进行定位,其定位方式为:“div.main>div.count-box>div.icon>img”,这也是CSS选择器的应用。当然,像这种层次太深的,我们也可以利用CSS样式选择器 “div.main  img”这种方式直接忽略中间的层次。


(3)利用组合加属性的方式定位:这也是标准的CSS定位元素的方法,即组合利用各种CSS本身支持的定位方式。比如我们要定位倒计时中“total”这个文本框,除了使用ID以外,也可以CSS选择器“div.count>input[type=text]”来进行定位。


(4)根据序号进行定位:比如倒计时程序中有多个元素拥有“class=icon”的属性。那么我们可以使用“div.icon>img:first-child”得到第一个元素,或使用“div.icon>img:last-child”得到最后一个元素,也可以使用“div.icon>img:nth-child(3)”得到第3个元素。


关于CSS选择器,其用法与HTML中CSS定位元素的方法一样,如果有前端开发基础的读者,理解起来会相对容易。由于篇幅所限,本书只为大家总结相对比较常见的一些用法。更多细致的用法读者可以参考“http://blog.csdn.net/galen2016/article/details/71106900”,感谢作者的总结。

 

9.关于XPath的定位机制


XPath即:XML路径语言,它是一种用来确定XML文档中某部分位置的语言。XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力。由于HTML文档同样具备XML(其实两者都属于通用标记语言的子集)的规范和树型节点(即前文所述的元素之间的层次关系),所以我们针对HTML文档元素的定位,也可以使用XPath,并且WebDriver和DOM均提供了对XPath的支持。XPath的定位主要提供以下四种定位方式:


(1)从根节点开始的元素层次定位:所有的节点以“/”标签开头,比如“/html/body/div/div”表示定位到第二层次的所有DIV元素。


(2)从任意子节点开始层次定位:任意节点开始进行定位,需要使用“//”开头,比如“//input”表示定位到所有的input标签。


(3)根据元素所在序号进行定位:利用“[n]”来指定其序号,如“//img[1]”表示找到第1个图像元素。


(4)根据元素的属性进行精准定位:其语法规则类似为“//input[@type='text']”。


比如针对倒计时程序,我们要定位“total”这个文本框,可以利用以下XPath表达式进行:

/html/body/div/div/div/input

/html/*/*/*/*/input

//input

//input[@type='text']

//input[1]

 

10.利用表格内容进行断言


针对公告管理的新增操作,除了使用数据库和操作消息进行断言以外,我们还可以利用表格的内容进行断言。当新增一条公告成功后,在下面的表格中会直接在第一行添加该条记录,只要我们能够获取到这条记录的内容,则可以用于进行断言。

 

通过F12开发人员的工具,我们可以定位到我们需要查找到表格中显示的最新一条记录的情况,如图所示。


20200825_101355_341.png


从图中我们可以看出,新增的公告标题位于表格的第二行,第二列,整个表格的ID属性为“dataTable”,同时也可以通过<tbody>标签来定位到该单元格位于其第一行第二列(<thead>标签不计算在内的情况),所以我们可以通过XPath表达式“//table[@id='dataTable']/tbody/tr[1]/td[2]”来定位到该单元格。所以,最终可以利用如下代码来实现新增公告的另外一种断言方式:


headline = “天啊!我的衣服又瘦了 - 903282”

xpath = "//table[@id='dataTable']/tbody/tr[1]/td[2]"

td_headline = driver.find_element_by_xpath(xpathExp).text

if td_headline == headline:

print("新增公告测试[TD-Headline]:成功.")

else:

print("新增公告测试[TD-Headline]:失败.")


当然,我们通过F12元素查看器也可以看到,标签<tbody>也有一个ID属性叫“dataPanel”,所以我们也可以直接使用XPath表达式“//tbody[@id='dataPanel']/tr[1]/td[2]”进行定位。


11.实现需求提案模块的随机编辑


要实现需求提案的随机编辑,其核心要解决的问题就是能够随机的点击一条列表中的“编辑”按钮,并提交要编辑的新内容,然后点击操作面板的“编辑”按钮即可。如图所示。


20200825_101420_049.png

图 5- 24 针对公告的编辑操作


那么应该如何能够点击到一个列表中的随机的“编辑”按钮呢?我们可以通过XPath表达式的序号的方式来找到一个随机的值。但是现在比较关键的问题是,我们并不知道一共有多少条公告,所以我们还需要获取到一个公告的条数。有两种方式可以获取到公告的全部数量:

(1)在列表的底部,有一个总数,我们可以读取该标签的值,并转化为整数即可。

(2)直接从数据库中查询proposal这张表的总数。

 

当获取到总数以后,我们可以再做一个判断,如果总数小于30条(30是每页的数量),则生成一个1到该总数的随机数,如果总数大于30条,则会分页,则直接生成一个1到30之间的随机数。根据该随机数再利用XPath表达式来进行定位操作,找到一个随机的“编辑”按钮,点击后,便可对该条记录进行编辑和提交。具体的实现代码如下:

def edit_proposal(self):

    sequence = random.randint(10000, 99999)

    self.driver.find_element_by_partial_link_text("需求提案").click()

    time.sleep(3)

 

    # 随机找到一个“编辑”按钮,进行标题和内容的修改

    # random_index = random.randint(1,30)

# self.driver.find_element_by_xpath(

# "(//label[@onclick='goEdit(this,true)'])[%d]" % random_index).click()

    # time.sleep(3)

 

    # 从数据库中查找前30条记录的ID,并用于定位随机的“编辑”按钮

    sql = "SELECT proposalid FROM proposal ORDER BY proposalid DESC LIMIT 0,30"

    conn = pymysql.connect(user='root', passwd='',

                           host='localhost', db='agileone', charset='utf8')

    cursor = conn.cursor()

    cursor.execute(sql)

    result_proposal_ids = cursor.fetchall()

    random_index = random.randint(0, len(result_proposal_ids)-1)

    random_id = result_proposal_ids[random_index][0]

self.driver.find_element_by_xpath

("//tr[@id='dtrow_%d']/td[5]/label" % random_id).click()

    time.sleep(3)

 

    print(self.driver.find_element_by_id("headline").get_attribute("value"))

    self.driver.find_element_by_id("headline").clear()

self.driver.find_element_by_id("headline").send_keys(

"修改过的需求提案标题-%d" % sequence)

self.driver.find_element_by_class_name("ke-iframe").

send_keys("修改过的需求提案内容-%d" % sequence)

    self.driver.find_element_by_id("edit").click()

    time.sleep(3)

 

    result = self.driver.find_element_by_id("msg").text

    if "成功啦: 更新数据成功" in result:

        print("修改需求提案:成功.")

    else:

        print("修改需求提案:失败.")


事实上,在进行元素定位时,各种可能的技术都会被用到。在真正应用过程中,我们一定不能过分拘泥于某一种特定的方式,而是熟练运用各种技术,以目的为导向,并且通过分析找到实现的核心点和关键环节,将关键问题先行解决。

 

12.关于定位的策略选择


通过上述对元素定位的各种手段的讲解,相信大家现在已经对元素的各类定位方式有了比较系统的理解。同时我们也可以看到,针对同样的一个元素,我们有很多种手段可以找到这个元素,那么究竟哪种方式更好呢,笔者按照优先级顺序为大家列出一个基本策略:


(1)优先使用findElement()定位唯一的元素,再考虑使用findElements()定位多个。

(2)优先使用ID属性定位唯一的元素。再考虑使用Name属性(如果存在的话)。

(3)当ID或Name属性均不存在的情况下,优先考虑使用CSS选择器,再使用Class或标签定位。

(4)最后再考虑使用XPath进行定位。

(5)如果以上方法均无法定位,则可以考虑使用键盘操作或坐标定位的方式。但是这并不是最推荐的做法,最优选择仍然是让程序员在下一个版本中加上ID属性。

(6)如果以上方法均无法定位,则可以考虑使用图像来进行定位。WebDriver本身并不支持图像定位,所以我们可以通过Python调用SikuliX提供的接口进行。



 思 考 练 习 



(1)请熟练将上述定位方式应用于Agileone的操作中,实现不同的定位方式进行相同的测试操作。


(2)请结合自己的项目实现最优的定位方式,并确认是否存在无法定位的情况,如果存在,那么你打算如何解决。


下周分享:核心实验:Selenium WebDriver->窗口切换




为了答谢大家对蜗牛学院的支持,蜗牛学院将会定期对大家免费发放干货,敬请关注蜗牛学院的官方微信。

20190320_095757_834.jpg



版权所有,转载本站文章请注明出处:蜗牛学苑, https://www.woniuxy.cn/article/496