一、什么是webUI自动化
通过自动化工具--selenium,按照测试人员的设想--代码逻辑,自动执行测试,简单来说,就是用代码模仿手工操作
webUI自动化的价值:
1、验证逻辑
2、代替大量的重复手工测试
3、ui自动化目标不是发现多少个bug,而是为手工回归测试节省了多少时间
selenium 与 webdriver
selenium 是一个用于web测试的工具,运行在浏览器中,就想真正的用户一样,支持主流的浏览器
webdriver封装了一套操作浏览器的api,因为python代码不能直接操作浏览器
测试脚本,浏览器驱动,浏览器
HTML,也可以叫做页面标签,也会叫做元素
HTML以开始标签起始,以结束标签终止,但有一些例外
部分HTML元素具有空内容
以开始标签的结束而结束
元素的内容是开始容标签与结束标签之间的内
大多数HTML元素有属性
属性一般描述于开始标签
属性相当于给标签添加了描述信息
属性通常以键值对形式出现
"""
ui 自动化主要包含两部分,一是选择界面元素,二是操作界面元素
根据元素的特征选择界面元素:
id属性 通常是唯一的,也有不唯一的时候(随机id,重复id)
随机id和重复id,不可以使用id进行定位
否则,id是第一选择
name属性
Class
tag_name
根据元素的关系 css、xpath
操作界面元素
输入操作:点击、输入、拖拽
输出操作:
定位元素的步骤
1、找到元素对应的标签
2、观察元素是否有可利用的属性
3、若无法通过属性定位,则增加关系进行辅助定位
元素定位八种方法
1、id属性 -- 常用
2、name属性 -- 常用
3、class属性
4、tag_name标签名称
5、link_text链接文本
6、partial_link_text链接文本模糊定位
7、css表达式 -- 常用
8、xpath表达式 -- 常用
实例:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://127.0.0.1:8088/login")
# 元素定位的第一种方法,根据id定位
a = driver.find_element_by_id("login-form")
# 元素定位的第二种方法,根据 name 属性进行定位
username_input_box = driver.find_element_by_name("username")
username_input_box.send_keys("libai")
# 元素定位的第三种方法,根据class属性进行定位
# 根据 classname进行定位的时候,有时候会遇到复合类,也就是class属性中间有空格
# class 属性比较特殊,空格代表间隔符号,表示这个元素的class属性有多个值
# 任取其中一个值就可以定位(但是不保证唯一
driver.find_element_by_class_name("xdafsdf")
# 元素定位的第四种方法, 根据标签名称进行定位
driver.find_element_by_tag_name("p")
# 元素定位的第五种方法,根据链接文本进行定位
driver.find_element_by_link_text("超链接显示在外的文本")
# 元素定位的第六种方法,根据链接文本模糊定位
driver.find_element_by_partial_link_text("超链接显示在外的文本,一部分即可,但是不能错")
# 元素定位的第七种方法,根据 css表达式进行定位
driver.find_element_by_css_selector(
"body > section > div.left-side.sticky-left-side > div.left-side-inner > ul > li:nth-child(1) > a > span")
# 元素定位的第八种方法
driver.find_element_by_xpath("/html/body/section/div[1]/div[3]/ul/li[1]/a/i")
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://127.0.0.1:8088/login")
# driver.find_element_by_class_name("form-control").send_keys("123")
"""
find_element,会返回找到的第一个元素,如果根据表达式找不到元素,就报错
find_elements,会寻找页面上所有满足表达式的元素,将所有能找到的元素存在一个列表里,如果一个找不到,那就返回空列表
"""
# 寻找所有class属性为 form-control 的元素,存入列表,列表赋值给变量 eleSli
eleSli = driver.find_elements_by_class_name("form-control")
# 声明了一个列表
txtSli = ["111", "222"]
# 事先知道 eleSli 列表中有两个元素,所以设置循环为两次
for idx in range(2):
# eleSli[idx] 每次循环,根据下标取出 eleSli中的内容 -- 一个元素
# 对这个元素输入文本,文本内容来源于 txtSli,取相同的下标
eleSli[idx].send_keys(txtSli[idx])
一、设置元素等待
大多数 Web 应用程序都是使用 Ajax 和 Javascript 开发的。当浏览器加载页面时,
我们想要与之交互的元素可能尚未被加载出来。此刻不仅难以识别元素,而且很容
易定位不到元素,抛出异常。使用 Waits,我们可以解决此问题
WebDriver 提供了两种类型的等待:显式等待和隐式等待
l
隐式等待: 设置一个超时时间,在这个时间内不断寻找这个元素,超时找不到就会抛出异
常
l
显示等待: 设置一个超时时间和一个元素查找条件,在这个时间内不断寻找这个元素,超
时找不到就会抛出异常
# 隐式等待
driver.implicitly_wait(10)
优点:
l
使用简单, 一次添加终生有效
l
一般在创建 driver 之后设置一个隐式等待(创建 driver 之后设置,目的是让
driver 的整个生命周期都有隐式等待的逻辑)
l
设置后的元素查找全都会进入隐式等待的逻辑
缺点:
l
无法对指定的元素进行等待
l
需要等页面全部加载完成, 相对浪费时间, 降低脚本执行速度显示等待
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 每隔 0.5 秒检查一次,最多等待 10 秒
ele = WebDriverWait(driver, 10, 0.5).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR, "元素定位表达式")
)
)
ele.click()
优点:
不需要等待页面全部加载完成, 相对节省时间
可以指定需要等待的元素, 对一些加载缓慢的元素, 可以为其增加等待时间
缺点:
使用相对复杂, 代码量多
每次等待都需要再写一遍代码
我们可以将显示等待封装为一个函数, 以后需要的时候就调用函数, 可以省去很多代码量
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def webElementWait(driver, timeout, lo_time, by_locate, locate):
"""
:param driver: 浏览器驱动对象
:param timeout: 最大等待时间
:param lo_time: 轮询时间
:param by_locate: 元素定位方法
:param locate: 元素定位表达式
:return:
"""
# 每隔 0.5 秒检查一次,最多等待 10 秒
ele = WebDriverWait(driver, timeout, lo_time).until(
# 这里传入我们的期望条件
EC.visibility_of_element_located( (by_locate, locate)
)
)
return ele
可将显示等待与隐式等待结合使用
l
申明一个隐式等待, 全局通用
l
对一些加载特别缓慢的元素,设置显示等待, 适当为其增加等待时间
l
当显示等待与隐式等待一同出现的时候, 取时间更多的一个
二、webdriver 常用方法
我们定位元素之后要对这个元素进行操作, 或单击(按钮、链接等) 或输入(输入框) ,
下面就来认识 WebDriver 中最常用的几个方法:
clear(): 清除文本。
send_keys (value): 模拟按键输入(被操作元素需要是个文本输入框)。
click(): 单击元素。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element_by_id("kw").send_keys("selenium") # 输入内容
driver.find_element_by_id("kw").clear() # 清除内容
driver.find_element_by_id("kw").send_keys("python") # 输入内容
driver.find_element_by_id("su").click() # 点击内容
driver.quit()
其他方法
size: 返回元素的尺寸。text: 获取元素的文本。
get_attribute(name): 获得属性值。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
# 获得输入框的尺寸
size = driver.find_element_by_id('kw').size
print(size)
# 返回百度页面底部备案信息
text = driver.find_element_by_id("cp").text
print(text)
# 返回元素的属性值, 可以是 id、 name、 type 或其他任意属性
attribute = driver.find_element_by_id("kw").get_attribute('type')
print(attribute)
driver.quit()
我们也可以获取一些页面上的信息
l
title:用于获得当前页面的标题
l
current_url:用户获得当前页面的 URL
l
text:获取标签对之间的文本信息
注意:
l
标签元素如果不展示在页面上,获取结果为空
l
标签对中间没有值,获取结果为空
l
如 input 标签之类的标签,获取结果为空
l
get_attribute 方法,获取元素某个属性的值
l
ele.get_attribute('href')
l
上述代码获取 ele 这个元素的 href 属性的值
三、控制浏览器操作
1、控制浏览器大小
WebDriver 提供了 set_window_size()方法来设置浏览器的大小
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
# 参数数字为像素点
print("设置浏览器宽 600、高 600 显示")
driver.set_window_size(600, 600)
driver.quit()
在 PC 端执行自动化测试脚本大多的情况下是希望浏览器在全屏幕模式下执行,那么可以使用
maximize_window()方法使打开的浏览器全屏显示
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
# 设置全屏显示
driver.maximize_window()
# # 设置最小化浏览器,基本没啥用
# driver.minimize_window()
driver.quit()
控制浏览器前进、后退、刷新
from selenium import webdriver
driver = webdriver.Firefox()# 访问百度首页
driver.get("http://www.baidu.com")
# 访问新闻页面
driver.get("http://news.baidu.com")
# 返回(后退)到百度首页
driver.back()
# 前进到新闻页
driver.forward()
# 刷新界面
driver.refresh()
driver.quit()
四、xpath 高级语法
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常
规的电脑文件系统中看到的表达式非常相似。其标准语法如下:
Xpath=//tagname[@attribute='value']
l
// : 选择当前节点
l
Tagname: 节点标签名
l
@: 选择属性
l
Attribute: 节点属性名
l
Value: 属性值
xpath 这种定位方式, webdriver 会将整个页面的所有元素进行扫描以定位我们所需要的元
素, 这是个非常费时的操作, 如果脚本中大量使用 xpath 做元素定位的话, 脚本的执行速
度会明显变慢。XPath 有绝对定位和相对定位两种,绝对定位使用绝对路径,缺点是路径太长,只要一个节点
变动就无法定位。以单斜杠(/)开始,表示从根节点开始选择元素。
相对路径以双斜杠(//)开始,可以从 HTML 文档的任何位置开始。
xpath id 定位:
# 选择 id 为 abc 的 span 元素
//span[@id="abc"]
class 定位
# 选择 class 为 abc 的 span 元素
//span[@class="abc"]
其他属性值定位
# 在没有 id 和 class 属性的前提下,可以使用其他属性定位
# 选择 name 为 abc 的 span 元素
//span[@name="abc"]
# 选择 type 为 abc 的 span 元素
//span[@name="type"]
xpath 可以使用 and 或者 or 连接多个属性
//input[@name='login'and @type='submit']
//input[@name='a'or @name='b']
xpath 还可以根据元素文本进行定位,以下示例中的 * 表示通配符,可匹配任意类型的标签
Xpath = //*[text()='text_value']
子元素定位,以 “/” 表示:
# 选择父元素为 div 的所有 a 元素
//div/a
后代元素定位,以 “//” 表示:
# 选择先辈元素为 div 的所有 a 元素
//div//afrom selenium import webdriver
"""
# XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电
脑文件系统中看到的表达式非常相似
缺点:
xpath 这种定位方式, webdriver 会将整个页面的所有元素进行扫描以定位我们所需要的元素,
这是个非常费时的操作, 如果脚本中大量使用 xpath 做元素定位的话, 脚本的执行速度可能
会稍慢
"""
# 创建浏览器驱动对象
driver = webdriver.Chrome("/Users/.../chromedriver")
# 访问网址
driver.get("/Users/...2/xpathStu.html")
# xpath 的绝对路径
driver.find_element_by_xpath("/html/body/div/ul/li[1]/ol/li[2]/ul/li[1]")
# xpath 的相对路径
driver.find_element_by_xpath("//li")
# xpath 以 . 表示当前路径
driver.find_element_by_xpath("/html/body/div/ul/li[1]/ol/li[2]/ul/li[1]/.")
# xpath 以 .. 表示上层路径
driver.find_element_by_xpath("/html/body/div/ul/li[1]/ol/li[2]/ul/li[1]/.")
# xpath 可以用 * 表示任意节点
driver.find_element_by_xpath("/html/body/div/ul/li[1]/ul/li[1]/ul/*")
# 如果需要用属性来筛选元素, 可以用 @
driver.find_element_by_xpath("//li[@id=\"abc\"]")
# 可以不为属性指定值
driver.find_element_by_xpath("//li[@id]")
# 也可以匹配固定值的任意属性
driver.find_element_by_xpath("//li[@*=\"abc\"]")
# 也可以用下标协助定位, 但要记住, 从 1 开始数
driver.find_element_by_xpath("/html/body/div/ul/li[1]/ol/li[2]/ul/li[1]")
# 最后第一个
driver.find_element_by_xpath("/html/body/div/ul/li[1]/ol/li[2]/ul/li[last()]")
# 倒数第二个driver.find_element_by_xpath("/html/body/div/ul/li[1]/ol/li[2]/ul/li[last()-1]")
# 根据元素文本值进行匹配
driver.find_element_by_xpath("//p[text=\"123\"]")
# 注意:当你使用 父元素.find_element_by_xpath 这种方式定位的时候,元素定位表达式必须以点
开头,如
ele = driver.find_elements_by_xpath("//div[@id='category-block']//ol/li")
b = ele.find_elements_by_xpath('.//li[@class=\'subcate-item\']//span')
# 如果不用点,会从整个文档开始找,即使你前边使用了父元素也没有效果,xpath
一、 css 高级语法
1、推荐的定位方式的优先级
l
优先级最高:ID
l
优先级其次:name
l
优先级再次:CSS selector
l
优先级再次:Xpath
针对 css selector 和 xpath 的优先级做一个简单的说明
在项目中我们可能用的最多的是 css 或者 xpath,那么针对这两种,我们优先选择 css,
原因在哪些?
原因 1:css 是配合 html 来工作,是一种匹配模式定位,而 xpath 是配合 xml 工作的,
通过遍历定位,所以两者在设计上,css 性能更优秀
原因 2:语言简洁,明了,相对 xpath
原因 3:前段开发主要是使用 css,不使用 xpath,所以在技术上面,我们可以获得帮助
的机会非常多
但 xpath 也有其优点:
l
Xpath 可以通过元素文本来定位,而 CSS Selector 不能
l
Xpath 可以通过子节点来定位父节点,CSS Selector 不能
2、定位元素的注意事项(划重点)
l
找到待定位元素的唯一属性
l
如果该元素没有唯一属性,则先找到能被唯一定位到的父元素/子元素/相邻元素,再使用
“>”," ","+"等进行辅助定位。
l
不要使用随机唯一属性定位
l
最重要的是多跟研发沟通,尽量把关键元素加上 ID 或者 name,并减少不合理的页面元
素,例如重复 ID 这样的事情最好不要发生。
CSS 规则由两个主要的部分构成:选择器,以及一条或多条声明:
我们做 web 自动化测试,只需关注 选择器部分,帮我们找到元素
3、css 选择器
id 选择器
id 选择器可以选中具有特定 id 的 HTML 元素
CSS 中 id 选择器以 "#" 来定义。
以下的样式规则应用于元素属性 id="para1":
#para1 { text-align:center; color:red; }
class 选择器
class 选择器可以选中具有特定 class 属性的 HTML 元素
在 CSS 中,class 选择器以 "."号 来定义,若有空格,亦可以"."来代替
在以下的例子中,所有拥有 class 属性为 center 的 HTML 元素均为居中
.center {text-align:center;}
标签选择器
标签选择器可以选中同类型的 html 标签元素
在以下的例子中,将所有的 p 标签设置为红色
p. {color:red;}
另外,你可以将标签选择器与属性选择器结合起来使用
以下,会将所有 class 属性为 re 的元素变成红色
p.re {color:red;}
分组选择器
分组选择器可以选中一组 HTML 元素
在 css 中,分组选择器以 “,” 来定义
以下实例,会将所有的 h1 标签、h2 标签、p 标签的内容变成红色
h1,h2,p { color:green; }
属性选择器
属性选择器选择具有特定属性的 HTML 元素
以下实例将所有包含 title 属性的元素变为蓝色
[title] { color:blue; }
你也可以为属性指定值
以下实例将所有 title 属性为 wa 的元素变为蓝色
[title=“wa”]
{ color:blue; }
你也可以为元素指定标签
以下实例将所有 title 属性为 wa 的 p 标签变为蓝色
p[title=“wa”]
{ color:blue; }
组合选择符
在 CSS3 中包含了四种组合方式:
l
后代选择器(以空格分隔)
l
子元素选择器(以大于号分隔)
l
相邻兄弟选择器(以加号分隔)
l
后续兄弟选择器(以小波浪号分隔)
后代选择器
后代选择器用于选取某元素的后代元素,无论层级有多深
以下实例将 div p { background-color:yellow; } 子元素选择器 与后代选择器相比,子元素选择器只能选择其父元素的直接子元素 以下实例选择了 div>p { background-color:yellow; } 相邻兄弟选择器 相邻兄弟选择器可选择紧接在另一元素后的元素,且二者有相同父元素 以下实例选取了所有位于 div+p { background-color:yellow; } 后续兄弟选择器 后续兄弟选择器选取所有指定元素之后的相邻兄弟元素。 以下实例选取了所有 : div~p { background-color:yellow; } 伪类 nth-child(n) 匹配属于其父元素的第 N 个子元素(不必指定标签类型) nth-last-child(n),如字面意思:倒数第几个标签 first-child,第一个标签 last-child,最后一个标签 二、 高级元素操作 1、窗口截图 from selenium import webdriver # 创建浏览器驱动对象 driver = webdriver.Chrome("D:\...\chromedriver") # 访问网址 driver.get("https://www.baidu.com/") # 截屏,截取整个页面 driver.get_screenshot_as_file("./all.png") # 截屏,截取单个元素 ele = driver.find_element_by_id("form") ele.screenshot("./ele.png") driver.quit() 出现 bug 之后,可以截图 from selenium import webdriver import time # 创建浏览器驱动对象 driver = webdriver.Chrome("/Users/。。。/chromedriver") driver.implicitly_wait(3) # 访问网址 driver.get("http://192.168.0.108:8088/login") # 输入用户名 driver.find_element_by_name("username").send_keys("libai") # 输入密码 driver.find_element_by_name("password").send_keys("opmsopms123") # 点击登录按钮 driver.find_element_by_tag_name("button").click() # 点击侧边菜单栏-员工相册 driver.find_element_by_css_selector( "body > section > div.left-side.sticky-left-side > div.left-side-inner > ul > li:nth-child(6) > a").click() # 点击油菜花这张照片 driver.find_element_by_link_text("油菜花").click()# 向下滑动 driver.execute_script("window.scrollBy(0, 900);") # 使用时间戳作为评论的内容 commentStr = "%s" % time.time() # 输入评论 driver.find_element_by_name("comment").send_keys("commentStr") # 提交评论 driver.find_element_by_css_selector("#album-comment-form > div > div > button").click() # 获取最后一条评论 lastComment = driver.find_element_by_xpath("//ul[@class=\"activity-list\"]/li[last()]//span").text # 不一致说明有问题,截图 if commentStr != lastComment: # 截屏,截取单个元素 ele = driver.find_element_by_xpath("//ul[@class=\"activity-list\"]") ele.screenshot("./%s.png" % lastComment) 2、警告框处理 在 WebDriver 中处理 JavaScript 所生成的 alert、confirm 以及 prompt 十分简单,具体做 法是使用 switch_to.alert 方法定位到 alert/confirm/prompt,然后使用 text/accept/dismiss/ send_keys 等方法进行操作 l text:返回 alert/confirm/prompt 中的文字信息 l accept():接受现有警告框 l dismiss():取消现有警告框 l send_keys(“haha”):发送文本至警告框 from selenium import webdriver # 创建浏览器驱动对象 driver = webdriver.Chrome("/Users/。。。/chromedriver") # 访问网址 driver.get("/Users/。。。/test1.html") """# 触发确认框 driver.find_element_by_id("bu1").click() # 获取对话框 al = driver.switch_to.alert al.accept() """ """ # 触发确认框 driver.find_element_by_id("bu2").click() # 获取确认框 al = driver.switch_to.alert # 取消确认框 al.dismiss() """ # 触发提示框 driver.find_element_by_id("bu3").click() # 获取提示框 al = driver.switch_to.alert print(al.text) al.send_keys("今天温度很高") al.accept() 3、鼠标事件 用 selenium 做自动化,有时候会遇到需要模拟鼠标操作才能进行的情况,比如单击、双击、 点击鼠标右键、拖拽等等。而 selenium 给我们提供了一个类来处理这类事件—— ActionChains ActionChains 类提供了鼠标操作的常用方法: l perform(): 执行操作 l context_click(): 右击; l double_click(): 双击; l drag_and_drop(): 拖动; l move_to_element(): 鼠标悬停。 from selenium import webdriver# 引入 ActionChains 类 from selenium.webdriver.common.action_chains import ActionChains driver = webdriver.Chrome() driver.get("https://www.baidu.cn") # 定位到要悬停的元素 above = driver.find_element_by_link_text("设置") # 对定位到的元素执行鼠标悬停操作 ActionChains(driver).move_to_element(above).perform() driver.quit() 4、键盘事件 Keys()类提供了键盘上几乎所有按键的方法。 前面了解到, send_keys()方法可以用来模拟 键盘输入, 除此 之外, 我们还可以用它来输入键盘上的按键, 甚至是组合键, 如 Ctrl+A、 Ctrl+C 等 from selenium import webdriver # 引入 Keys 模块 from selenium.webdriver.common.keys import Keys driver = webdriver.Chrome() driver.get("http://www.baidu.com") # 输入框输入内容 driver.find_element_by_id("kw").send_keys("seleniumm") # 删除多输入的一个 m driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE) # 输入空格键+“教程” driver.find_element_by_id("kw").send_keys(Keys.SPACE) driver.find_element_by_id("kw").send_keys("教程") # ctrl+a 全选输入框内容 driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'a') # ctrl+x 剪切输入框内容driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'x') # ctrl+v 粘贴内容到输入框 driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'v') # 通过回车键来代替单击操作 driver.find_element_by_id("su").send_keys(Keys.ENTER) driver.quit() 以下为常用的键盘操作: l send_keys(Keys.BACK_SPACE) 删除键(BackSpace) l send_keys(Keys.SPACE) 空格键(Space) l send_keys(Keys.TAB) 制表键(Tab) l send_keys(Keys.ESCAPE) 回退键(Esc) l send_keys(Keys.ENTER) 回车键(Enter) l send_keys(Keys.CONTROL,'a') 全选(Ctrl+A) l send_keys(Keys.CONTROL,'c') 复制(Ctrl+C) l send_keys(Keys.CONTROL,'x') 剪切(Ctrl+X) l send_keys(Keys.CONTROL,'v') 粘贴(Ctrl+V) l send_keys(Keys.F1) 键盘 F1 l …… l send_keys(Keys.F12) 键盘 F12 5、下拉框选择 WebDriver 提供了 Select 类来处理下拉框。注意,只有当下拉框是 select 标签的时候使用 from selenium import webdriver from selenium.webdriver.support.select import Select import time # 实例化一个浏览器对象 driver = webdriver.Chrome("D:\\tool\...\chromedriver")# 访问网址 driver.get("D:\\test\...\selectStu.html") # 定位到下拉框元素 ele = driver.find_element_by_id("bc288089-c52d-497b-aa4d-71f81b24faa3") # # 根据 value 属性选择 # Select(ele).select_by_value("3") # 根据下拉框文本选择 Select(ele).select_by_visible_text("3333333") time.sleep(3) 一、高级浏览器操作 1、文件上传 对于通过 input 标签实现的上传功能,可以将其看作是一个输入框,即通过 send_keys()指定 本地文件路径的方式实现文件上传 对于非 input 标签实现的上传功能,我们可以通过模拟键盘敲击的方式实现,代码如下 # 这个是 windows 版本的 from selenium import webdriver import win32com.client import time driver = webdriver.Chrome() driver.implicitly_wait(5) driver.get("https://tinypng.com/") # 触发上传文件的操作 driver.find_element_by_css_selector("#top .icon").click() sh = win32com.client.Dispatch("WScript.shell") time.sleep(3) sh.Sendkeys("D:\\test\script\study\seleniumStu\day6\qqjt.png\n") # 无目标的,单纯的敲击键盘 driver.quit() # 这个是 mac 版本的 from selenium.webdriver.common.action_chains import ActionChains from selenium import webdriver from pykeyboard import PyKeyboard from pymouse import PyMouse import pyperclip import time # 安装命令 pip3 install PyUserInput# 创建浏览器驱动对象 driver = webdriver.Chrome() driver.implicitly_wait(3) # 访问网址 driver.get("http://192.168.0.108:8088/login") # 输入用户名 driver.find_element_by_name("username").send_keys("libai") # 输入密码 driver.find_element_by_name("password").send_keys("opmsopms123") # 点击登录按钮 driver.find_element_by_tag_name("button").click() # 点击员工相册 driver.find_element_by_css_selector("ul.js-left-nav > li:nth-child(6)").click() # 点击上传照片 driver.find_element_by_css_selector(".pull-right > a:nth-child(3)").click() # 点击请选择, 触发上传文件的操作 ActionChains(driver).click(driver.find_element_by_id("albumUpload")).perform() time.sleep(3) def upload_file(file): # 创建鼠标对象 k = PyKeyboard() # 创建键盘对象 m = PyMouse() filepath = "/" # 模拟快捷键 Command+Shift+G k.press_keys(["Command", "Shift", "G"]) # 输入文件路径 x_dim, y_dim = m.screen_size() m.click(x_dim // 2, y_dim // 2, 1) # 复制文件路径开头的斜杠/ pyperclip.copy(filepath) # 粘贴斜杠/ k.press_keys(["Command", "V"]) time.sleep(2) # 输入文件全路径进去 k.type_string(file) k.press_key("Return") time.sleep(2) k.press_key("Return") time.sleep(2)upload_file("/Users/。。。/ele.png") 2、cookie 操作(课后阅读,不作要求) WebDriver 提供了操作 Cookie 的相关方法,可以读取、添加和删除 cookie 信息 WebDriver 操作 cookie 的方法: l get_cookies(): 获得所有 cookie 信息 l get_cookie(name): 返回字典的 key 为“name”的 cookie 信息 l add_cookie(cookie_dict) : 添加 cookie。“cookie_dict”指字典对象,必须有 name 和 value 值 l delete_cookie(name,optionsString):删除 cookie 信息。“name”是要删除的 cookie 的名称,“optionsString”是该 cookie 的选项,目前支持的选项包括“路径”,“域” l delete_all_cookies(): 删除所有 cookie 信息 from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.youdao.com") # 获得 cookie 信息 cookie = driver.get_cookies() # 将获得 cookie 的信息打印 print(cookie) driver.quit() 可以尝试一下,获取 opms 登陆以后的 cookies 信息,注意是登陆以后的 from selenium import webdriver # 这是之前获取到的 cookies, 记得把每一个字典的 expiry 注释# 用你刚才获取到的 opms 登陆后的 cookie 替换 cookies = [{'domain': '.baidu.com', 'httpOnly': False, 'name': 'H_PS_PSSID', 'path': '/', 'secure': False, 'value': '1431_21125_30839_31186_30909_31228_30823_31086_31164_22158'}, {'domain': '.baidu.com', # 'expiry': 1845350230.981488, 这个时间由服务端规定,不能插入 'httpOnly': True, 'name': 'BDUSS', 'path': '/', 'secure': False, 'value': 'jlhWTBAAAAAAAAF67il5eu4peR'}] # 是个列表,列表里是一个个的字典 driver = webdriver.Chrome() driver.implicitly_wait(10) driver.get("opms 的网址") # 清除所有 cookie driver.delete_all_cookies() for cookie in cookies: # 添加 cookie driver.add_cookie(cookie) driver.refresh() 3、iframe 切换 iframe,又叫浮动帧标记,是内嵌的网页元素,可以将一个 html 文件嵌入到另一个 html 文 件中显示 对 iframe 进行操作,需要用到以下三种方法: l switch_to_iframe() 切换到 iframe 上(未来会被删除,不建议使用) l switch_to.frame() 切换到 iframe 上 l switch_to.default_content() 切换回原主页面通过如下方式进行切换操作 #定位到 iframe iframe=driver.find_element_by_id("x-URS-iframe") #切换到 iframe driver.switch_to_frame(iframe) from selenium import webdriver import time driver = webdriver.Chrome("D:\。。。\chromedriver") # 隐式等待 driver.implicitly_wait(5) driver.get("D:\\test\。。。\test.html") """ 对 iframe 进行操作,需要用到以下三种方法: switch_to_iframe() 切换到 iframe 上 switch_to.frame() 切换到 iframe 上 switch_to.default_content() 切换回原主页面 """ iframe = driver.find_element_by_id("f91fb5bc-bd95-45bf-bd77-977a2afbd25f") driver.switch_to.frame(iframe) driver.find_element_by_id("e4890ded-16e1-4445-bf8e-7f655793f332").send_keys("haha") time.sleep(5) driver.quit() 4、多标签页切换 在页面操作过程中有时候点击某个链接会弹出新的窗口,这时就需要切换到新打开的窗口上进 行操作。 WebDriver 提供了以下方法 l current_window_handle:获得当前标签页句柄 l window_handles:返回所有便签页的句柄 l switch_to.window(标签页句柄):切换到对应的标签页 l 关闭标签页使用 close 方法 from selenium import webdriver # 创建浏览器驱动对象driver = webdriver.Chrome() # 访问网址 driver.get("http://www.baidu.com") # 找到搜索按钮 input_element = driver.find_element_by_id("kw") # 输入搜索内容 input_element.send_keys("松勤\n") # 点击松勤教育官网 driver.find_element_by_css_selector("#\34 > h3 > a > em").click() # 获得当前所有打开的窗口的句柄 all_handles = driver.window_handles for handle in all_handles: driver.switch_to.window(handle) print(driver.title) # 退出浏览器 driver.quit() 5、页面滚动 在自动化操作中,如果 web 页面过长,而我们需要的元素并不在当前可视页面中,那么 selenium 就无法对其进行操作;此时,我们就需要像平时操作浏览器一样来滚动页面,使我 们需要操作的对象可见! window.scrollBy() l window.scrollBy(0,500) 向下滚动 500 个像素 l window.scrollBy(0,-500) 向上滚动 500 个像素 l window.scrollBy(500,0) 向右滚动 500 个像素 l window.scrollBy(-500,0) 向左滚动 500 个像素 Selenium 中实现滚动页面 l driver.execute_script("window.scrollBy(0,500)") l driver.execute_script("arguments[0].scrollIntoView();", ele) 滚动至元素 ele 可见 有时候,我们需要滚动的不是整个页面,而是页面中的某个元素,这个元素有滚动条。 HTML 案例: 下面这个元素自带滚动条 处理方式也很简单,我们找到这个带滚动条的元素的 css 表达式就可以了js="document.querySelector("这里填 css 表达式").scrollTop=100" # 修改这个元素的 scrollTop 就可以 dr.execute_script(js) # 控制横向滚动 js="document.querySelector("这里填 css 表达式 po 模式 1、简单的 po 模式 Page Object Model: PO 设计模式是一种思想,应用在 selenium 自动化上,其主要体现在对界面交互细节的封装,也就是在实际测试中只关注业务流程就 OK 了 ,传统的设计中,在新增测试用例之后,代码会有以下几个问题: l 易读性差:一连串的 find element 会使代码显得杂乱无章 l 可复用性差:无公共方法,很难复用 l 可维护性差:一旦元素变化,需要维护修改大量测试用例 因此考虑到优化: PO 模式是一种自动化测试设计模式,将页面定位和业务操作分开,也就是把元素的定位 和测试脚本分开,从而提供可维护性。 首先抽象封装一个 BasePage 类,这个基类拥有一些 Webdriver 实例的属性,然后每一 个 Page 继承 BasePage,可以通过 driver 管理每一个 Page 中的元素,而且在 Page 中将这些操作封装为一个一个的方法。TestCase 依赖 page 类,进行组织测试步骤的工作。 这样做的好处,就是有元素变化,只需要维护每一个 Page 就行了,测试步骤变化,只需 要维护 TestCase 即可 下面举一个简单的例子来看一下 PO 模式: from selenium import webdriver class LoginPage: def __init__(self, path=None): # 创建 driver 对象 self.driver = webdriver.Chrome() # 最大化窗口 self.driver.maximize_window() # 访问网址 self.driver.get(path) # 用户名输入框 def username_input_box(self): return self.driver.find_element_by_id("username") # 密码输入框 def password_input_box(self): return self.driver.find_element_by_id("password") # 登录按钮 def login_button_box(self): return self.driver.find_element_by_id("btnLogin") # 抽离出页面动作类, 继承对应的页面类 class LoginPageAction(LoginPage): def login_action(self): """ 登录宝利商城 :return: """ self.username_input_box().send_keys("松勤老师") self.password_input_box().send_keys("123456") self.login_button_box().click() if __name__ == '__main__': LoginPageActionObj = LoginPageAction("http://120.55.190.222:38090/#/login") LoginPageActionObj.login_action() 2、初级版本 我们观察到,有些信息是可以写在配置文件里的,所以提取出文件 mySettings.py # 项目网址 url = "http://120.55.190.222:38090/#/login" # 账号密码 username = "松勤老师" password = "123456" 另外浏览器驱动对象也可以提取出来,所以提取出文件 myDriver.py # 路径记得换成自己的 from foo.songQing.seleniumV2.day4.初级版本.mySettings import url from selenium import webdriver class Driver: # 初始化为 None _driver = None @classmethod def get_driver(cls, browser_name="chrome"): """ 获取浏览器驱动对象 :param browser_name: 浏览器名称 :return: """ # 如果不为空就不需要新建了 if cls._driver is None: if browser_name == 'chrome': cls._driver = webdriver.Chrome() elif browser_name == 'firefox': cls._driver = webdriver.Firefox() elif browser_name == 'safari': cls._driver = webdriver.Safari() elif browser_name == 'opera': cls._driver = webdriver.Opera() elif browser_name == 'edge': cls._driver = webdriver.Edge() elif browser_name == 'ie': cls._driver = webdriver.Ie() # 隐式等待五秒 cls._driver.implicitly_wait(5) # 最大化窗口 cls._driver.maximize_window() # 访问默认的网页 cls._driver.get(url) # 登录 cls.__login() return cls._driver @classmethod def __login(cls): """ 私有方法, 只能在类里边使用 类外部无法使用, 子类不能继承 解决登录问题 :return: """ cls._driver.find_element_by_id("username").send_keys("松勤老师") cls._driver.find_element_by_id("password").send_keys("123456") cls._driver.find_element_by_id("btnLogin").click()if __name__ == '__main__': # 无论调用多少次, 都只有一个浏览器 Driver.get_driver() Driver.get_driver() Driver.get_driver() Driver.get_driver() 我们知道,ui 自动化的价值不在于发现多少 bug,而在于帮助手工测试节省了多少时间。所以,ui 自动化的 case 只测试正向场景,对于登录而言,只要能正常登录,即认为此条 case 是通过的。所以将登录的逻辑写 在 driver 类中。这样做的另一个好处是,当页面的数量增多,我们无需额外花费精力去保证登录一定在其他 的 case 之前执行。 这个时候,页面类就只剩下了与网页元素、元素操作相关的代码,我们以商城后台的登录页面为例 # 注意, 这个导入路径要换成自己的 from foo.songQing.seleniumV2.day5.初级版本.mySettings import username, password from foo.songQing.seleniumV2.day5.初级版本.myDriver import Driver class LoginPage: def __init__(self): # 创建 driver 对象 self.driver = Driver.get_driver() # 用户名输入框 def username_input_box(self): return self.driver.find_element_by_id("username") # 密码输入框 def password_input_box(self): return self.driver.find_element_by_id("password") # 登录按钮 def login_button_box(self): return self.driver.find_element_by_id("btnLogin") # 抽离出页面动作类, 继承对应的页面类 class LoginPageAction(LoginPage): def login_action(self): """ 登录宝利商城 :return: """ self.username_input_box().send_keys(username) self.password_input_box().send_keys(password) self.login_button_box().click() if __name__ == '__main__': LoginPageActionObj = LoginPageAction() LoginPageActionObj.login_action() 3、抽离出 BasePage 的版本 等待的时候,需要一个超时时间和一个轮询时间----将其加入配置文件 # 项目网址 url = "http://120.55.190.222:38090/#/login" # 账号密码 username = "松勤老师" password = "123456" # 智能等待超时时间 time_out = 10 # 智能等待轮询时间 poll_frequency = 0.5 driver 类保持不变 from foo.songQing.seleniumV2.day5.抽离出 BasePage 的版本.mySetting import url from selenium import webdriver class Driver: # 初始化为 None _driver = None @classmethod def get_driver(cls, browser_name="chrome"): """ 获取浏览器驱动对象 :param browser_name: 浏览器名称 :return: """ # 如果不为空就不需要新建了 if cls._driver is None: if browser_name == 'chrome': cls._driver = webdriver.Chrome() elif browser_name == 'firefox': cls._driver = webdriver.Firefox() elif browser_name == 'safari': cls._driver = webdriver.Safari() elif browser_name == 'opera': cls._driver = webdriver.Opera() elif browser_name == 'edge': cls._driver = webdriver.Edge() elif browser_name == 'ie': cls._driver = webdriver.Ie() # 最大化窗口 cls._driver.maximize_window() # 访问默认的网页 cls._driver.get(url) return cls._driver if __name__ == '__main__': # 无论调用多少次, 都只有一个浏览器 Driver.get_driver() Driver.get_driver() Driver.get_driver() Driver.get_driver() 我们发现每个页面类都有一些通用的内容,所以抽离出来,形成 basePage 类 from foo.songQing.seleniumV2.day5.抽离出 basePage 的版本.mySetting import time_out, poll_frequency from foo.songQing.seleniumV2.day5.抽离出 basePage 的版本.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.get_driver() def get_element(self, locator): """ 根据表达式匹配单个元素 :param locator: 元素定位表达式, 以元组形式传入, 示例: (By.ID, "abc") :return: """ # 判断元素是否存在 WebDriverWait( # 传入浏览器对象 driver=self.driver, # 传入超时时间 timeout=time_out, # 设置轮询时间 poll_frequency=poll_frequency).until( # 检测定位的元素是否可见 ec.visibility_of_element_located(locator)) # 返回元素对象, 元组传参 return self.driver.find_element(*locator) def get_elements(self, locator): """ 根据表达式匹配元素列表 :param locator: :return: """ # 判断元素是否存在 WebDriverWait( # 传入浏览器对象 driver=self.driver, # 传入超时时间 timeout=time_out, # 设置轮询时间 poll_frequency=poll_frequency).until( # 检测定位的元素是否可见 // 元素被定位并可见 ec.visibility_of_all_elements_located(locator)) # 返回元素列表, 元组传参 return self.driver.find_elements(*locator) loginpage 也进行一些修改 from foo.songQing.seleniumV2.day5.抽离出 basePage 的版本.mySetting import username, password from foo.songQing.seleniumV2.day5.抽离出 basePage 的版本.basePage import BasePagefrom selenium.webdriver.common.by import By class LoginPage(BasePage): def __init__(self): """ 进一步抽离元素定位方法 这里只封装寻找元素的方法, 不会真的去找元素 """ # 执行父类的构造方法 super().__init__() # 用户名输入框 self.username_input_locator = (By.ID, "username") # 密码输入框 self.password_input_locator = (By.ID, "password") # 登录按钮 self.login_button_locator = (By.ID, "btnLogin") # 用户名输入框 def username_input_box(self): return self.driver.find_element(*self.username_input_locator) # 密码输入框 def password_input_box(self): return self.driver.find_element(*self.password_input_locator) # 登录按钮 def login_button_box(self): return self.driver.find_element(*self.login_button_locator) # 抽离出页面动作类, 继承对应的页面类 class LoginPageAction(LoginPage): def login(self): """ 访问商家管理后台, 登录用户 :return: """ # 输入用户名 self.username_input_box().send_keys(username) # 输入密码 self.password_input_box().send_keys(password) # 点击登录按钮 self.login_button_box().click() if __name__ == '__main__': LoginPageActionObj = LoginPageAction() LoginPageActionObj.login() mySettings.py # 项目网址 url = "http://120.55.190.222:38090/#/login" # 账号密码 username = "松勤老师" password = "123456" # 智能等待超时时间 time_out = 10 # 智能等待轮询时间 poll_frequency = 0.5 myDriver.py from foo.songQing.seleniumV2.day5.utils.mySettings import url from selenium import webdriver class Driver: # 初始化为 None _driver = None @classmethod def get_driver(cls, browser_name="chrome"): """ 获取浏览器驱动对象 :param browser_name: 浏览器名称 :return: """ # 如果不为空就不需要新建了 if cls._driver is None: if browser_name == 'chrome': cls._driver = webdriver.Chrome() elif browser_name == 'firefox': cls._driver = webdriver.Firefox() elif browser_name == 'safari': cls._driver = webdriver.Safari() elif browser_name == 'opera': cls._driver = webdriver.Opera() elif browser_name == 'edge': cls._driver = webdriver.Edge() elif browser_name == 'ie': cls._driver = webdriver.Ie() cls._driver.implicitly_wait(5) cls._driver.maximize_window() cls._driver.get(url) cls.__login() return cls._driver @classmethod def __login(cls): """ 私有方法, 只能在类里边使用 类外部无法使用, 子类不能继承 解决登录问题 :return: """ cls._driver.find_element_by_id("username").send_keys("松勤老师") cls._driver.find_element_by_id("password").send_keys("123456") cls._driver.find_element_by_id("btnLogin").click() if __name__ == '__main__': # 无论调用多少次, 都只有一个浏览器 Driver.get_driver() Driver.get_driver() Driver.get_driver() Driver.get_driver() basePage.py from foo.songQing.seleniumV2.day5.utils.mySettings import time_out, poll_frequency from foo.songQing.seleniumV2.day5.utils.myDriver import Driver from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.wait import WebDriverWait import time class BasePage: def __init__(self): # 获取浏览器对象 self.driver = Driver.get_driver() def get_element(self, locator): """ 根据表达式匹配单个元素 :param locator: 元素定位表达式, 以元组形式传入, 示例: (By.ID, "abc") :return: """ # 判断元素是否存在 WebDriverWait( # 传入浏览器对象 driver=self.driver, # 传入超时时间 timeout=time_out, # 设置轮询时间 poll_frequency=poll_frequency).until( # 检测定位的元素是否可见 ec.visibility_of_element_located(locator)) # 返回元素对象, 元组传参 return self.driver.find_element(*locator) def to_page(self, url): time.sleep(1) self.driver.get(url) if __name__ == '__main__': x = BasePage() addProductPage.py from foo.songQing.seleniumV2.day5.pages.basePage import BasePage import time class AddProductPage(BasePage): def __init__(self): super().__init__() self.url = "http://120.55.190.222:38090/#/pms/addProduct" def product_classification_select_box(self): """商品分类下拉框, 外框""" return self.driver.find_element_by_css_selector("form > div:nth-child(1) .el-cascader__label") def product_classification_select_box_idx1(self, idx1): """商品分类下拉框, 一级分类""" return self.driver.find_element_by_css_selector("ul.el-cascader-menu > li:nth-child(%s)" % idx1) def product_classification_select_box_idx2(self, idx2): """商品分类下拉框, 二级分类""" return self.driver.find_element_by_css_selector("ul + ul.el-cascader-menu > li:nth-child(%s)" % idx2) def product_name_input_box(self): """商品名称输入框""" return self.driver.find_element_by_css_selector("label[for=\"name\"] + div input") def product_subtitle_input_box(self): """副标题输入框""" return self.driver.find_element_by_css_selector("label[for=\"subTitle\"] + div input") def product_brand_select_box(self): """商品品牌下拉框外框""" return self.driver.find_element_by_css_selector("label[for=\"brandId\"] + div input") def product_brand_select_box_option(self, idx): """商品品牌下拉框, 一级分类""" return self.driver.find_element_by_css_selector("body > div:nth-child(8) ul > li:nth-child(%s)" % idx) def next_step_commodity_promotion_button_box(self): """下一步, 填写商品促销按钮""" return self.driver.find_element_by_xpath("//*[text()=\"下一步,填写商品促销\"]") def is_herald_box(self): """预告商品开关""" return self.driver.find_element_by_xpath("//*[text()=\"预告商品:\"]/..//span") def next_step_product_attribute_button_box(self): """下一步, 填写商品属性按钮""" return self.driver.find_element_by_xpath("//*[text()=\"下一步,填写商品属性\"]") def next_step_choose_product_related_button_box(self): """下一步, 选择商品关联按钮""" return self.driver.find_element_by_xpath("//*[text()=\"下一步,选择商品关联\"]") def submit_product_button_box(self): """完成, 提交商品按钮""" return self.driver.find_element_by_xpath("//*[text()=\"完成,提交商品\"]") def confirm_submission_box(self): return self.driver.find_element_by_css_selector( "[class=\"el-button el-button--default el-button--small el-button--primary \"]") class AddProductPageAction(AddProductPage): def add_product_action(self, idx1, idx2, product_name, subtitle, brand_select_idx): self.to_page(self.url) # 点击商品分类下拉外框 self.product_classification_select_box().click() # 选择一级分类 self.product_classification_select_box_idx1(idx1).click() # 选择二级分类 self.product_classification_select_box_idx2(idx2).click() # 输入商品名称 self.product_name_input_box().send_keys(product_name) # 输入副标题 self.product_subtitle_input_box().send_keys(subtitle) # 点击商品品牌下拉框外框 self.product_brand_select_box().click() # 选择商品品牌一级分类 self.product_brand_select_box_option(brand_select_idx).click() # 点击[下一步,填写商品促销]按钮 self.next_step_commodity_promotion_button_box().click() # 点击是否预告商品开关 self.is_herald_box().click() # 点击[下一步, 填写商品属性]按钮 self.next_step_product_attribute_button_box().click() # 点击下一步, 选择商品关联按钮 self.next_step_choose_product_related_button_box().click() time.sleep(0.3) # 点击完成,提交商品按钮 self.submit_product_button_box().click() # 确认提交 self.confirm_submission_box().click() AddProductPageActionObj = AddProductPageAction()if __name__ == '__main__': AddProductPageActionObj.add_product_action(1, 1, "针不戳羊毛衫", "羊毛姗姗", 1) case.py import time from study.seleniumStu.day5.抽离出 basePage 版本.addProductPage import AddProductPageActionObj as APP from study.seleniumStu.day5.抽离出 basePage 版本.productLIstPage import ProductLIstPageActionObj as PLP def addProductCase(): product_name = "%s" % time.time() # 添加一个商品 APP.add_product_action("1", "1", product_name, product_name, 1) # 进入商品列表页面 PLP.to_page(3, PLP.url) # 获取商品列表,第一个商品的商品名称 first_product_name = PLP.first_tr_product_name_box().text if product_name == first_product_name: print("测试通过") else: print("测试失败") addProductCase() selenium 中如何判断元素是否存在 元素列表 如何保证操作元素的成功率 1、网速不好,适当等待 2、被点击的元素要占有一定的空间 3、被点击元素不能被其他元素遮挡 4、被操作的元素不能在视野之外 5、测试尽量使用专用环境,避免干扰 如何提高我们脚本的执行效率 1、钞能力,使用更高配置的电脑和更快的网络环境 2、优化代码,可以直接get访问网址的,不要一步步点击,能用显示等待的,不要强制等待 3、尽量用chrome,Safari 4、尽量用效率更高的css定位,不用xpath ui自动化的执行策略 每日执行一次,比如晚上11点,测试报告邮件团队 每次冒烟测试或回归测试的时候执行 动态执行,开发提交代码就执行一次(成本比较高 ui自动化不做数据库验证,这是接口测试要做的事情
6.自动化实战
7.selenium知识点梳理
8.常见面试题