web自动化测试工具-Selenium
Selenium 是一个开源的 web 自动化测试工具,免费,主要做功能测试。
1.特点
- 开源
- 跨平台:linux、windows、mac
- 支持多种浏览器
- 支持多种语言
- 成熟稳定
- 功能强大
2.环境搭建
2.1.基于Python环境搭建
1. Python开发环境
2. 安装selenium包
# 安装
pip intsall selenium
pip intsall selenium==2.48.0
# 查看
pip show selenium
# 卸载
pip uninstall selenium
3. 安装浏览器
4. 安装浏览器驱动 -- 保证能够用程序驱动浏览器,实现自动化。
# 安装地址
谷歌:chromedriver.storage.googleapis.com/index.html
# 使用
将驱动目录路径添加到环境变量
3.webdriver使用
1.浏览器操作
from selenium import webdriver
from time import sleep
# 打开谷歌
driver = webdriver.Chrome();
# 关闭
driver.quit()
# 获取百度
driver.get('http://www.baidu.com')
driver.maximize_window() # 最大化浏览器
driver.set_window_size(width=, height=) # 设置浏览器窗口大小
driver.set_window_position(x=,y=) # 设置浏览器窗口位置
driver.back() # 浏览器后退按钮
driver.forward() # 浏览器前进按钮
driver.refresh() # 刷新
driver.close() # 关闭当前窗口
driver.quit() # 关闭浏览器驱动对象 -- 关闭所有窗口
driver.title # 获取页面的title
driver.current_url # 获取页面的url
# 拉动滚动条,执行js语句
js = "window.scrollTo(左边距, 上边距)" # 单位px
driver.execute_script(js)
# 切换子窗口,当前页面无法访问子页面的元素,需要切换页面才能定位
driver.switch_to.frame(frame_reference=) # frame_reference 可以是frame看框架的name,id或者定位到frame元素
driver.switch_to.default_content() # 恢复默认页面
# 切换窗口
driver.current_window_handle # 获取当前窗口句柄
driver.window_handles # 获取所有窗口句柄
driver.switch_to.window(handle) # 切换指定句柄窗口
handles = driver.window_handles
for h in handles:
if h != current_handle:
driver.switch_to.window(h)
# 窗口截图
driver.get_screenshot_as_file(filename=) # filename 为图片保存路径包含文件名
2.元素操作
# 获取元素
element = driver.find_element_by_id("kw")
element = driver.find_element_by_name("wd")
element = driver.find_element_by_class_name("s_ipt")
elements = driver.find_elements_by_tag_name("input")
xinwen = driver.find_element_by_link_text("新闻") # 精确查询
xinwen = driver.find_element_by_partial_link_text("新") # 模糊查询
element = driver.find_element_by_xpath("/html/body/div/div/div[5]/div/div/form/span/input") # div[5]为集合,从1开始, 绝对路径
element = driver.find_element_by_xpath("//input[@id='kw' and @class='s_ipt']") 相对路径
element = driver.find_element_by_css_selector("#kw")
# 操作元素
element.send_keys("软件测试") # 输入 软件测试
element.click() # 点击
element.clear() # 清除文本
element.size 返回元素的大小
element.text 返回元素的文本
element.get_attribute("xxx") 获取属性值
element.is_displayed() 判断元素是否可见
element.is_enable() 判断元素是否可用
element.is_selected() 判断元素是否选中
# 元素等待:在定位页面元素时,如果未找到,会在指定时间内一直等待的过程。
from selenium.webdriver.support.wait import WebDriverWait
# 隐式等待
driver.implicitly_wait(30) # 隐式等待为全局设置,作用于所有元素
# 显式等待
wait = WebDriverWait(driver=driver, timeout=30, poll_frequency=0.5)
element = wait.until(lambda x: x.find_element_by_id('kw'))
# 操作cookie
driver.get_cookie(name=) # 获取指定 cookie
driver.get_cookies() # 获取本网站所有本地 cookie
driver.add_cookie(cookie_dict=) # 添加 cookie cookie_dict 一个字典对象,必须的包括 name 和 value
3.鼠标操作
from selenium.webdriver import ActionChains
# 实例化对象
action = ActionChains(driver)
# 方法
action.click(element) 点击
action.context_click(element) 右击
action.double_click(element) 双击
action.drag_and_drop(source=, target=) 拖动
action.drag_and_drop_by_offset(source=, xoffset=, yoffset=) 拖动
action.move_to_element(element) 悬停
action.move_by_offset(xoffset=,yoffset=) 悬停
action.perform() 执行,此方法用来执行以上所有鼠标操作
# 注意
右键菜单无法点击,不能取消,要使用键盘按钮一同使用。
button = driver.find_element_by_css_selector("input[type='submit']")
action.move_to_element(button).perform()
action.click().perform()
4.键盘操作
from selenium.webdriver.common.keys import Keys
element.send_keys(Keys.BACK_SPACE) 删除backspace
element.send_keys(Keys.SPACE) 空格
element.send_keys(Keys.TAB) 制表键tab
element.send_keys(Keys.ESCAPE) 回退键esc
element.send_keys(Keys.ENTER) 回车enter
element.send_keys(Keys.CONTROL, 'a') 全选ctrl+a
element.send_keys(Keys.CONTROL, 'c') 复制ctrl+c
5.文件操作
# 上传文件按钮
element = driver.find_element_by_css_selector("[name='upload']")
# 上传文件
element.send_keys("D:\hello.txt")
6.表单操作
from selenium.webdriver.support.select import Select
# 获取select对象, el为select标签
Select(el).select_by_index() # 通过下标
Select(el).select_by_value() # 通过value
Select(el).select_by_visible_text() # 通过文本
7.弹出框操作
# 获取弹出框对象, 弹出框类型 alert confirm prompt
alert = driver.switch_to.alert
# 调用方法
alert.text # 返回文字信息
alert.accept() # 接收对话框选项
alert.dismiss() # 取消对话框选项
4.数据传入方式
1.JSON
import json
# 字典 - 》 json json.dumps(data)
print("字典 - 》 json ")
data = {"name":"张三", "age":18}
print(" 转换前的数据类型:", type(data))
print(" dict :", data)
string = json.dumps(data)
print(" 转换后的数据类型:", type(string))
print(" json : ", string)
# json - 》 字典 json.loads(data2)
print("字典 - 》 json")
data2 = '{"name":"张三", "age":18}' # 属性名必须使用双引号
print(" 转换前的数据类型:", type(data2))
print(" json :", data2)
string2 = json.loads(data2)
print(" 转换后的数据类型:", type(string2))
print(" dict : ", string2)
# 写入文件
data = {"name":"李四", "age":20}
with open("./write.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
# 文件读取
with open("./data.json", encoding="utf-8") as f:
data = json.load(f)
print(data)
5.UnitTest框架
'''
UnitTest 框架是 Python 自带的一个单元测试框架,用来做单元测试。
1. TestCase 测试用例
2. TestSuite 测试套件
3. TextTestRunner 以文本形式运行测试用例,用来执行测试用例和测试套件
4. TestLoader 批量执行测试用例
5. Fixture 固定装置,两个固定的函数,一个初始化,一个结束。
'''
1.TestCase
import unittest
'''
TestCase
1. 导包 import unittest
2. 测试类 继承 unittest.TestCase
3. 测试方法 以test开头
若无法运行,则 File -> Settings -> Tools -> Python Integrated Tools -> default test runner 选择需要的框架
'''
2.TestSuite
import unittest
'''
TestSuite 测试套件,多条测试用例集合再一起
1. 实例化 suite = unittest.TestSuite()
2. 添加用例 suite.addTest(test=)
3. 添加扩展 suite.addTest(unittest.makeSuite(testCaseClass=)) 将类testCaseClass中以test开头的方法添加到测试套件中
'''
3.TextTestRunner
import unittest
'''
TextTestRunner 用来执行测试用例和测试套件
1. 实例化:runner = unittest.TextTestRunner()
2. 执行: runner.run(test=) test 为测试用例或测试套件
'''
4.TestLoader
'''
TestLoader 用来加载 TestCase 到 TestSuite 中
# start_dir 为测试用例的目录
# pattern 匹配文件的格式 如:test*.py 匹配以test开头的py文件
# top_level_dir 默认为 None,可不传
suite = unittest.TestLoader.discover(start_dir=, pattern=, top_level_dir=)
区别:
TestSuite 需要手动添加测试用例,可以添加测试类,也可以添加测试类中的测试方法。
TestLoader 搜索指定目录下指定开头.py文件,添加测试类中所有测试方法,不能指定测试方法。
'''
5.Fixture
import unittest
'''
Fixture 装置 ,对一个测试用例环境的初始化和销毁就是一个 Fixture
1.初始化函数: def setUp() # 每个测试方法前都运行
2.结束函数: def tearDown() # 每个测试方法后都运行
Fixture 级别:
1.函数级别 setUp tearDown
2.类级别 setUpClass tearDownClass
3.模块级别 setUpModule tearDownModule
'''
def setUpModule():
print("setUpModel------")
def tearDownModule():
print("tearDownModel------")
class Test05(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
print("setUpClass执行")
@classmethod
def tearDownClass(cls) -> None:
print("tearDownClass执行")
def setUp(self):
print("setUp执行")
def tearDown(self) -> None:
print("tearDown执行")
def test01(self):
print("test01执行")
def test02(self):
print("test02执行")
6.断言 assert
self.assertTrue(expr=,msg=None) # 验证expr是否为true
self.assertFalse(expr=,msg=None) # 验证 expr 是否为 false
self.assertEqual(first=,second=,msg=None) # 验证 first == second
self.assertNotEqual(first=,second=,msg=None)
self.assertIsNone(obj=, msg=None)
self.assertIsNotNone(obj=,msg=None)
self.assertIn(member=, container=,msg=None) # 验证container中是否包含member
self.assertNotIn(member=,container=,msg=None)
7.添加测试数据
import unittest
from parameterized import parameterized
'''
parameterized 的应用
1.导包 from parameterized import parameterized
2.修饰测试函数 @parameterized.expand(列表类型数据)
3.在测试函数中使用变量接收值
语法:
1.单个参数:值为列表
2.多个参数:值为列表嵌套元组 如:[(1,2,3),(2,3,4)]
'''
def get_data():
return [(1, 2, 3), (2, 3, 4), (3, 4, 5)]
# 定义测试类
class Test07(unittest.TestCase):
# 单个参数
@parameterized.expand(['1','2','3'])
def test01(self, num):
print(num)
# 多个参数
@parameterized.expand([(1,2,3),(2,3,4),(3,4,5)])
def test03(self, num1, num2, result):
print("{}+{}={}".format(num1,num2,result))
@parameterized.expand(get_data())
def test04(self, a, b, c):
print(a, "+", b, "=", c)
8.跳过测试
@unittest.skip('代码未完成') # 直接将测试函数标记成跳过
@unittest.skipIf(condition=, reason=) # 根据条件判断测试函数是否跳过
9.生成测试报告
import time
import unittest
from jd_HTMLTestRunner import HTMLTestRunner
'''
基于 unittest 框架执行生成 html 报告
'''
suite = unittest.defaultTestLoader.discover("./", "d01*.py")
report_dir = "../report/{}.html".format(time.strftime("%Y %H_%M_%S"))
with open(report_dir, "wb") as f:
HTMLTestRunner(stream=f, verbosity=2, title="xx项目自动化测试报告", description="操作系统win10").run(suite)
6.日志处理
1.简单使用
import logging
'''
日志级别: debug -> info -> warning -> error -> critical
'''
# 日志格式
formatter = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d] - %(message)s"
# 日志保存到指定文件
filename = "./log/log01.log"
# 设置日志级别 默认级级别 WARNING
#logging.basicConfig(level=logging.DEBUG) # 输出在控制台
#logging.basicConfig(level=logging.INFO, format=formatter) # 输出在控制台,使用格式
logging.basicConfig(level=logging.INFO, format=formatter, filename=filename) # 输出到文件
# 只会显示 大于等于 日志级别的日志信息
logging.debug("this is a debug")
logging.info("this is a info")
logging.warning("this is a warning")
logging.error("this is a error")
logging.critical("this is a critical")
2.四大组件
'''
日志模块四大组件
1.日志器 Logger:提供了程序使用日志的入口
2.处理器 Handler:将日志记录发送到合适的目的输出
3.格式器 Formatter:决定日志记录的最终输出格式
4.过滤器 Filter:提供了更细粒度的控制工具来决定输出那条日志记录,丢弃那条日志记录
Logger 用法
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critical()
logger.setLevel() 设置日志级别
logger.addHandler() 添加一个 handler 对象
logger.addFilter() 添加一个 filter 对象
Handler 对象 : 将消息分发到 handler 指定的位置,比如 控制台、文件、网络、邮箱等。
Handler 是一个接口,需要实例化具体的子类
logging.StreamHandler 将日志输出到 Stream , 如 std.out \ std.err
logging.FileHandler 将日志发送到磁盘文件,默认文件大小无限增长
logging.handlers.RotatingFileHandler 将日志发送到磁盘文件,支持日志文件按大小切割
logging.handlers.TimedRotatingFileHandler 将日志发送到磁盘文件,支持日志文件按时间切割
Formatter 对象 用于配置日志信息的格式
logging.Formatter(fmt=, datefmt=, style=) # fmt 指定消息格式化字符串
# datefmt 指定日期格式字符串
# style 默认为 %
'''
# 导入包名,可以使用其下的 init 文件
import logging
# 导入模块
import logging.handlers
# 使用 Logger
logger = logging.getLogger(name="myLogger")
logger.setLevel(logging.INFO)
# 添加 Handler
sh = logging.StreamHandler()
logger.addHandler(sh)
# when: S 按秒分 M 按分钟分, H 按小时分, D 按天分
# interval: 间隔
# backupCount:保留文件数量
th = logging.handlers.TimedRotatingFileHandler(filename="./log/logtime.log", when="M", interval=1, backupCount=3, encoding="utf-8")
logger.addHandler(th)
# 添加 Formatter
formatter = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d] - %(message)s"
fmt = logging.Formatter(formatter)
sh.setFormatter(fmt)
th.setFormatter(fmt)
logger.info("log -- info") # 文件 和 控制台 都能输出
3.封装日志,单例模式
'''
封装日志
'''
import logging.handlers
# 单例模式
class GetLogger:
logger = None
# 单例模式
@classmethod
def get_logger(cls, filename="./log/logtime.log", when="M", interval=1, backupCount=3, encoding="utf-8"):
if cls.logger is None:
# 使用 Logger
cls.logger = logging.getLogger()
cls.logger.setLevel(logging.INFO)
# 添加 Handler
sh = logging.StreamHandler()
cls.logger.addHandler(sh)
# when: S 按秒分 M 按分钟分, H 按小时分, D 按天分
# interval: 间隔
# backupCount:保留文件数量
th = logging.handlers.TimedRotatingFileHandler(filename, when, interval, backupCount, encoding)
cls.logger.addHandler(th)
# 添加 Formatter
formatter = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d] - %(message)s"
fmt = logging.Formatter(formatter)
sh.setFormatter(fmt)
th.setFormatter(fmt)
return cls.logger
if __name__ == '__main__':
logger1 = GetLogger().get_logger()
logger2 = GetLogger().get_logger()
print("两个logger是否相等:{}".format(logger1 == logger2))
logger1.info("123456")
7.PO模式
Page Object(页面对象)
核心思想是通过对界面元素的封装减少冗余代码,主要体现在对界面交互细节的封装,也就是在实际测试中只关注业务流程;同时在后期维护中,若元素定位发生变化, 只需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。
1.对象库层
Base(基类) :封装page 页面一些公共的方法,如初始化方法、查找元素方法、点击元素方法、输入方法、获取文本方法、截图方法等
'''
page 页面一些公共方法
'''
from selenium.webdriver.support.wait import WebDriverWait
class Base:
def __init__(self, driver):
self.driver = driver
# loc 为元组, 如 (By.CSS_SELECTOR, ".tel")
# 使用*loc解包
def base_find_element(self, loc, timeout=30, poll=0.5):
return WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll).until(lambda x:x.find_element(*loc))
def base_click(self, loc):
self.base_find_element(loc).click()
def base_input(self, loc, value):
el = self.base_find_element(loc)
el.clear()
el.send_keys(value)
def base_get_text(self, loc):
return self.base_find_element(loc).text
def base_get_image(self):
self.driver.get_screenshot_as_file("../../../img/v4-fail.png")
2.操作层
page(页面对象):封装对元素的操作,一个页面封装成一个对象
# __init__.py
from selenium.webdriver.common.by import By
login_username = By.ID, "u"
login_pwd = By.ID, "pwd"
login_code = By.ID, "code"
login_btn = By.ID, "login"
text = By.ID, "msg"
# page_login.py
from PO模式.v4.base.base import Base
from PO模式.v4 import page
class PageLogin(Base):
# 输入用户名
def page_input_username(self, username):
self.base_input(page.login_username, username)
# 输入密码
def page_input_password(self, pwd):
self.base_input(page.login_pwd, pwd)
# 输入验证码
def page_input_code(self, code):
self.base_input(page.login_code, code)
# 登录
def page_click_login_btn(self):
self.base_click(page.login_btn)
# 获取信息
def page_get_text(self):
return self.base_get_text(page.text)
# 截图
def page_get_img(self):
self.base_get_image()
# 组装业务
def page_login(self, username, pwd, code):
self.page_input_username(username)
self.page_input_password(pwd)
self.page_input_code(code)
self.page_click_login_btn()
self.page_get_text()
self.page_get_img()
3.业务层
import unittest
from PO模式.v4.page.page_login import PageLogin
from selenium import webdriver
from parameterized import parameterized
def get_data():
return [
("15122223333", "123456", "8888", "账号不存在!"),
("15100001111", "123123", "8888", "密码错误!")
]
class TestLogin(unittest.TestCase):
login = None
@classmethod
def setUpClass(cls) -> None:
driver = webdriver.Chrome() # 打开谷歌
driver.maximize_window() # 窗口最大化
# driver.implicitly_wait(30) # 隐式等待
url = 'D:\Codes\PyCharmProject\webdriver自动化测试\html\login.html'
driver.get(url) # 打开 url
cls.login = PageLogin(driver)
@classmethod
def tearDownClass(cls) -> None:
cls.login.driver.quit()
# 登录测试代码
@parameterized.expand(get_data())
def test_login(self, username, pwd, code, expect):
self.login.page_login(username, pwd, code)
msg = self.login.page_get_text()
try:
self.assertEqual(msg, expect)
except AssertionError:
self.login.page_get_img()