web自动化测试工具Selenium的使用

321 阅读10分钟

web自动化测试工具-Selenium

Selenium 是一个开源的 web 自动化测试工具,免费,主要做功能测试。

1.特点

  1. 开源
  2. 跨平台:linux、windows、mac
  3. 支持多种浏览器
  4. 支持多种语言
  5. 成熟稳定
  6. 功能强大

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()