selenium
1:selenium特点:注意事项
python+接口,python进阶多数是逻辑问题
selenium百分之80遇到的都是操作跟前端界面不吻合(这时候大部分报错问题检索的关键实时和前端网页联合查问题,一般代码没什么问题)
2:自动化到底是什么(自动化进行分类,两部分)
一:功能自动化(现在主要是功能自动化)
1:ui自动化:就是对前端的界面进行点点点(ui自动化)关注的是软件功能实现的正确性
2:相对应手工的ui测试,自动化去进行ui测试有几个难点
一:非预期产生的缺陷是很难被自动化发现的(ui自动化基本上很难发现bug)ui自动化重心不是在发现bug上,而是在主功能压测和后期冒烟回归
而是对核心功能的正确使用上(验证核心功能和流程的正确性)
手工测试能轻松发现这个bug,(界面展示不正常等问题轻松发现),ui自动化的时候很难发现
(ui自动化逻辑假如是进入vip系统,点击课程,点击请假--这中间任何一个环节都必须主动检查
没有主动检查的部分,比如倒计时按钮消失,整个ui自动化过程中没有对倒计时按钮进行任何检查操作,这种bug ui自动化很难发现,
但是手工测试很轻松发现这种问题,但是手工测试很轻松发现这种问题,但ui自动化测试请假测试用例流程时候是发现不了这种问题的))
ui自动化很难发现非预期的bug,但是手工测试很容易就能发现
二:第二难点,ui本身的变化性,界面可能发送变化,每一期需求的更改,版本的迭代,前端界面图片的排版格式都可能发送变化,前端的代码逻辑也很容易发生变化
html标签层级关系很容易多一层少一层,也容易变化,这些变化性是非常容易发送变化的,
所有想在ui自动化达到和手工测试的相同覆盖率基本很难手工测试能测到百分之80-90,但
但是ui自动化测试取覆盖百分之80-90成本无限高,收益小,想要ui自动化覆盖百分之80-90,异常场景全部都覆盖验证一遍
成本可能10w-20w,收益100-200(这就是差距)相对于手工测试来说,
ui自动化测试很难去证明自己的投资回报,一般覆盖率不会做到百分之80-90左右或者以上
三:ui元素控件本身一个识别的复杂性 ,元素控件(就是对这些标签而已)按钮就是一个控件,下拉框也是一个控件,
输入框也是一个控件,他们的变化性也是非常大的识别也是很复杂的
四:ui自动化测试,出现问题的时候,恢复到下一条测试场景,恢复到原来的场景很复杂的,因为出现这种问题是非预期的,
比如说:作业布置了,把这个作业布置之后,如果我们布置作业这条测试用例中间任何一个步骤发生异常,
那么这一整条的数据变化想要把它恢复到原来的样子是很难的
ui自动化测试用例与测试用例之间一定不能够相互干扰,一旦相互相互干扰,相互有依赖逻辑关系有关联会变得无穷复杂
五:ui自动化的case(测试用例)--大部分是关于用户交互方面的,(人和系统进行交互,点击,删除,输入,确定,提交等等,用户和系统交互)
用户的交互很容易根据自己的感觉出现问题(比如说:点击下拉框,下拉框点击后展示异常
(点之后在其他位置出现,但是ui自动化测试不出来这种问题,人手工点一下就能看见这个地方有问题))
3:ui自动化的价值:核心场景做反复的测试和冒烟测试
核心场景,百分之10-30非常重要最最核心的场景一定要做ui自动化的,比如淘宝:用户-产品到购物车---购物车结算--提交订单---确认收货--退款评价
(淘宝程序最核心的功能这一部分需要做成ui自动化的)但是相对与其他功能(用户在产品详情页上点开了某个评价,
然后给某个评价进行点赞评论--这种是非核心的部分,那么这一部分不适合做ui自动化
4:什么时候进行ui自动化测试
ui自动化时候在基本用户场景测试和验收确认测试的时候(基本用户场景:每家公司都有自己的核心业务流程)
(验收确认测试:每一次提交都必须必要做的冒烟测试这个也可以做ui)
现实场景:很多公司考虑ui自动化成本很高,多数情况下会用手工测试去替代,但是ui自动化还是必须存在的
成本--难题--见仁见智的问题
5:接口自动化(对接口层面,对后端的逻辑,对应用程序背后的逻辑进行测试)
二:性能自动化
3:html:前端界面网页就是由html组成的,网页就是一个又一个html标签组成的,ui自动化就是找标签然后操作这个标签
4:chrome浏览器查找标签
1:Chrome浏览器--鼠标右键---选择检查(开发者工具)--查看网页对应的一个又一个的标签---(找到元素对应标签的办法)
2:Chrome浏览器右上角三个点---更多工具---开发者工具点击打开--左上角有一个鼠标一样的按钮,点一下,然后鼠标移动到左侧页面上,
移动到具体某个元素的时候右侧html标签也会变化--鼠标左键单机点击一下右边html代码就固定住了,就找到了这个标签(div或者什么的标签)
5:html标签的特点:
1:标签一般是一对,标签对,结尾标签比开头标签多了个/ 斜杠--前面是起始标签,后面叫结束标签(结束标签比开始标签多了个斜杠)----标签对
一般来说html元素以起始标签开始,以结束标签终止-------元素内容就是起始标签和结束标签之间的内容
2:并不是所有的标签都有起始和结束的,也有一些标签是单身狗
比如:input,br换行标签,这些标签都是单标签(因为这些标签中间不需要显示内容,) ---单标签
3:标签里面的内容:如class,name,style,type---这些叫做属性,大多数html元素都是可以拥有属性的,属性可以在元素当中添加一些符加的信息,
比如input标签--id为身份证号码,class是什么类的,name名字是什么,style格式样式是什么,type(type="text")类型是是什么---文本类型
可以在同事电脑上--查看登录页面密码信息(文本类型从password改成text)
属性一般放在起始标签,不放在结束标签,
input输入标签,p段落标签,a文本标签,div区块标签
4:元素:HTML 元素指的是从开始标签(start tag)到结束标签(end tag)的所有代码。
5:最外层的标签是html,所有的东西都包括在最外层的一对html当中,
第一行是声明:<!DOCTYPE html> 文档类型是html,html分为两部分一部分为body,一部分为head
头里面一般存的是一些信息,body是存放眼睛看到的内容
<title>是标题,网页最上面的内容,
<p 段落标签,段落标签结尾自带换行,
6:<span 标签--也是文本标签,span标签也是不带换行的,a标签自己也不带换行,p标签自己带换行的
7:<table>标签是表格<tr>代表一行<td>代表一格
8:form标签是表单
6: 怎么使用selenium
Selenium是一个浏览器自动化操作框架。selenium主要由三种工具组成。
1.第一个工具——SeleniumIDE,是Firefox的扩展插件,支持用户录制和回访测试。录制/回访模式存在局限性,对许多用户来说并不适合
2:第二个工具——Selenium WebDriver提供了各种语言环境的API来支持更多控制权和编写符合标准软件开发实践的应用程序。
3.最后一个工具——SeleniumGrid帮助工程师使用Selenium API控制分布在一系列机器上的浏览器实例,支持并发运行更多测试
WebDriver是一个干净、快速的web应用自动测试框架
7:访问百度并进行搜索操作
from selenium import webdriver #Selenium是一个浏览器自动化操作框架。 #1:创建浏览器对象,操作网页需要浏览器对象,驱动对象,浏览器驱动对象 driver=webdriver.Chrome() #webdriver创建浏览器对象,一般使用Chrome,Chrome最稳定也符合标准(这时候浏览器就会打开) driver.get("https://www.baidu.com/") #2:访问百度网址 #3:找到搜索输入框,id属性进行定位---id="kw" inpELE=driver.find_element_by_id("kw") #通过driverfind_element寻找元素 ,by_id通过什么方法--找到搜索输入框(元素对象)赋值给一个变量 #4:操作这个变量,输入内容,文本框输入内容 .send_keys() 输入内容:特朗普 inpELE.send_keys("特朗普") driver.find_element_by_id("su").click() #5:找到百度一下按钮进行点击,这个元素也有id属性id="su"--点击是click # driver.quit() #6:退出浏览器
8: 元素定位操作:自动化元素定位操作
1:找到元素
2:操作元素
整个自动化就在这两个步骤中循环往复,先找到元素,再操作元素,找元素是重点,有哪些找元素方法
9:元素定位方法:8种元素定位方式都可以根据特征选择,还可以根据关系进行选择
根据元素特征定位:ID,Name,class,tag_name(标签名字),link_text,partial_link_text等这些是根据特征选择
根据元素的特征和关系定位:css,xpath,这两个可以根据选择也可以根据元素关系定位,
9成的代码都是使用css和xpath的,pc端使用css,移动端使用xpath
driver.find_element :不加s匹配元素对象
driver.find_elements :加s匹配元素列表,所有能匹配到的元素对象存入列表,如果一个都匹配不到,列表为空(没找到,不报错)
selenium没有提供判断元素是否存在的功能,所以当我们需要去判断元素存不存在的时候直接定位就可能报错,
找不到就报错了,那么这时候匹配元素列表,如果这时候列表为空,则元素不存在,如果列表不为空,则元素存在(灵活用法)
一:通过id属性进行定位(要求元素必须具备id属性)
driver.find_element_by_id() 单数形式,只返回匹配到的第一个元素,如果找不到就报错
一般id属性是唯一的,如果不唯一,前端垃圾(name,class等属性就不一定唯一了)
driver.find_elements_by_id() 匹配多个,复数形式,返回一个列表,找不到返回空列表
二:通过name属性进行定位(name一般也是唯一的,要求元素必须具备name属性,class重复是最多的)
driver.find_element_by_name("abd") 单数形式
driver.find_elements_by_name("abd") 复数形式
三:根据xpath定位,只返回匹配到的第一个元素,如果找不到报错
driver.find_element_by_xpath("/html/body/input") 单数形式
driver.find_elements_by_xpath("/html/body/input") 复数形式
四:根据css定位,只返回匹配到的第一个元素,如果找不到报错 css:css select
driver.find_element_by_css_selector("body > p:nth-child(4)") 单数形式
driver.find_elements_by_css_selector("body > p:nth-child(4)") 复数形式
五:根据链接文本link_text定位:只返回匹配到的第一个元素,如果找不到报错(链接文本就是标签对中间的内容)
<a href="http://www.baidu.com">这是链接文本1</a> 标签对中间的:"这是链接文本1"就是链接文本,会展示在前端界面
driver.find_element_by_link_text("这是链接文本1") 单数形式
driver.find_elements_by_link_text("这是链接文本1") 复数形式
六:根据链接文本模糊搜索:partial_link_text,只返回匹配到的第一个元素,如果找不到报错
driver.find_element_by_partial_link_text("文本1")
driver.find_elements_by_partial_link_text("文本1")
七:根据标签名称tag_name定位,只返回匹配到的第一个元素,如果找不到报错
driver.find_element_by_tag_name("p") 找p标签里面的元素
driver.find_elements_by_tag_name("p")
八:根据class属性定位,只返回匹配到的第一个元素,如果找不到报错(class属性是可以重复的,而且也是重复最多的)
driver.find_element_by_class_name("ab1") 单数形式
driver.find_elements_by_class_name("ab1") 复数形式
ps:class属性定位有一个问题:注意
class="ab1 ab2" 复合类,class属性定位的时候会遇到复合类
复合类(就是class属性中间有空格,class属性复合类,class就是类)
class属性比较特殊,中间的空格是间隔符号,表示这个元素有多个class属性名称
定位元素的时候取class属性其中一个就行,取ab1和ab2都行,单不保证它唯一定位(因为html页面的class经常重复)
定位的时候class属性不能两个都用:
driver.find_element_by_class_name("ab1 ab2") 这样写不行,寻找不到报错
但是可以 "."号连接两个class属性一起使用
driver.find_element_by_class_name("ab1.ab2") 这样写可以找到
或者单独写一个class属性也行
driver.find_element_by_class_name("ab1") 这样写可以找到
driver.find_element_by_class_name("ab2") 这样写可以找到
10:操作界面元素(找到界面元素之后要去操作界面元素)
输入操作:点击,输入文字,清除,前进后托,拖拽等
这个输入不是输入文字而是对页面有输入,点击是把操作给到了页面,写文字把东西给到页面,往里面增加修改等操作
输出操作:获取元素各种属性,各种值,把元素属性值拿出来这是输出
ele.send_keys() 向输入框输入文字,操作input标签输入内容
ele.get_attribute("name") ele这个元素获取它的name属性的值
ele.text 获取ele元素中间的文本信息
driver.title 用于获取当前页面的标题
driver.current_url 获取当前页面的url实时获取的
ele.get_attribute("value") ele这个元素获取它的name属性的值,这种方法需要先获取到ele这个元素对象才可以
11:分步骤定位:
比如说在进行定位时候,元素层级比较复杂,层级很深,有时候一步定位会有点麻烦,
很精细一个个对比,路径太长可能出错,思考步骤多,复杂---分步骤定位:
分步骤定位:
先定位大元素,再定位小元素 li < ul标签
1:先定位外层大元素table,赋值给元素对象
txtEle=driver.find_element_by_tag_name("table")
2:再根据外层元素匹配里边的子元素
txtEle.find_element_by_tag_name("td") #这里匹配到的是第一个td标签
12:对元素定位的另一种方式 (后面使用po模式封装代码需要用到这种定位方法)
一:方法一需要导入By库如下:from selenium.webdriver.common.by import By
from selenium import webdriver from selenium.webdriver.common.by import By driver=webdriver.Chrome() driver.get("file:///C:/Users/ywt/PycharmProjects/selenium_ywt/day1/test.html") driver.find_element(By.ID,"abc") driver.find_element(By.NAME) driver.find_element(By.CLASS_NAME) driver.find_element(By.LINK_TEXT)
二:第二种方法更简单:直接传id和属性就行
driver.find_element("id","abc") #这样也能找到,更简单 driver.find_element("css selector","abc")
13:已经找到一个元素了,根据找到的这个元素获取元素其他的属性值
ele.get_attribute("name") #ele这个元素获取它的name属性的值
14:selenium 环境的安装
pip uninstall selenium 卸载
pip install selenium 安装
15:Chrome如果不是默认浏览器webdriver.Chrome()的时候需要指定Chrome的路径
也可以把webdriver浏览器驱动配置到环境变量
代码里指定浏览器取得的路径如下:
webdriver.Chrome(executable_path=”chromedriver路径“, chrome_options=”谷歌浏览器路径“)
16:获取断言信息.
自动化测试是获取页面信息和预期结果来进行断言,页面上有哪些断言信息是值得获取的如下四种
1:页面上元素的文本值(这是最常用的)(获取的是标签对中间的文本信息)
ele.text 获取ele元素中间的文本信息
因为是获取标签对中间的文本信息,有几个注意事项:
1:标签元素如果不展示在页面上那么获取的结果为空,
我现在这个界面就展示这么多,获取这个页面下面没有展示在页面上的元素的文本,那么获取到的就是空
比如说更多这种需要鼠标悬停才展示的元素,那么打印这些元素的文本值也是空,
除非鼠标悬浮在这里,让这些元素展示出来再去打印,这样获取到的内容才不是空,
也就是说,如果元素不显示在页面上拿到的文本值都是空的
2:如果标签对中间没有值,如input标签没有标签对,获取标签对中间没有值,那么拿到的结果也是空的
2:title(网页的标题),获取title,获取当前页面的标题
driver.title #用于获取当前页面的标题
3:网页的url,
driver.current_url #获取当前页面的url实时获取的
4:还有个最不常用的获取元素的属性---很少用
ele.get_attribute("value") ele这个元素获取它的name属性的值,这种方法需要先获取到ele这个元素对象才可以
17:设置元素等待
有些时候元素未加载而代码已经执行到,这时候就会报错,(查找不到元素,元素不存在)
不是真的找不到,而是代码执行速度比较快,页面还没有加载完成
因为现在我们大多数web应用程序都是使用ajx和javascript开发的,会有个加载的过程
当浏览器加载页面,我们想要与交互的元素确没有被加载出来,这时候容易识别不到元素
识别不到元素就会报错(查找不到元素)此刻需要等待一下
webdriver给我们提供了两种等待方法,显示等待,隐式等待
显示等待只对声明了的元素有效,显示等待必须主动声明才有效,不声明没有效果,大部分代码都使用显示等待
隐式等待只要写一次,后面的所有代码的元素定位都有效果,隐式等待代码很少,全局设置,对所有等待都生效,看起来简单
什么时候使用显示等待什么时候使用隐式等待,取舍逻辑:
微博举例:元素被加载有界面刷选,上一步操作有界面刷选的,下一个步骤的元素定位通常是要等待的
因为有可能元素未加载而代码执行到导致定位失败,所有相对这种元素定位之前步骤有元素界面刷新的很需要等待
但是这个步骤过了之后,页面已经稳定下来了,再去操作页面的元素,就有很多个元素,那么每一个元素去操作它其实都没必要等,
因为页面已经稳定了,此刻不需要等,此时使用隐式等待代码执行的时间会被拉长--20s执行完的需要25s或者30s,效率降低
显示等待就是有条件的等待:指定一个等待条件,和一个最长等待时间,程序会判断在等待时间内条件是否满足,
如果满足则返回,如果不满足会继续等待,超过时间就会抛出异常
隐式等待就是无条件的等待:当使用了隐式等待执行测试的时候,如果 WebDriver 没有在 DOM 中找到元素,将继续等待,
超出设定时间后则抛出找不到元素的异常,换句话说,当查找元素或元素并没有立即出现的时候,
隐式等待将等待一段时间再查找 DOM,默认的时间是 0
一:显示等待(ui自动化里面应用最多的)
显示等待和隐式等待功能是一样的,也是等待某个元素,如果元素存在继续往下走,
如果元素不存在就以轮询方式不停的判断元素是否存在直到设定的最大超时时候达到了,还找不到就抛出异常
区别:
显示等待只对声明了显示等待的元素生效,也就是说某个元素如果没有声明显示等待,那么这个元素是不会走显示等待的逻辑
而隐式等待只要声明一次之后的所有元素定位都是生效的,
显示等待需要导入三个库
from selenium import webdriver from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By driver=webdriver.Chrome() driver.implicitly_wait(10) driver.get("https://www.baidu.com/") #显示等待文本框元素--WebDriverWait是一个类,WebDriver浏览器,Wait等一下, #WebDriverWait参数:1:driver对象 2:最大超时时间 3:轮询时间可以不传,默认0.5s # WebDriverWait(driver,10,1) #浏览器等待,设置一个条件,什么时候等,什么时候不等 #条件设置: .until直到什么时候--直到元素被找到 #visibility_of_all_elements_located找到所有页面元素是不是存在(所有页面元素加载完毕) #visibility_of_element_located元素加载并且界面上可见 #一般来说只看一个元素判断存在不存在:EC.visibility_of_element_located( #要知道找那个元素,需要传参数---(By.ID,"kw")传进入 ele=WebDriverWait(driver,10,1).until( #每隔1s检查一次,最多等待10s, EC.visibility_of_element_located( #判断元素是否被加载出来 (By.ID,"kw") #显示等待的元素 ) ) #上面是一个显示等待,每隔1s检查一次,最多等待10s, ele.send_keys("特朗普") #大部分代码都使用显示等待,显示等待只对声明了的元素有效,显示等待必须主动声明才有效,不声明没有效果 #隐式等待只要写一次,后面的所有代码的元素定位都有效果
二:隐式等待 driver.implicitly_wait(10)
作用:隐式等待在脚本执行到某个元素定位的时候如果能找到元素就继续往下执行,
如果找不到元素将以轮询方式不停的判断元素是否存在,直到设定的最大超时时候达到了,还找不到就抛出异常
隐式等待一般放在driver创建之后去设置一个隐式等待,隐式等待接受一个参数,数字整形,以秒为单位,
driver.implicitly_wait(10)
设置隐式等待时长10s,当代码执行到某个元素定位的时候呢,就会去做一个判断,
先去看元素存不存在,存在就继续往下走,如果找不到元素将以轮询方式不停的判断元素是否存在
轮询:每隔一段判断一下,默认这个时间是0.5s,
隐式等待注意点:
1:设置隐式等待只对之后的元素定位有效,
2:隐式等待,默认单位是s秒,代码:driver.implicitly_wait(10)最大等待时间10s,
3:当代码执行到某个元素定位的时候,能找到元素就继续执行,如果找不到元素将以轮询方式不停的定位元素是否存在
先定位一下不存在,等上0.5s再定位一下---循环,一直循环到要么找到元素继续执行,要么找不到元素10s超时报错
三:硬编码等待 time.sleep()
隐式等待相对而言比较智能,最大超时时间10s,10s内找到元素就不等继续往下走,time.sleep是不管找到还没没找到硬等
18:xpath的高级语法 谷歌浏览器复制的xpath和css可能定位不准,这时候需要我们自己编写xpath
xpath://*[@id=\"kw\"] id选择器,自己这个元素有id属性可以这样写xpath语法定位
//*[@id="hotsearch-content-wrapper"]/li[1]/a/span[2] 其他没有id属性的xpath,就使用父级元素的id,整个表达式像文件目录结构
xpath结构特别像文件的目录结构, xpath:用路径表达式选取文档的节点
路径表达式:和文件夹路径很像(其实就是针对文档节点进行一层层追层定位,)最顶层html,
元素定位的操作元素基本都在body标签里面,界面上能看到的元素,body里面又是一层层的元素,所以xpath可以通过层级进行定位
xpath表达式的基础语法:
1:最顶层开始写是一个 '/' 如:/html /绝对路径开头是一个/,
2:层和层之间用一个斜杠隔分离开: 如:html/body 上层和下层紧挨,是父子关系才能使用 /
3:如果上层和下层不仅挨,html下有个body子标签,body下有个div子标签:可以使用//表示上层和后辈层之间的关系,不一定要父子: /html//div
4:一层下面有几个同样的标签,匹配其中的一个:可以使用下标[] 来匹配,下标都是从1开始寻找的,如body标签有多个div子标签:
/html/body/div[1]
5:/html开头的定位方式称之为:绝对路径,完整的路径--绝对路径
以根节点最顶层开始,一层一层往下找,这种方式非常受界面影响变化比较大,每一层都必须写,任何一层写错就找不到
中间任何一层发生了变法整个表达式就无法使用了,所以与之对应的是相对路径
一:绝对路径定位:绝对路径以/开头,里面寻找元素的层级关系是一层层往里找
绝对路径定位以一个斜杠开头,代表文档最顶层:/html/body/div/table/tbody/tr[2]/td[2] 一层层找
二:相等路径定位:相对路径定位以两个斜杠开头,两个斜杠任何元素节点都可以作为我的起点
//td //tr[2]//td[2]
相对路径两个斜杠,可以从文档的任意位置进行解析
//匹配到任意层级,任意位置匹配
但是下标不是按照整个文档的顺序来的,假如4个td在不同的tr里面,下标是看相对上个目录下的第几个,可以匹配到两个下标为1的td标签)
三:索引定位(下标,下标从1开始)
//tr[2]//td[2]
四:属性定位,根据元素属性进行定位,需要加@
//td[@id] 匹配所有具有id属性的td标签
//td[@id="a2"] 给属性指定值,匹配id属性为a2的td标签
五:匹配元素它的父元素:使用 ..
//td[@id="a2"]/..
两个点是选取当前节点的父节点: //td[@id="a2"]/.. 选取//td[@id="a2"]元素的父元素
一个点是选取当前节点 //td[@id="a2"]/. 选取//td[@id="a2"]元素
六:xparh处理class复合属性的情况。 contains:包含
<div class="test demo"></div>
<div class="demo test"></div>
<div class="test demo2"></div>
想选出有demo这个class的对象,那应该怎么弄 //div[contains(@class,"demo")]
xpath:'//div[contains(@class,"a")]' 它会取得所有class为a的元素
xpath:'//div[contains(@class,"a") and contains(@class,"b")]' 它会取class同时有a和b的元素
//div[@class="s-top-wrap s-isindex-wrap"] 这样写也可以
七:xpath分布定位的一个坑:谨记
from selenium import webdriver
driver=webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("file:///C:/Users/ywt/PycharmProjects/selenium_ywt/day2/test.html")
ele=driver.find_element_by_xpath('//td[@id="a2"]/..') #找到tr标签赋值给ele
print(ele.find_element_by_xpath("td[2]").text) #通过ele寻找元素找到td2
print(ele.find_element_by_xpath("./td[2]").text) #以后记得加./ 不然可能出错--原因如下
父元素.find_element_by_xpath 开头要加./代表当前目录下的
//代表从任意位置下的(记住重点)
这时候发现分步骤定位应该定位到bbb,这里面没有出现坑,有时候可能出现坑
大部分情况下会出现:这样去寻找--分步骤定位,父元素.find_element_by_xpath
这时候要求在元素开头放一个./ ---为什么这么要求:因为xpath它会出现问题
如果不在父元素.find_element_by_xpath开头不用./代表当前目录下的有可能会从整个文档开始找
就不会按照预期父元素找
19:xpath定位之函数定位方式
一:定位当前节点的父节点两种方式: parent
1:.. 两个点获取到父元素 //td[@id="a2"]/..
2:寻找父元素也可以通过函数:/parent::tr //td[@id="a2"]/parent::tr
两个冒号后面需要指定标签类型(用法垃圾,没优势,知道就行)
预期判断td标签的父元素就是一个p标签, 预期判断,如果匹配不到就证明td标签父标签不是p标签,预期结果不一致(要求标签类型的测试用例基本不存在)
二:选取当前节点的所有先辈节点以及当前节点本身,先辈,父亲,祖父,曾祖父(叔叔伯伯旁系血亲不行)ancestor::table
ancestor::table 选取当前节点的所有先辈元素,不包括本身
//td[@id="a2"]/ancestor::table 这里双冒号后面也需要指定标签类型
ancestor-or-self::table 选取当前节点所有的先辈元素以及当前节点本身
//td[@id="a2"]/ancestor-or-self::td 加个-or-self 能匹配到自己
三:选取当前节点的所有后代元素 descendant
descendant和//一个意思,能够匹配到所有的后代元素
1://tbody//td 双斜杠定位后代元素
2://tbody/descendant::td 这样也可以定位到后辈元素td和//功效一样(这就是后代元素)
3://tbody/descendant-or-self::td 加个-or-self表示选取当前节点所有的后代元素以及当前节点本身也可以找到
四:选取文档当前节点开始标签之前的所有节点 preceding
//td[@id="a2"] 这是当前节点,找当前节点为开始标签之前的节点
//td[@id="a2"]/preceding::td //td[@id="a2"]标签这个开始标签之前的所有td标签都能选到
选取不到当前标签所在的,当前标签所在大标签也匹配不到
就是当前的td标签的之前的哥哥,叔叔,叔叔的儿子都可以匹配上,
当前td标签的爹,爷爷,祖宗匹配不上,因为当前标签在这些祖辈里面
只能匹配到这个层级外面的前面的元素
五:选取当前节点结束标签之后的所有节点 following
//td[@id="a1"]/following::td
选取不到当前标签所在的,当前标签所在都大标签页匹配不到:
只能匹配到这个层级外面的后面的元素
20:定位心得:
1:当前元素属性不能唯一定位的时候,可以尝试往上通过父元素一层一层唯一定位寻找突破口,不能定位就一层层往上加条件,直到定位到为止
2:clik点击一般会点击找到元素中点的位置(只要不是面积特别小,就尽可能的把定位的标签精准一下)
3:代码很容易被前端界面影响的,前端更新代码可能就使用不了
4:xpath里面允许空格的存在--如:@class=\"card card11"
21:找不到元素四种原因:排查顺序
1:表达式写错了 2:要么没有等待 3:要么内嵌网页 4:要么多标签页
22:相对路径和绝对路径,当前节点和父节点
.. 表示上一层目录 . 代表当前目录 / 直接子元素 // 后代子元素
./ 当前路径下的下一层
.// 当前路径之后的任意层级 .
23:xpath过父元素查询下层元素/前面需要加点号:如 ./和 .//
不加可能就报错了,可能从文本最起点开始找 ,或者文本随意节点开始找
24:appium里面必须使用xpath定位,css不行,appium没有实现css的支持
xpath在web浏览器自动化不太建议使用,xpath效率特别低,css的效率高的多,
css定位占据代码的百分之80,但是appium不支持css只支持xpath(app自动化只支持xpath)
25:谷歌浏览器copy的xpath可能存在问题:
1:机器不可能做的很智能,copy的xpath非常不稳定,页面稍微变动就定位不到
2:copy的一般是绝对路径,
所以我们可以一般手记写相对路径,兼容性高
26:关于浏览器的一些操作
1:driver.set_window_size(600,800) 设置浏览器指定的长宽,参数数字单位为像素点,
2:driver.maximize_window() 浏览器最大化,设置全屏,不需要传参
3:driver.minimize_window() 设置最小化浏览器,不需要传参
也可以做成自适应,但是成本很高,selenium没有做
4:driver.back() 控制浏览器后退
5:driver.forward() 控制浏览器前进
6:driver.refresh() 控制浏览器刷新
代码演示:
import time from selenium import webdriver driver=webdriver.Chrome() driver.get("https://www.baidu.com") driver.get("https://m.weibo.cn/") time.sleep(3) driver.back() #控制浏览器后退 time.sleep(3) driver.forward() #控制浏览器前进 time.sleep(3) driver.refresh() #控制浏览器刷选
27:webdriver的一些常用方法:操作的是ele元素对象
1:ele.click() 点击,单击元素,一般来说单机的位置在元素中间点
每个元素都有覆盖范围,也有元素是没有范围的,因为在前端界面上没有展出来
click点击的时候那么selenium会去点击元素覆盖范围最中间的那个点,注意点:元素面积比较小的时候有可能会操作失败
2:ele.send_keys("特朗普") 输入操作,一般操作input类型的元素
3:ele.clear() 清空文本框的内容,一般也是操作input类型的标签
4:ele.submit() 提交表单,类似点击回车
ele=driver.find_element_by_id("kw") #找到输入文本框
ele.send_keys("python\n") 也能成功搜索 \n 模拟回车的操作,换行的操作
ele.submit()
5:ele.get_attribute 获取元素的属性值
6:ele.is_displayed() 检查元素ele是否可见,在界面上隐藏的元素一般是不可见的,返回一个布尔值
7:ele.text 获取标签对(元素)中间的内容
8:ele.size 获取元素的尺寸,返回一个字典 如这种格式:{'height': 44, 'width': 548}
28:css高级语法
css表达式如右: .card-list:nth-child(1) .main-text.m-text-cut
什么叫选择器(这就是css语法的规范了,)
XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言
CSS 指层叠样式表 (Cascading Style Sheets)
css和xpath的说明:
1:项目中用到最多的就是css和xpat,一般来说我们优先选择css
1:css配合html工作:实现原理是一个匹配对象的原理,xpath是配合xml工作的(实现原理是遍历的,xpath效率低),
2:css语言相对于xpath来说更简洁,
3:前端开发更主要用css,所以可以获得更多的帮助
css由两个部分构成:
1:一部分称之为选择器(web-ui自动化测试元素定位就是根据选择器去进行定位的)
2:声明:声明是以键值对的形式展示出来的,如:{color:blue}代表选择器选到的元素是蓝色的
{color:blue;font-size:12px}指定颜色蓝色,字体大小为12像素点,由一个或者多个声明组成
css是给前端指定样式的,功能除了ui自动化定位元素外,还能给前端指定样式
类似穿衣服,页面怎么展示,一般是写在head当中的style标签里面来指定元素的样式
一:id选择器 使用#表示id 如:#ab1
在 css 当中,id 选择器以 # 来定义,#后面跟id属性
#ab1 就可以选中id为ab1的任何标签
二:class 选择器 使用. 表示class属性 如:.ab2
在 css 当中 class 选择器以 . (点号)来定义:.ab2
如果class是复合类:<p class="ab2 ab3">这是class选择器1</p> 这种类型的元素
css selector里面允许把空格变成 . 如:.ab2.ab3
也可以任取其一:.ab2或者.ab3
一定要注意这个css允许clss类用 .号连起来,空格变成.就行
元素定位的时候不要在by_class_name中出现空格,以点代替空格
在css选择器定位的时候以 .代替空格
三:标签选择器: (标签选择器可以和calss选择器组合使用)
p :所有的p标签都选中
p.ab2 :这是标签选择器和class选择器组合使用,选中p标签且标签class为ab2,同时满足两个条件才能选中
四:分组选择器:
a, span :两个标签不需要同时满足,或者的关系,只要是a标签或者span标签都能被选中,选中一组html
在css当中,分组选择器以一个 , 逗号来定义: a, span
五:属性选择器:
选中具有特点属性的html元素 ,在css当中属性选择器以一对中括号[]来定义,
1:属性选择器直接指定属性:选中具有某属性的元素
[name] 指定name属性,选中所有具有name属性的标签
[class] 指定class属性,
2:为属性指定值:
css选择器很灵活,可以用一个css表达式选中具有name属性的元素,
也可以继续添加为他指定值,这样就可以唯一定位了,减少约束条件匹配到的可能更多
唯一定位需要把表达式增加约束条件,相当于缩小选中范围
[name = "ab3"] 选中name属性等于ab3的所有标签
[name="ab3"],[name="ab1"] 可以分组表达式选中多个,选中name属性等于ab3的所有标签或者name等于ab1的所有标签
3:指定标签类型,指定匹配有一类型的标签,有点像tag_name
a[name] 选中所有具有name属性的a标签,为属性指定标签类型
4:匹配一个单词边界,和为属性指定值联合使用的 ~ 匹配一个单词边界
有个元素 <span name="hello world">匹配单词边界</span> name属性为hello world
[name ~= "hello"] 加波浪线~,匹配到name属性有hello字符的标签
~ 匹配一个单词边界,单词边界(完整的单词,hello和world都可以其他不行)还需要是边界的单词
单词边界:匹配一个完整的单词, 单词的开始结束匹配
六:组合选择符
一:后代选择器:后代选择器以空格分离
后代选择器用于选取某元素的后代元素,无论层级有多深,(儿子孙子,从孙,都能选到)
#a1 p 匹配到id为a1的后代元素标签为p的元素(不止匹配儿子,孙子,全部后代只要标签是p都可以匹配到)
二:子元素选择器,子元素选择器以大于号分离(只能选中儿子,不能选中孙子)
#a1 > p 匹配到id为a1的元素的子标签为p的元素
特点:子元素选择器只能选中其父元素的直接子元素(一层,它的儿子)
三:相连兄弟选择器以+分离 可以选中紧挨在另一元素背后的元素,且二者有用相同的父元素
div > ol > li div标签直接子标签ol的直接子标签li
div > ol > li:nth-child(2) + li 选中第三个 li((只能选中紧挨在当前元素背后的元素
只能选中一个,且这个+ li 标签是当前标签的下一个弟弟,有相同的父元素才行
四:后续兄弟选择器,以小波浪线分离
选中指定元素之后所有的弟弟,都是弟弟
div > ol > li:nth-child(2) ~ li 选中当前元素后面所有标签类型为li的弟弟,二弟,三弟,四弟,五弟都能选中
#a4 > div:nth-child(1) ~div 选择在id为a4的第一个div子标签的所有标签为div的弟弟
29:定位方式选择:
优先级最高:id
优先级其次:name属性,(name不一定唯一,但是一般情况下前端开发的时候name属性与id属性多数情况下使其唯一,不唯一的话就使用css等)
优先级再次:css选择器
最次的优先级:xpath(效率低)
其他的class_name,link_text,模糊选择等剩下的四种方法更次,尤其class,link_text,tag_name基本上不用
30:已经找到父元素,在父元素里找子元素谨记点
根据css表达式已经找到的大元素ele,然后再根据ele查找ele里面的子元素,css元素表达式不需要加 .表示当前路径下
ele.find_element_by_css_selector('div >div >a >span').text 不需要加点,
而找到大元素,然后根据xpath查找ele里面的子元素,元素表达式需要加 . 点号
ele.find_element_by_xpath(".//div[1]//span") 需要加 .号才能寻找,css不需要加 .,
这种情况xpath不加. 会从整个html页面从头开始寻找,而不会从爹元素里面找子元素
31:chrome调试前端代码步骤:
1:f112打开开发者模式
2:点击右上角的三个点标志
3:点击 show console drawer 后最下面会显示调试的输入文本框 可以帮忙调试前端代码
32:鼠标悬停操作:
from selenium.webdriver.common.action_chains import ActionChains #鼠标悬停的库函数
ActionChains(driver).move_to_element(ele).perform() #在ele元素上悬停,需要找到这个ele元素
33:driver.get 会等页面加载完成,除了get以外任何刷新都不会等,
34:遗漏的知识点:
一:class复合类,xpath不支持使用"."连接class,css不支持空格写多个class属性
复合类的xpath表达式: //div[@class="span-968 fl"]/ul[@class="grid-list clearfix"] "span-968 fl" 使用空格间隔class属性,.不写
复合类的css表达式: div.span-968.fl > ul.grid-list.clearfix > li.grid-items .span-968.fl 使用 . 点连接多个class属性,空格不行
二:我们整个selenium有不稳定的地方, ----selenium找元素就是不稳定
定位不到的情况下:
1:换种方式定位
2:重写表达式匹配
35:js代码滑动下拉框
#window.scrollBy(0,1000) 这是js滑动进度条代码,执行js代码向下滚动
driver.execute_script("window.scrollBy(0,1000)") js代码当成字符串传进去,这样就可以执行js代码 ---向下滑动
python里想要执行js代码需要使用 driver.execute_script方法
36:窗口截图 screenshot函数
一:对元素进行截图,截屏,截取单个元素,截取元素首先需要找到元素ele,
ele=driver.find_element_by_id('kw')
ele.screenshot("./ele.png")
二:对整个页面进行截图
driver.get_screenshot_as_file("./all.png") 截取整个页面,要求文件的后缀名是png,否则会给个警告,
这个函数截屏整个页面,截下来的这个图片是一个文件,
要给这个文件指定一个路径和取名--这就是函数的参数,
把图片保存那个路径 ./ 当前路径下的
图片名字是什么 all.png
37:警告框处理(webdriver处理警告框比较简单)
什么是警告框:点击框会有消息弹窗(这个警告框不是html标签,不能通过find_element_by_这种方式定位到)
selenium提供了警告框专门处理方法
type="button" 元素展示为一个按钮的形状
警告框有三种:
1:alert("这个窗口是对话框!")
2:confirm("确认框!")
3:prompt("请输入您的反馈意见", "测试") ,这是提示框
一:对话框(对话框只有一个确认按钮)
driver.find_element_by_id('bu1').click() #点击触发对话框
al=driver.switch_to.alert #selenium提供这个方法获取到alert对象,对话框对象,赋值给一个变量al
print(al.text) #al.text返回现在警告框当中的文字信息
al.accept() #接受现有现有警告框---也就是点击确认
二:确认框(除了确认按钮还多出个取消按钮)
driver.find_element_by_id('bu2').click() #触发确认框
al=driver.switch_to.alert #提供这个方法获取到alert对象,对话框对象,赋值给一个变量al
al.accept() #接受---点击确认
al.dismiss() #取消现有警告框
三:提示框(除了有确认+取消按钮还有文本输入的框)
driver.find_element_by_id('bu3').click() #触发提示框 al=driver.switch_to.alert al.send_keys("布谷鸟") #发送文本至警告框,input标签send_keys是能看到结果的,警告框发送文本send_keys看不到输入的值,
#但是能输入成功(输入的文本是成功的,但是看不到输入过程) time.sleep(5) al.accept()
38:鼠标事件
鼠标事件需要先导入鼠标事件库函数才能操作:
from selenium.webdriver.common.action_chains import ActionChains
selenium做自动化难免会遇到模拟鼠标操作才能进行的情况::单机,双击,鼠标右键,拖拽等等这些操作
selenium提供了一个类专门ActionChains处理这些事情
一:鼠标拖动 drag_and_drop鼠标拖动
鼠标拖动需要涉及到两个元素,ele1和ele2(目标元素和起始元素) ele1起始元素 ele2目标元素
鼠标拖动操作,需要进入鼠标类,from selenium.webdriver.common.action_chains import ActionChains这个类提供了鼠标模拟操作
ActionChains(driver).drag_and_drop(ele1,ele2).perform() drag_and_drop拖动函数,从ele1拖到ele2
鼠标事件和其他的操作不同,想执行必须加perform(),前面只是把操作注册进去,并没有执行
二:鼠标右击 context_click右击
鼠标右击.context_click,右键操作只操作一个元素
ActionChains(driver).context_click(ele1).perform()
三:鼠标双击操作 double_click双击
ActionChains(driver).double_click(ele1).perform()
四:鼠标悬停
ActionChains(driver).move_to_element(ele1).perform() #移动到元素上悬停
五:鼠标左键单击 .click方法也是单击
ActionChains(driver).click(ele1).perform()
from selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains import time driver=webdriver.Chrome() driver.get("file:///C:/Users/ywt/PycharmProjects/selenium_ywt/day4/test2.html") ele1=driver.find_element_by_id('blackSquare') ele2=driver.find_element_by_id('targetEle') #鼠标拖动需要涉及到两个元素,ele1和ele2 #鼠标拖动操作,需要进入鼠标类:from selenium.webdriver.common.action_chains import ActionChains这个类提供了鼠标模拟操作 ActionChains(driver).drag_and_drop(ele1,ele2).perform() #drag_and_drop拖动函数,从ele1拖到ele2 #鼠标事件和其他的操作不同,想执行必须加perform(),前面只是把操作注册进去,并没有执行 #鼠标右击.context_click,右键操作只操作一个元素 ActionChains(driver).context_click(ele1).perform() #鼠标双击操作 ActionChains(driver).double_click(ele1).perform() #鼠标悬停 ActionChains(driver).move_to_element(ele1).perform() #移动到元素上 #鼠标左键单击,可以直接元素.click方法直接操作,偶尔失败的话使用鼠标事件的单击去替换,下面这种模拟鼠标事件很少用 ActionChains(driver).click(ele1).perform()
39:键盘事件
键盘事件需要导入Keys类才能进行操作:from selenium.webdriver.common.keys import Keys
Keys类几乎提供了键盘上所有按键的方法,基本都有
Keys.BACK_SPACE 删除按键
Keys.SPACE 空格键
Keys.TAB 制表符
Keys.ESCAPE 回退键--左上角esc
Keys.CONTROL,'A' ctrl+a control键
Keys.CONTROL,'C' ctrl+C
Keys.CONTROL,'X' ctrl+X
Keys.SHIFT shift键位
from selenium.webdriver.common.keys import Keys from selenium import webdriver import time driver=webdriver.Chrome() driver.get("https://www.baidu.com") ele=driver.find_element_by_id('kw') #找到搜索的输入框 ele.send_keys('特朗普') time.sleep(3) #想要键盘事件退格删掉一个字--删除一个字符也就是按下退格键 ele.send_keys(Keys.BACK_SPACE) #BACK_SPACE是退格按键(ele输入框按) ele.send_keys(Keys.CONTROL,'A') #模拟ctrl+a全选 ---a不区分大小写,都可以实现 ele.send_keys(Keys.BACK_SPACE) #这里是先全选再删除
40:内嵌网页 :iframe标签
内嵌网页类似这种的html:<iframe src="https://cn.bing.com/"></iframe> iframe标签 就是内嵌网页
内嵌网页:网页还镶嵌着另外的网页(一个网页镶嵌到另一个网页当中,为了少些代码)
网页比较复杂可以拆分成不同模块分开来写,这就是内嵌网页的价值
怎么发现内嵌网页:
1:点击一个网页,发现整个页面只刷新一部分,一些界面总是不变的,
相当于点击这个按钮内嵌网页a,点击b按钮变成镶嵌网页b,c按钮镶嵌网页c--内嵌网页的价值
2:一直往上滑动找到iframe,一直往上滑滑到顶了没有了都找不到iframe标签证明不是内嵌网页
3:也可以右键copy xpath 查看绝对路径:/html/body/iframe[2]
如果元素在iframe标签内,直接定位是定位不到的,内嵌网页类似房间一样,大房间单独开辟小房间,
想拿小房间里面的元素,必须进入小房间才可以
body > iframe:nth-child(3) 这个代表body下第三个标签,且标签类型为iframe,而不是第三个iframe标签(记住了)
这个标签有两个条件:1:下标 2:标签类型
一:进入内嵌网页: 内嵌网页里面的元素是不能直接定位操作的,需要跳进内嵌网页才能操作
ifra=driver.find_element_by_css_selector('body > iframe:nth-child(3)') 先定位到想切进去的iframe标签
driver.switch_to.frame(ifra) 切换到iframe
二:跳出内嵌网页:
driver.switch_to.default_content()
from selenium import webdriver driver=webdriver.Chrome() driver.get('file:///C:/Users/ywt/PycharmProjects/selenium_ywt/day4/test3.html') ifra=driver.find_element_by_css_selector('body > iframe:nth-child(3)') #定位到想切进去的iframe标签 driver.switch_to.frame(ifra) #切换进去。切换到iframe # driver.switch_to_frame(ifra) #这个方法未来会被删除,不推荐使用 driver.switch_to.default_content() #跳出内嵌网页,切换到主页面 driver.find_element_by_id('kw').send_keys('特朗普')
41:多标签页的切换
找不到元素就四种可能:(除了这四个没有其他可能了)
1:元素表达式错误,元素就是不存在
2:需要元素等待有没有
3:内嵌网页 iframe是不是
4:多标签页
写元素定位表达式:满足以下两个条件 精准匹配
1:所有你想要的元素都能匹配到
2:除了你想要的元素没有任何其他元素被匹配到(要精准定位)
多标签页:
两个标签页或者以上,driver寻找元素的时候还在前一个标签也上面找
点击一个网页元素,会打开一个新的标签页,这时候就有两个标签页,而还在前一个标签页上面找
找不到需要切换到第二个新打开标签页才能找到
一:获取当前所有打开的窗口的句柄 :driver.window_handles
句柄:文件资源标识符,相当于操作系统中给文件给资源唯一的身份信息
all_handles=driver.window_handles 获取所有的窗口句柄(多数个标签页多数个句柄),返回一个列表
得到所有的文件句柄后,for循环进入每个文件句柄,找到想要操作的句柄后就停止循环break,进行操作
for hanle in all_handles:
driver.switch_to.window(hanle)
if driver.title =='张一鸣(北京字节跳动科技有限公司创始人、CEO)_百度百科': 找到对应页面的句柄
break 找到了对的句柄退出循环,然后可以在新的句柄上进行操作了(切换到想要的句柄了就break)
也可以根据下标进行操作
百度搜索张一鸣,后点击第一个选到的链接,链接点开后点击找到秒懂百科内容输出,获取text文本
from selenium import webdriver driver=webdriver.Chrome() driver.implicitly_wait(10) driver.get('https://www.baidu.com') driver.find_element_by_id('kw').send_keys('张一鸣\n') #搜索完成 #点击第一个链接 driver.find_element_by_css_selector('div[id="content_left"]> div:nth-child(2)> h3[class="t c-gap-bottom-small"]> a[href]').click() #获取当前所有打开的窗口的句柄(句柄:文件资源标识符,相当于操作系统中给文件给资源唯一的身份信息) #上面有两个标签页 all_handles=driver.window_handles #获取所有的窗口句柄(多数个标签页多数个句柄),返回一个列表 for hanle in all_handles: driver.switch_to.window(hanle) if driver.title =='张一鸣(北京字节跳动科技有限公司创始人、CEO)_百度百科': #找到对应页面的句柄 break #找到了对的句柄退出循环然后可以在新的句柄上进行操作了(切换到想要的句柄了就break) #也可以根据下标进行操作 #获取一个某元素的文本 txt=driver.find_element_by_css_selector('dl[class="second-know "]>dt').text print(txt)
42:点击一个输入框输入时候,会展开一个输入框的界面,如果展开的输入框把下面的元素挡住,操作被遮挡的元素
会报错(元素操作交互异常) 因为遮挡的原因
解除遮挡看具体网页情况:点击一下空白位置。(selenium需要找一个点击没有反应的元素点击一下就行)
43:有时候页面有跳转的情况下,代码不报错,但是页面操作元素没反应
代码没有报错证明元素操作成功的,元素操作成功就证明找得到元素
所以显等和隐等都不起作用,因为元素找到的并且操作成功
为什么元素找到了没有效果:因为电脑运算比较慢,加强制等待阻塞代码,
让浏览器可以慢慢去计算(不是找不到元素,而是元素操作成功了,而是浏览器反应不过来就需要使用time.sleep强等)
44:下拉框选择
下拉框:有两种:
1:普通的div-span标签形成的下拉框,自己写的html,能html直接定位到元素直接操作
2:select标签形成的下拉框,需要专门方式去处理如下:
需要导入专门的库来处理select选择标签:from selenium.webdriver.support.select import Select
也可以使用html定位元素的类型定位进行点击
<select id="abc">
<option value="p0">请选择你的月薪</option>
<option value="p1">月薪三千</option>
<option value="p2">月薪三万</option>
<option value="p3">月薪三十万</option>
</select>
一:对select类型下拉框进行操作步骤
1:需要导入库函数:from selenium.webdriver.support.select import Select
2:定位到select下拉框:ele=driver.find_element_by_id('abc')
3:根据文本,value值,下标进行选择:如下
ele=driver.find_element_by_id('abc') 定位到下拉框元素,select标签
Select(ele).select_by_visible_text('月薪三万') 通过可视文本,眼睛能够看到的就是可视文本,标签对中间能看到的内容
Select(ele).select_by_value('p3') 根据option标签里面value属性进行选择的
Select(ele).select_by_index(0) 根据下标进行选择,从0开始数
select下拉框演示代码:使用Select第三方库的方法进行操作 from selenium.webdriver.support.select import Select from selenium import webdriver driver=webdriver.Chrome() #实例化一个浏览器对象 driver.get('file:///C:/Users/ywt/PycharmProjects/selenium_ywt/day5/test.html') #访问网页 #操作select下拉框选择月薪3w #定位到下拉框元素---select标签 ele=driver.find_element_by_id('abc') #定位到下拉框元素,select标签 #1:选择可视文本选择 Select(ele).select_by_visible_text('月薪三万') #通过可视文本,眼睛能够看到的就是可视文本,标签对中间能看到的内容 #2:根据value属性值进行选择 Select(ele).select_by_value('p3') #根据option标签里面value属性进行选择的 #3:根据下标进行选择 Select(ele).select_by_index(0) #根据下标进行选择,从0开始数 select下拉框演示代码:使用html自己定位来进行操作 import time from selenium import webdriver driver=webdriver.Chrome() #实例化一个浏览器对象 driver.implicitly_wait(10) driver.get('file:///C:/Users/ywt/PycharmProjects/selenium_ywt/day5/test.html') driver.find_element_by_id('abc').click() time.sleep(3) driver.find_element_by_css_selector('#abc > option[value="p3"]').click()
45:文件上传
文件上传有两种情况:
1:直接用input标签实现的文件上传功能,这种文件上传功能直接当成一个文本输入框就可以了。
直接通过send_keys指定本地文件路径就可以实现文件上传了(简单)
2:非input标签实现的文件上传功能,我们可以通过模拟键盘敲击的方式实现
一:非 input 标签实现的上传功能:需要导入:import win32com.client
需要pip安装pywin32 pip install pywin32
对于非 input 标签实现的上传功能,我们可以通过模拟键盘敲击的方式实现
非 input 标签实现的上传,点击上传元素会打开文件资源服务
文件资源管理器(有一个默认的光标选中文件名可以输入,直接敲击键盘可以输入进去)
实现步骤:
1:点击文件上传按钮,启动文件资源管理器之后
2:直接输入文件路径
3:敲击回车键就 ok了(达到文件上传的目的)
注意点:
1:自己电脑上装了输入法,输入法设置默认是中文输入,敲击键盘输入的是中文(需要修改默认是英文输入---莫得感情的机器)
2:结尾\n模拟回车键,电脑不行的需要\r\n模拟回车键 \r代表回车
from selenium import webdriver import win32com.client import time driver = webdriver.Chrome() driver.get("https://tinypng.com/") driver.find_element_by_css_selector(".target > .icon").click() # 触发文件上传。文件文件上传按钮 time.sleep(3) sh = win32com.client.Dispatch("WScript.shell") #创建一个shell对象,WScript.shell---windowscript.shell, sh.Sendkeys("C:\\Users\\ywt\\PycharmProjects\\selenium_ywt\\day4\\ele.png\n") #sh对象创建好了后可以调用sh操作输入,毫无任何判断逻辑, click操作结束以后,立马就到输入信息,不管,没有任何判断逻辑,点击输入框跳转到文件资源管理器需要反应的时间(所以需要强等sleep)
二:input 类型标签实现的文件上传功能:定位到input这个元素,之后直接send_keys
对于 input 标签实现的文件上传功能,可以将其看作是一个文本输入框
直接通过 send_keys 指定本地文件路径就可以实现文件上传
driver.find_element_by_css_selector("input[type=\"file\"]").send_keys("C:\\Users\\ywt\\PycharmProjects\\selenium_ywt\\day4\\ele.png")
46:网页端上的cookie操作 driver.get_cookies()
cookie:缓存,小饼干,--放在本地浏览器上的缓存,记录了一些信息,
webdriver提供了cookie一些相关方法,读信息,添加信息,也可以去删除cookie信息
token不一定存在cookie上,看开发怎么定义
一:cookie的操作(获取登录前和登录后的cookie进行对比)
import time from selenium import webdriver driver=webdriver.Chrome() driver.get('http://127.0.0.1:8088/login') #获取登录前的cookie信息 cook=driver.get_cookies() #返回的的列表,列表里面多个字典,获取所有的cookie信息 print(cook) # print(driver.get_cookie('Hm_lpvt_750463144f16fe69eb3ac11bea1c4436')) #driver.get_cookie获取一个cookie字典,根据cookie里的name属性的值获取的,返回的值是cookies返回的列表里的一个元素 #cookie存的一个又一个的信息,信息以字典形式存在,很多条信息需要存储,字典放在一个cookies列表当中, # 字典都有一个name,名字name,这个名字就是这个字典的名字 #cookie的长度是不固定的,它有最长的长度,但是长度不固定,分部负责什么,对于开发人员来说想存什么就存什么--自定义的 #多个cookie,比如说 'name': 'beegosessionID'这个cookie用来判断登录有没有效的 #每个cookie的作用是什么(详情看开发) #cookie一般用来:在浏览器本地存储信息,具体存哪些信息由开发者定义,不固定的,数量和内容都不固定
cookie拿到登录以后可以进行绕过登录的,但是这个功能比较鸡肋,
因为这个cookie有一个有效时间,一旦超过了有效时间,cookie失效
那么cookie就不能用了,一般来说普遍习惯cookie有效时间2小时,时间可以调控的
使用cookie绕过登录: #登录后的cookie如下:已经获取到 cookie=xxx #已经获取得到的现成的cookie值 import time from selenium import webdriver driver=webdriver.Chrome() driver.get('http://127.0.0.1:8088/login') #设置之前delete清除原有的cookie,登录后的所有的cookie都拿过来了然后设置进去,所以设置之前要清除当前所有的cookie driver.delete_all_cookies() #删除所有的cookie信息 #设置cookie for cook in cookie: driver.add_cookie(cook) #add_cookie 不是add_cookies ---所以需要循环去添加, #selenium没有提供一次性加一个cookie列表的操作,提供的操作一条条加的 driver.refresh() # cookie设置进去后浏览器已经被加进cookie了,但是没有即时生效,所以需要主动刷新一些页面
有验证码怎么办
1:万能验证码(开发在测试环境设置一个码。无论何时何地都有效)
2:去掉验证码,测试环境干掉验证码
3:破解验证码成本很高,不建议这也做(selenium破解不了,需要其他技术)
4:让开发不检查验证码的真实性,随便传就行
登录当中经常用到cookie:
接口当中的cookie和webdriver的cookie有一些差别,
cookie是服务器在本地浏览器上存的一些信息,不同账号登录cookie不同,
sock注入:基本所有网站默认都有sock注入的防护功能的,安全防护
47:po模式基础(重点)
po模式:page object page页面 objcet对象,页面对象
po模式好处:代码结构更加清晰,维护起来更加方便,可读性更高,扩展性更强,后期维护方便,高复用,低耦合
ui自动化操作网页,一个又一个的页面,每个页面都封装成一个class,
有首页,我的主页,登录页面等各种页面,每个页面都有一些页面元素
这些页面都能够封装成一个class,这个叫page object,po模式的缩写,---面向对象
登录页面封装成class类,登录页面上有很多html标签,只把需要和测试用例交互,用得上的元素封装到class里面就行
用户名,密码,登录按钮,用得到就封装,用不到就不封装,元素需不需要封装看测试用例里面有没有涉及到它的操作,
一:po模式基础版本,页面元素封装到init方法,和动作类分离 如下
from selenium import webdriver class LoginPage: def __init__(self): #在init方法里把需要操作的元素封装进去 self.driver=webdriver.Chrome() #创建driver对象#实例属性, #打开网址 self.driver.get('http://127.0.0.1:8088/login') #用户名输入框 self.username=self.driver.find_element_by_name('username') #密码输入框 self.pwd=self.driver.find_element_by_name('password') #登录按钮 self.loginButton=self.driver.find_element_by_css_selector('button') def login(self): self.username.send_keys('libai') self.pwd.send_keys('opmsopms123') self.loginButton.click() if __name__ == '__main__': Lp=LoginPage() Lp.login()
#这样写提前在init里面把找到页面元素对象找到,方法不上很好,页面刷新再操作找到的元素对象会报陈旧元素错误
二:po模式陈旧元素报错
陈旧的元素:
1:定位元素对象赋值给变量
2:界面刷新
3:调用变量又操作元素。报错陈旧的元素
刷新页面后,里面的资源发生了变化,操作系统给他分配的资源,内存地址已经发生了变化
资源有一次消耗再创建的过程,陈旧的元素的引用,对象有消耗再重写创建的过程,(页面对象)
主页点击后页面刷新。页面刷新之后之前封装的pm元素对象就不能用了,用了会导致陈旧元素引用报错
selenium有一个特点:页面有刷新操作的时候前面ele=driver.find_element_by_XXX这种方式找到的ele元素对象
是陈旧的元素,不能再ele.click()和ele.send_keys等各种方法操作这个元素对象了 ------陈旧的元素
#po模式报错版本,元素陈旧错误 ---注意这种情况 from selenium import webdriver driver=webdriver.Chrome() #driver全局变量,让driver唯一,先创造一个driver变量,让别人调用这个变量 driver.implicitly_wait(10) driver.get('http://127.0.0.1:8088/login') class LoginPage: #登录类 def __init__(self,driver): #driver浏览器对象再外面创建的 self.driver=driver self.username=self.driver.find_element_by_name('username') self.pwd=self.driver.find_element_by_name('password') self.loginButton=self.driver.find_element_by_css_selector('button') def login(self): self.username.send_keys('libai') self.pwd.send_keys('opmsopms123') self.loginButton.click() class HomePage(): #主页类 def __init__(self,driver): self.driver=driver #我的主页 self.myhome=self.driver.find_element_by_css_selector('i[class="fa fa-home"]+span') #项目管理 self.pm=self.driver.find_element_by_css_selector('i[class="fa fa-book"]+span') #元素存变量 if __name__ == '__main__': Lp=LoginPage(driver) Lp.login() #登录 Lh=HomePage(driver) Lh.myhome.click() #跳转到我的主页 Lh.pm.click() #前面我的主页点击后页面刷新。页面刷新之后之前封装的pm元素就不能用了,用了会导致陈旧元素引用, Lh.myhome.click() 这里操作项目管理分成两步骤 1:获取元素 :Lh=HomePage(driver)这里实例化得到元素主页和管理并且存在了LH这个实例的名称空间里 2:操作元素 :Lh.pm.click()去操作元素,获取元素和操作元素中间Lh.myhome.click()点击了主页 点击我的主页界面有刷新,界面一刷新javascript就要重新加载,所以再去使用之前找到的元素就会报错(陈旧的元素引用) 解决方案:重新获取元素,重写driver.find_element_by_css_selector给变量--不合适,目前写法做不到 只在对象被实例化的时候去获取一遍元素,会很容易出现陈旧的元素引用 因为元素在实例化的时候就被找完了,各种操作会引起界面刷新,前面实例化定位的元素表达式虽然没有出错, 但是前面的定位的元素表达式存给的变量不能使用了,用就报陈旧的元素异常,因为界面刷新了, 尽管表达式没有变化是正确的,之前获取到的变量再去调用时候就陈旧元素的引用了 上面这种代码模式,陈旧元素不可避免的,把元素定位专门写成一个函数,这个函数里面每一次去实时获取
三:po模式避免元素陈旧问题修改版本:把元素定位写成函数的方式而不是定义在init里面写成一个变量去调用
from selenium import webdriver driver=webdriver.Chrome() driver.implicitly_wait(10) driver.get('http://127.0.0.1:8088/login') class LoginPage: def __init__(self,driver): self.driver=driver #为了避免陈旧元素,元素定位专门写成一个函数 def usernameBox(self): #用户名输入框,写成一个函数,需要用到这个元素的时候每一次去实时获取 return self.driver.find_element_by_name('username') def pwdBox(self): #密码输入框 return self.driver.find_element_by_name('password') def loginButtonBox(self): #登录按钮 return self.driver.find_element_by_css_selector('button') def login(self): self.usernameBox().send_keys('libai') self.pwdBox().send_keys('opmsopms123') self.loginButtonBox().click() #self.loginButtonBox()是一个元素,函数执行结果的返回值return,相当于self.driver.find_element_by_css_selector('button') #相当于点击框元素,所以可以click操作, #在这里实时获取,而不是都调用一个已经找到的变量 class HomePage(): def __init__(self,driver): self.driver=driver #我的主页 def myhomeBox(self): return self.driver.find_element_by_css_selector('i[class="fa fa-home"]+span') # 项目管理 def pmBox(self): return self.driver.find_element_by_css_selector('i[class="fa fa-book"]+span') #元素存变量 if __name__ == '__main__': Lp=LoginPage(driver) Lp.login() Lh=HomePage(driver) Lh.myhomeBox().click() Lh.pmBox().click() Lh.myhomeBox().click()
#为了避免陈旧元素错误,元素定位专门写成一个函数,放在init方法里的话对象的初始化的时候就会把所有的元素对象定位到
#写成函数的话每一次Lh.myhomeBox().click()这样操作元素的时候,都会去执行函数myhomeBox
#函数每一次都是实时获取元素对象的,和把元素对象赋值给一个变量调用不同,所有能够避免陈旧的元素引用异常
四:po模式的进阶:
1:po模式就是用面向对象的方式写web -ui自动化的代码,就这么简单page object,将页面封装成类
然后通过对类的调用去实现自动化操作(就这么简单的一个内容),分层--是一种思想---建立在面向对象的基础操作)
设计模式是一种思想,大部分的基础操作都是建立在面向对象的基础之上,
元素以封装的形式封装到clsss的def中,使用的使用实例化再调用
2:driver对象的写法
driver浏览器对象,每一个class类都需要使用,除了首页登录页面,考勤等等页面类都需要操作driver
思想:
driver抽离出来单独形式一个类,避免重复使用(driver封装成class),写个专门处理driver的文件 my_driver
my_driver中定义一个class,就叫 Driver
这个 Driver作用:浏览器驱动工具类
1:需要有一个浏览器的变量代表浏览器,使用变量.fid_element去寻找元素,
希望通过继承的方式被其他所有的class去使用,比如Login函数去继承driver来直接使用
2:driver只希望有一个,只打开一个浏览器 单例模式
3:除了driver以外,还希望浏览器打开后能自行的去最大化
4:每一次去主动调用driver当中的init方法,需要生成创建驱动所以主动调用这个方法
每一次调用都会打开新的浏览器驱动(每次执行这个函数都会打开新的浏览器)
加个判断逻辑,如果第一次调用就生成浏览器驱动,
如果不是第一次调用就把生成好的浏览器驱动直接返回就行,
实现这个首先需要一个初始化的值,driver=None,driver 初始化为空,为了做判断,
3:一个class中有页面动作函数,也有页面元素函数,显得有些冗余,结构显得有点点臃肿,
所以抽离出页面动作类,页面动作类每一个都继承相应的页面元素类
继承页面元素类之后,各种按钮元素,登录按钮,密码输入按钮,driver子类都可以直接使用
4:po模式初级版本代码
mySettings.py
# driver 路径 driverPath = { "Chrome":"D:\\tool\selenium\chromedriver.exe", "Firefox":"D:\\tool\selenium\gcdriver.exe" }
homePage.py
from xxx.myDriver import Driver class HomePage:
def __init__(self): # 创建浏览器驱动 self.driver = Driver.getDriver() # 首页侧边栏,我的主页按钮 def myHomePageButtonBox(self): return self.driver.find_element_by_css_selector("i[class=\"fa fa-home\"] + span") # 首页侧边栏,项目管理按钮 def pMbuttonBox(self): return self.driver.find_element_by_css_selector("i[class=\"fa fa-book\"] + span")
my_Driver.py
from selenium import webdriverclass Driver(): '''浏览器驱动的工具类''' #driver 初始化为空 driver=None #类属性,全部对象共享 @classmethod #类方法,这个方法只涉及到类属性driver,所以定义为类方法 def getDriver(cls,brower_name='Chrome'): #获取浏览器驱动对象 #如果driver=None则需要创建driver,执行下面的代码创建driver,创建可能涉及到不同浏览器可以getDriver里面加个浏览器参数 if cls.driver is None: #如果第一次进入函数driver=None,cls.driver为假,cls为假就初始化driver创建浏览器驱动 #后面每次进入getDriver函数,cls.driver都不为空了,已经有浏览器对象直接return cls.driver就可以了 if brower_name =="Chrome": cls.driver=webdriver.Chrome() elif brower_name=="Firefox": cls.driver = webdriver.Firefox() elif brower_name=="Ie": cls.driver=webdriver.Ie() cls.driver.maximize_window() # 窗口最大化 cls.driver.get('http://127.0.0.1:8088/') #访问网址 return cls.driver 逻辑:定义一个浏览器驱动的工具类Driver,这个工具类初始化的driver变量的值为None,然后定义了一个函数, 这个函数要使用类里的类属性driver,所以定义成类的方法@classmethod,
函数还需要能根据不同情况初始化不同的浏览器对象,所以加个缺省参数,默认使用谷歌浏览器,如果想用其他浏览器 需要主动传递参数进入,创建驱动逻辑之外,加判断,判断driver是不是初始化状态,如果为空则需要创建,
如果不为空,直接retuen已经有的浏览器对象给调用者直接使用 让创造浏览器对象的操作只有在第一次进入getDriver函数的时候会被使用,后面进入都不会被调用了
因为需要适配不同的浏览器,可以把浏览器的路径和浏览器驱动的路径放在conf/mySetting里面
可以把浏览器驱动文件放在date文件夹里,方便维护和操作
loginPage.py
from XXX import Driver class LoginPage(): #页面元素类,里面封装的都是页面元素和创建driver对象 def __init__(self): self.driver=Driver.getDriver() #创建driver对象,不传参数默认谷歌浏览器 #为了避免陈旧元素,元素定位写成一个函数 #用户名输入框,写成一个函数,需要用到这个元素的时候每一次去实时获取 def usernameBox(self): return self.driver.find_element_by_name('username') # 密码输入框 def pwdBox(self): return self.driver.find_element_by_name('password') # 登录按钮 def loginButtonBox(self): return self.driver.find_element_by_css_selector('button') #抽离出页面动作类,元素和代码分离,继承相应的页面类,这样动作和元素分开了 class LoginPage_action(LoginPage): def login(self): self.usernameBox().send_keys('libai') self.pwdBox().send_keys('opmsopms123') self.loginButtonBox().click() if __name__ == '__main__': LoginPage_action().login()
5:继承里面打开多浏览器的问题详解
代码一:这样写会打开两个浏览器,b.getDriver()会调用父类Driver的getDriver方法
此时:cls=b,b类自己的名称空间没有driver名字,会使用父类Driver名称空间的driver
父类名称空间的driver=None,能进入if语句里面,运行 cls.driver = webdriver.Chrome()
此时的cls=b 那么b.driver= webdriver.Chrome(),会再b类的名称空间里创建一个driver变量,然后return返回出去
但是b类名称空间的driver和父类Driver名称空间的driver不是一个,是两个driver,父类Driver名称空间的driver还是None
然后c.getDriver(),同理,c名称空间没有driver,调用父类的driver,父类Driver名称空间的driver是None
进入if语句,c.driver = webdriver.Chrome(),c名称空间也创建一个driver -----所以b类里的driver和c类里的driver不是一个driver
创建了两次
from selenium import webdriver class Driver(): driver = None @classmethod def getDriver(cls, brower_name='Chrome'): if cls.driver is None: if brower_name == "Chrome": cls.driver = webdriver.Chrome() cls.driver.maximize_window() # 窗口最大化 cls.driver.get('https://www.baidu.com/') # 访问网址 return cls.driver class b(Driver): pass class c(Driver): pass if __name__ == '__main__': b.getDriver() c.getDriver()
代码二:使用继承但是修改一下代码让driver只创建一次--只操作Driver类里面的driver对象
里面的cls全部修改成Driver,只操作Driver名称空间的driver变量,让所有子类共享--保持一份
from selenium import webdriver class Driver(): driver = None @classmethod def getDriver(cls, brower_name='Chrome'): if Driver.driver is None:
if brower_name == "Chrome": Driver.driver = webdriver.Chrome()
Driver.driver.maximize_window() # 窗口最大化
Driver.driver.get('https://www.baidu.com/') # 访问网址
return cls.driver class b(Driver): passclass c(Driver): passif__name__ == '__main__': b.getDriver() c.getDriver()
代码三:b和c不继承Driver类,只在init里调用Driver类的getDriver()方法也可以保证只打开一个浏览器
from selenium import webdriver class Driver(): driver = None @classmethod def getDriver(cls, brower_name='Chrome'): if Driver.driver is None:
if brower_name == "Chrome": Driver.driver = webdriver.Chrome()
Driver.driver.maximize_window() # 窗口最大化
Driver.driver.get('https://www.baidu.com/') # 访问网址
return cls.driver
class e: def __init__(self): self.driver=Driver.getDriver() class f: def __init__(self): self.driver = Driver.getDriver()
e()
f()
这样通过init方法调用的话只会有一个浏览器,e()一开始调用Driver类的getDriver方法,Driver类的driver属性就创建了
f()再调用Driver类的getDriver方法的时候现在Driver类已经有了driver对象的值了,所有不需要再实例化driver对象了
报此只打开一个浏览器
代码四:使用__new__双下方法保持类里面对象的单例模式
六:代码里还存在的问题
1:析构方法:
def __del__(self):
self.driver.close()
析构方法使用close()退出浏览器成功,但是析构方法使用quit()退出浏览器报错
ImportError: sys.meta_path is None, Python is likely shutting down
2:测试用例执行完毕后退出浏览器,下一条测试用例执行,重新打开浏览器每次打不开,也会报错:
ImportError: sys.meta_path is None, Python is likely shutting down
目前只能暂且使用打开一次浏览器测试全部测试用例,每个页面一个url
每个页面写一个跳转到页面的办法,测试一个页面跳转一次url,简单
3:代码里面还需要加日志文件 logging模块:写个日志装饰器
七:po模式抽离出basepage 的版本
1:mySettings.py文件
DOMAIN ="https://www.baidu.com/" #域名url TIMEOUT = 10 #超时时间 POLL_FREQUENCY = 0.5 #轮询时间 driverPath = {"Chrome": "/Users/nathaniel/MyApp/lib/seleniumLib/chromedriver","Firefox": "D:\\tool\selenium\gcdriver.exe"}#driver路径
2:myDriver.py
from xxx.mySettings import driverPath, DOMAIN from selenium import webdriver class Driver: _driver = None #私有类属性 @classmethod def get_driver(cls, browser_name='Chrome'): if cls._driver is None: if browser_name == 'Chrome': cls._driver = webdriver.Chrome(driverPath["Chrome"]) elif browser_name == 'Firefox': cls._driver = webdriver.Firefox(driverPath["Firefox"]) elif browser_name == 'Safari': cls._driver = webdriver.Safari(driverPath["Safari"]) elif browser_name == 'Opera': cls._driver = webdriver.Opera(driverPath["Opera"]) elif browser_name == 'edge': cls._driver = webdriver.Edge(driverPath["Edge"]) elif browser_name == 'Ie': cls._driver = webdriver.Ie(driverPath["Ie"]) cls._driver.maximize_window() #窗口最大化 cls._driver.get(DOMAIN) #访问默认的页面 return cls._driver
3:basePage.py basePage 类,我们发现每个页面类都有一些通用的内容,所以抽离出来,形成basePage 类
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from xxx.myDriver import Driver from xxx.mySettings import TIMEOUT, POLL_FREQUENCY class BasePage: def __init__(self): self.driver = Driver.get_driver() #获取浏览器对象
def __del__(self): #del析构方法,类被销毁的时候,自动执行 self.driver.close()
def get_element(self, locator): #根据表达式匹配单个元素,先显示等待某个元素 # 判断元素是否存在 WebDriverWait( driver=self.driver, #传入浏览器对象 timeout=TIMEOUT, #传入超时时间 poll_frequency=POLL_FREQUENCY).until( #设置轮询时间 EC.visibility_of_element_located(locator)) #显示等待检查定位元素是否可见,元素没有出现就一直等待,元素出现就执行下面的return return self.driver.find_element(*locator) # 返回元素对象, 元组传参需要解包
def get_elements(self, locator): #根据表达式匹配元素列表
#根据表达式匹配元素列表,显示等待元素的出现 WebDriverWait( driver=self.driver, timeout=TIMEOUT, poll_frequency=POLL_FREQUENCY).until( EC.visibility_of_element_located(locator)) return self.driver.find_elements(*locator) #返回元素列表, 元组传参
BasePage基础类里面还可以封装各种页面的通用方法,如显示等待,iframe的跳出和进入
多标签页的跳转,获取元素的文本值,可以封装各种对元素的操作,
4:loginPage.py
from xxx.basePage import BasePage from selenium.webdriver.common.by import By class LoginPage(BasePage): #页面元素类 def __init__(self): """进一步抽离元素定位方法,这里只封装寻找元素的方法, 不会真的去找元素""" super().__init__() #执行父类的构造方法 self.username_input_locator = (By.NAME, "username") # 用户名输入框 self.password_input_locator = (By.NAME, "password") #密码输入框 self.login_button_locator = (By.TAG_NAME, "button") #登录按钮 def username_input_box(self): return self.get_element(self.username_input_locator) #用户名输入框 def password_input_box(self): #密码输入框 return self.get_element(self.password_input_locator) def login_button_box(self): #登录按钮 return self.get_element(self.login_button_locator)
class LoginPageAction(LoginPage): #抽离出页面动作类,基础页面元素类 def login(self): #访问 opms 登录界面, 登录用户 self.username_input_box().send_keys("libai") #输入用户名 self.password_input_box().send_keys("opmsopms123") #输入密码 self.login_button_box().click() #点击登录按钮
5:myHomePage.py
from xxx.basePage import BasePage from selenium.webdriver.common.by import By import time class MyHomePage(BasePage): def __init__(self): super().__init__() self.myName = (By.CSS_SELECTOR, "body > section > div.main-content > div.wrapper > div > div.col-md-4 > div >\
div:nth-child(2) > div > div > ul > li:nth-child(1) > div.desk") def toPage(self): time.sleep(3) self.driver.get("http://127.0.0.1:8088/my/manage") def myNameBox(self): return self.driver.find_element(*self.myName) if __name__ == '__main__': mhp = MyHomePage() mhp.toPage() print(mhp.myNameBox().text)
6:解决登录问题:
web 自动化登录问题不必多说,对 driver 类进行扩展,新增一个函数 __login ,解决此问题,其他部分不变,以后写用例,跳过登录
myDriver.py #这里直接使用添加cookie绕过登录,还可以输入用户名密码点击直接登录
from xxx.mySettings import driverPath, DOMAIN from selenium import webdriver import requests class Driver: _driver = None @classmethod def get_driver(cls, browser_name='Chrome'): if cls._driver is None: if browser_name == 'Chrome': cls._driver = webdriver.Chrome(driverPath["Chrome"]) elif browser_name == 'Firefox': cls._driver = webdriver.Firefox(driverPath["Firefox"]) elif browser_name == 'Safari': cls._driver = webdriver.Safari(driverPath["Safari"]) elif browser_name == 'Opera': cls._driver = webdriver.Opera(driverPath["Opera"]) elif browser_name == 'edge': cls._driver = webdriver.Edge(driverPath["Edge"]) elif browser_name == 'Ie': cls._driver = webdriver.Ie(driverPath["Ie"]) cls._driver.maximize_window() cls._driver.get(DOMAIN) cls.__login() cls._driver.refresh() return cls._driver @classmethod def __login(cls): #私有方法, 只能在类里边使用,类外部无法使用, 子类不能继承,解决登录问题 cookieSli = [{'domain': '192.168.43.25', 'httpOnly': False, 'name': 'Hm_lpvt_750463144f16fe69eb3ac11bea1c4436', 'path': '/', 'secure': False, 'value': '1586422813'}, {'domain': '192.168.43.25', # 'expiry': 1617958812, 'httpOnly': False, 'name': 'Hm_lvt_750463144f16fe69eb3ac11bea1c4436', 'path': '/', 'secure': False, 'value': '1586422813'}, {'domain': '192.168.43.25', # 'expiry': 1617958812.079112, 'httpOnly': True, 'name': 'beegosessionID', 'path': '/', 'secure': False, 'value': 'a6348d7df889f00967c9a835fd3f79d0'}] cls._driver.delete_all_cookies() # 清除所有cookie for cookie in cookieSli: cls._driver.add_cookie(cookie) # 添加 cookie
八:po模式集合pytest+allure报告成型代码
mySettings.py 配置文件
# 网址 domain = "http://127.0.0.1:8088" # 超时时间 timeout = 10 # 轮询时间 pollFrequency = 0.5 # driver 路径 driverPath = { "Chrome": "D:\\tool\selenium\chromedriver.exe", "Firefox": "D:\\tool\selenium\gcdriver.exe" }
myDriver.py 开始我们要把driver类写好,driver类主要完成以下工作
1:实例化driver对象 2:最大化窗口 3:打开浏览器跳转到登录页面 4:完成登录操作
from study.seleniumStu.day7.utils.mySettings import driverPath, domain from selenium import webdriver class Driver: """浏览器驱动工具类""" driver = None @classmethod def getDriver(cls, browser_name="Chrome"): """ 获取浏览器驱动对象 :param browser_name: :return: """ # 如果为空,则需要创建 if cls.driver is None: if browser_name == "Chrome": cls.driver = webdriver.Chrome(driverPath[browser_name]) elif browser_name == "Firefox": cls.driver = webdriver.Firefox(driverPath[browser_name]) # ...... 省略其他的浏览器类型 else: raise ("找不到浏览器,请检查配置或检查传参") # 最大化窗口 cls.driver.maximize_window() # 访问网址 cls.driver.get(domain) # 执行登录 cls.__login() return cls.driver @classmethod def __login(cls): cls.driver.find_element_by_name("username").send_keys("libai") cls.driver.find_element_by_name("password").send_keys("opmsopms123") cls.driver.find_element_by_css_selector("button").click() if __name__ == '__main__': Driver.getDriver()
basePage.py 基类页面,把所有页面的操作抽离到这个类里完成
from study.seleniumStu.day7.utils.mySettings import timeout, pollFrequency, domain from study.seleniumStu.day7.utils.myDriver import Driver from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait class BasePage: def __init__(self): # 获取浏览器对象 self.driver = Driver.getDriver() self.url = domain # def __del__(self): # """ # 类被销毁的时候,自动执行 # :return: # """ # self.driver.close() def getElement(self, locator): """ 根据表达式匹配单个元素 :param locator: :return: """ # 判断元素是否存在 WebDriverWait( # 传入浏览器对象 driver=self.driver, # 传入超时时间 timeout=timeout, # 设置轮询时间 poll_frequency=pollFrequency).until( # 检查元素是否可见 EC.visibility_of_element_located(locator) ) # 返回元素对象,元组传参 return self.driver.find_element(*locator) def getElements(self, locator): """ 根据表达式匹配元素列表 :param locator: :return: """ # 判断元素是否存在 WebDriverWait( # 传入浏览器对象 driver=self.driver, # 传入超时时间 timeout=timeout, # 设置轮询时间 poll_frequency=pollFrequency).until( # 检查元素是否可见 EC.visibility_of_element_located(locator) ) # 返回元素列表,元组传参 return self.driver.find_elements(*locator)
ProjectManagerPage.py 项目管理页面,页面类,页面类基础基类里面的方法
from xxx.basePage import BasePage from selenium.webdriver.common.by import By import time class ProjectManagerPage(BasePage): def __init__(self, path="/project/manage"): """ 项目管理页面,页面类 """ # super(ProjectManager, self).__init__() super().__init__() self.path = self.url + path # 以下封装页面元素寻找方法 # 项目状态搜索选择下拉框 self.project_status_locator = (By.NAME, "status") # 项目名称搜索输入框 self.project_name_input_locator = (By.CSS_SELECTOR, "form > input") # 搜索按钮 self.search_button_locator = (By.CSS_SELECTOR, "form > button.btn-primary") # 新建项目按钮 self.create_project_buuton_locator = (By.CSS_SELECTOR, "a.btn.btn-success") # 匹配列表当中的每一个项目名称 self.list_of_project_name_locator = (By.CSS_SELECTOR, "tbody > tr > td:nth-child(1)") # 匹配列表当中的每一个项目别名 self.list_of_project_another_name_locator = (By.CSS_SELECTOR, "tbody > tr > td:nth-child(2)") def project_name_input_box(self): return self.getElement(self.project_name_input_locator) def search_button_box(self): """函数返回搜索按钮""" return self.getElement(self.search_button_locator) def list_of_project_name_boxes(self): """匹配列表当中的每一个项目名称, 返回元素列表""" return self.getElements(self.list_of_project_name_locator) def list_of_project_another_name_boxes(self): return self.getElements(self.list_of_project_another_name_locator) def to_page(self): """ 访问此页面的网址 :return: """ time.sleep(3) self.driver.get(self.path) class ProjectManagerPageAcction(ProjectManagerPage): pass # 创建对象实例,其他模块引用此对象,可保持对象在内存中只有一个 #单例模式 ProjectManagerPageAcctionObj = ProjectManagerPageAcction()
attendanceManagementPage.py 考勤管理页面类
from xxx.basePage import BasePage from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select import time class AttendanceManagementPage(BasePage): def __init__(self, path="/checkwork/manage"): """ 考勤管理页面类 :param path: 页面网址 """ super().__init__() self.path = self.url + path # 以下封装页面元素寻找方法 # 打卡按钮 self.sign_button_locator = (By.CSS_SELECTOR, "#js-clock > span") # 打卡状态下拉框 self.sign_status_select_locator = (By.CSS_SELECTOR, "form > select[class=\"form-control\"]") # 打卡状态搜索按钮 self.sign_status_search_button_locator = (By.CSS_SELECTOR, "form > select[class=\"form-control\"]+button") # 考勤表 self.sign_table_locator = (By.TAG_NAME, "table") # 匹配考勤表的每一行考勤 self.sign_table_tr_locator = (By.CSS_SELECTOR, "tbody > tr") # 匹配考勤表的每一个日期 self.sign_table_date_locator = (By.CSS_SELECTOR, "tbody td:nth-child(1)") # 匹配考勤表的每一个打卡 self.sign_table_time_locator = (By.CSS_SELECTOR, "tbody td:nth-child(2)") # 匹配考勤表的每一个状态 self.sign_table_status_locator = (By.CSS_SELECTOR, "tbody td:nth-child(3)") # 匹配考勤表的每一个ip self.sign_table_ip_locator = (By.CSS_SELECTOR, "tbody td:nth-child(4)") def to_page(self): """访问此页面网址""" time.sleep(3) self.driver.get(self.path) def sign_button_box(self): """打卡按钮""" return self.getElement(self.sign_button_locator) def sign_status_select_box(self): """打卡状态下拉框""" return self.getElement(self.sign_status_select_locator) def sign_status_search_button_box(self): """打卡状态搜索按钮""" return self.getElement(self.sign_status_search_button_locator) def sign_table_box(self): """考勤表""" return self.getElement(self.sign_table_locator) def sign_table_tr_boxes(self): """匹配考勤表的每一行考勤""" return self.getElements(self.sign_table_tr_locator) def sign_table_date_boxes(self): """匹配考勤表的每一个日期""" return self.getElements(self.sign_table_date_locator) def sign_table_time_boxes(self): """匹配考勤表的每一个打卡""" return self.getElements(self.sign_table_time_locator) def sign_table_status_boxes(self): """匹配考勤表的每一个状态""" return self.getElements(self.sign_table_status_locator) def sign_table_ip_boxes(self): """匹配考勤表的每一个ip""" return self.getElements(self.sign_table_ip_locator) class AttendanceManagementPageAction(AttendanceManagementPage): def punchClock(self): """ 点击打卡按钮 :return: """ self.sign_button_box().click() def sign_status_search(self, status): """ 按打卡状态搜索考勤 :param status: 打卡状态,按此参数传入的值进行搜索 :return: """ # 根据可视文本选择下拉选项 Select(self.sign_status_select_box()).select_by_visible_text(status) # 点击搜索按钮 self.sign_status_search_button_box().click() # 创建对象实例,其他模块引用此对象,单例模式,保持对象在内存当中只有一个 AttendanceManagementPageActionObj = AttendanceManagementPageAction() if __name__ == '__main__': AttendanceManagementPageActionObj.to_page() AttendanceManagementPageActionObj.sign_status_search("早退")
attendanceManagementPageCase_test.py 考勤管理页面的测试用例,page页面类写好了后现在写testcase测试用例了
from xxx.attendanceManagementPage import AttendanceManagementPageActionObj as AMPA import pytest class TestAttendanceManagementPageCase: def testPunchClock(self): """ 测试打卡功能,打卡成功后,考情表会多出来一条记录 :return: """ AMPA.to_page() """1 获取打卡之前的考勤表考勤数量""" beforeNum = len(AMPA.sign_table_tr_boxes()) """2 点击打卡按钮""" AMPA.punchClock() """3 重新加载考勤表""" AMPA.driver.refresh() """4 """ afterNum = len(AMPA.sign_table_tr_boxes()) assert afterNum - 1 == beforeNum if __name__ == '__main__': pytest.main()
ProjectManagerPageCase_test.py 项目管理页面测试类
from study.seleniumStu.day7.pages.projectManagerPage import ProjectManagerPageAcctionObj as PPAO import pytest class TestProjectManagerPageCase: def test_indistinct_search(self): """ 模糊查询测试用例,当我搜索一个项目的时候 搜索出来的列表,别名或项目名称中至少有一个包含我搜索的文本 :return: """ PPAO.to_page() """1 选定文本,输入并搜索""" project_name = "fs" PPAO.project_name_input_box().send_keys(project_name) PPAO.search_button_box().click() """2 获取项目名称列表,获取项目别名列表""" # 获取项目名称列表 获取的是元素对象的列表 project_name_list = PPAO.list_of_project_name_boxes() # 获取项目别名列表 获取的是元素对象的列表 project_another_name_list = PPAO.list_of_project_another_name_boxes() """断言验证,搜索出来的列表,别名与项目名称中至少有一个包含我需要的文本""" for projectName in project_name_list: as1 = project_name in projectName.text as2 = project_name in project_another_name_list[project_name_list.index(projectName)].text assert as1 or as2 if __name__ == '__main__': pytest.main()
48:js进行元素定位
document代表文档,整个页面都会被选中
document.querySelector('input[name="started"]') js定位到这个元素,元素定位表达式和css语法一样
document.querySelector('input[name="started"]').value="123" js给元素设置值
49:三种方法输入时间框代码
方案一:直接输入开始日期和结束日期,开始日期元素和结束日期元素是一个input类型的标签,先clear清除文本再输入新的文本
startEle=driver.find_element_by_css_selector('input[name="started"]') #开始日期标签元素 startEle.clear() startEle.send_keys('2020-10-30') endEle=driver.find_element_by_css_selector('input[name="ended"]') #结束日期标签元素 endEle.clear() endEle.send_keys("020-10-31")
方案二:点击一下输入日期文本框然后模拟键盘事件输入退格删除内容,再重新输入
点击后光标聚集在输入日期框,然后全选,全选后删除,删除后重输
#开始日期 startEle=driver.find_element_by_css_selector('input[name="started"]') startEle.click() startEle.send_keys(Keys.CONTROL,"a") #ctrl a startEle.send_keys(Keys.BACK_SPACE) #输入删除键 startEle.send_keys('2020-10-30') #结束日期 endEle=driver.find_element_by_css_selector('input[name="ended"]') endEle.click() endEle.send_keys(Keys.CONTROL,"a") #ctrl a endEle.send_keys(Keys.BACK_SPACE) #输入删除键 endEle.send_keys("2020-10-31")
方案三:有的时间控件不允许send_keys输入的,需要使用其他技术(js大法)
会css表达式就会写js语法,开始日期css表达式:input[name="started"]
driver.execute_script('document.querySelector(\'input[name="started"]\').value="2020-10-30" ') #开始日期 driver.execute_script('document.querySelector(\'input[name="ended"]\').value="2020-10-31" ') #结束日期
selenium自动化对某些框的输入操作:
如果是可输入的文本框使用:1.2种方法,如果不可输入的文本框不能sen_keys那么使用方法三js
50:自动化ui的意义:核心必要操作的功能自动化,冒烟测试,ui自动化失败那么冒烟gg
ui自动化:
1:冒烟测试
2:只做核心业务的正向测试用例,不做逆向测试用例,依旧可以一定程度上节省人工成本的,
异常场景一般不考虑的,异常场景如果都考虑那么ui自动化的成本将大于手工测试的成本,还不如点点点
只做冒烟测试和核心功能的正向场景测试,ui自动化很实用,节省人工成本
数据正确与否不在ui自动化上面做测试,ui自动化不管数据正确与否的,验证结果正确与否一般在接口里面去做
也可以ui自动化定位到列表页面获取列表元素元素文本值,进行比较
一般不建议在ui自动化中做任何有关数据正确性与否的测试,
断言可以断言页面元素操作是否成功,但是不去管数据的对错
51:框架,库 库libirary和框架framework是一个有别于软件的东西
库: 代码集合起来供我们调用,开发为了具体解决某一类问题,功能也不是完整的
框架:是升级版的库,框架是为解决一类问题而开发的产品。如selenium框架:为了解决webui自动化
ui自动化,webui自动化是一个真实存在的需求,是一个问题---selenium是为了解决这些问题而开发出来的
框架是为解决一类问题而被开发的产品,更加复杂的库
time模块 os模块并不是为了解决某个具体的问题,功能也不是很完整
selenium解决一些列问题,而time模块为了专注解决某些问题,点,面问题,
并非为了解决某一具体问题,而框架就是为了解决某一类问题而开发的产品
框架的用户一般只需要提供使用框架的类和函数就行,如selenium使用selenium导出来的各种类
函数,类和函数结合实现web ui自动化需求,实现整体的功能需求(webui自动化)
框架是库的升级版,开发者使用框架的时候,必须使用框架的全部代码--selenium整个都安装过来了,库只使用一部分
比如说:我想买一条电脑,我需要写代码,电脑就是需求
框架:为我们提供组装好的完整的电脑,会导致很多人用一模一样的电脑(selenium都用同一个)
库:让你自己去组装电脑,零件提供好,库的零件不是很完整,还需要自己写零件出来
自己创造零件,所有库的使用很灵活,一个个零件
框架必须完整的使用他的框架,按照他的规则去实现特点的功能,框架比较方便,
自己做的事情变少了,所以的操作必须受到框架的牵制,规定怎么做
52:搭建自动化框架:框架模板如下
DemoUI-test---项目根目录
config ---配置文件目录
settings.py ---项目配置文件
env.py ----环境配置(不同环境不同测试策略和方法,很少用)
logs ----存放日志文件的目录
tools -----公共方法库,可以二次分类,越来越臃肿
excel.py----excel表的操作,excel的读取和写入
md5.py----加密模块
logs.py ---封装日志工具类
newReport.py --封装测试报告工具类
sendMail.py ---封装邮件工具类.测试报告生成后自动给邮件组发送邮件,抄送
lib --存放封装的接口的或者ui的源代码
basepage.py --基础页面工具类
login.py --登录工具类,解决登录问题
setup.py ---一些前置工作集合
report ----存放测试报告,每次生成的测试报告在此路径下维护
testcase ---测试用例目录
冒烟测试用例集
核心测试用例集合
testData ---测试用例数据维护在这里,做到测试数据和测试用例分离,使用的时候testcase里面去导入testData里面的数据
框架就是把每个模块分个类,页面放页面里面,配置放配置里面,--分类,分层(分类过程中不断冒出新的想法,不断补充)
这就是做项目,结构更加清晰,可读性上升,页面和页面在一起,配置和配置在一起,测试用例和测试用例放一起
53 :Selenium Grid使用 详细文档参考:https://blog.csdn.net/lb245557472/article/details/91966770
什么是Selenium Grid: Selenium Grid是Selenium套件的一部分,它专门用于并行运行多个测试用例在不同的浏览器、操作系统和机器上
Selenium Grid 主要使用 master-slaves (or hub-nodes) 理念 -
一个 master/hub 和多个 基于master/hub注册的子节点 slaves/nodes。
当我们在master上基于不同的浏览器/系统运行测试用例时,master将会分发给适当的node运行
什么时候用Selenium Grid
同时在不同的浏览器、操作系统和机器上运行测试。最大程度用于兼容性测试
减少运行时间
启动Selenium Grid?
一台运行hub(H) 多台运行node(N)
Step 1:
配置java环境:安装jdk
已安装需要运行的浏览器
下载浏览器驱动,放到和selenium server相同的路径下(查看),
否则在启动node时要加参数,不然启动不了浏览器
(java -Dwebdriver.chrome.driver="C:\your path\chromedriver.exe" -jar selenium-server-standalone-3.141.59.jar
-role node -hub http://192.168.1.100:5566/grid/register/,可切换浏览器)
下载selenium server,将 selenium-server-standalone-X.XX.jar 分别放在“Machine H”和“Machine N”上(自定义路径)
Step 2:
在机器“hub”上打开命令行,到selenium server所在的路径,
运行:java -jar selenium-server-standalone-3.141.59.jar -role hub -port 5566
hub机器输入:http://localhost:5566/grid/console 进入控制页面
在机器“node”上打开命令行,到selenium server所在的路径,
运行:java -jar selenium-server-standalone-3.141.59.jar -role node -hub http://192.168.1.100:5566/grid/register/ -port 5577
输入的ip是hub机器的ip,node机器连接上hub机器
node连接上hub后:
http://localhost:5566/grid/console 进入控制页面就能看到node连接上来的机器了
step 3:
运行测试脚本,将会看到在机器“Machine N”上打开了Chrome浏览器,并运行了测试用例:
from selenium import webdriver ds = {'platform': 'ANY', 'browserName': "chrome", 'version': '', 'javascriptEnabled': True } dr = webdriver.Remote('http://192.168.1.101:5577/wd/hub', desired_capabilities=ds) dr.get("https://www.baidu.com") print dr.name