web自动化测试思路及实战
全部代码-Gitee:
Web_Auto_Test_FrameWork: WEB自动化测试实战练习 (gitee.com)
前期准备工作:
1、 python 列表、字典、循环、面向对象 (python语言基础)
2、 python第三方模块的使用:
os time 、文件操作、日期查找、配置文件ini、log日志、excel (框架底层的工具)
3、selenium入门:驱动、浏览器、元素识别、元素操作(页面元素识别、元素操作)
4、unittest框架:(优化成4章)框架的流程、断言、忽略测试、构建套件、测试报告生成(测试用例层、执行层)
5. selenium+unittest实战:编写线性脚本框架,
6. 正式开始编写PO模式的UI自动化框架
pageobjects 设计模式
Page Objects概念:
Page Objects是指UI界面上用于与用户进行交互的对象
pageobjects 设计模式概念:
pageobjects 模式是Selenium中的一种测试设计模式,主要是将每一个页面设计为一个Class,其中包含页面中需要测试的元素(按钮,输入框,标题 等),这样在Selenium测试页面中可以通过调用页面类来获取页面元素,这样巧妙的避免了当页面元素id或者位置变化时,需要改测试页面代码的情况。 当页面元素id变化时,只需要更改测试页Class中页面的属性即可。
简单来讲,就是将代码以页面为单位进行组织,针对这个页面上的所有信息,相关操作都放到一个类中;从而使具体的测试用例变成了简单的调用和验证操作
pageobjects 设计模式:把页面设计成类,把页面上的控件作为属性,把控件的操作设计成方法
类=属性+方法
以下是以关键字数据驱动框架做的实战项目
被测系统:禅道系统
框架01-pageobjects模型基础及应用
思路:
1. 新建一个元素信息包element_info,存放我们的页面
2. 新建webdriver驱动文件夹,导入浏览器的驱动文件
3. 新建login_page.py,添加属性、方法
4. 新建一个main_page.py
1. 在项目名下新建一个python的文件夹element_infos
新建Web_Auto_Test_Framework项目,在该项目的下面新建element_infos的py文件夹
2. 新建webdriver驱动文件夹,导入浏览器驱动文件
3. 在element_infos下新建login_page.py,添加属性、方法
前置条件下载selenium库:
代码示例:
# encoding: utf-8 # @author: Jeffrey # @file: login_page.py # @time: 2022/7/17 21:39 # @desc: 页面是类 控件:属性 控件操作:方法 import os from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service current_path = os.path.dirname(__file__) driver_path = os.path.join(current_path, '../webdriver/chromedriver.exe') class LoginPage: def __init__(self): chrome_driver_path = Service(driver_path) self.driver = webdriver.Chrome(service=chrome_driver_path) self.driver.get('http://47.107.178.45/zentao/www/index.php?m=user&f=login') self.driver.maximize_window() self.driver.implicitly_wait(30) # 控件:属性 self.username_input = self.driver.find_element(By.XPATH,'//input[@name="account"]') self.password_input = self.driver.find_element(By.XPATH,'//input[@name="password"]') self.keep_login_box = self.driver.find_element(By.XPATH,'//input[@id="keepLoginon"]') self.login_button = self.driver.find_element(By.XPATH,'//button[@id="submit"]') # 输入用户名 def input_username(self,username): self.username_input.send_keys(username) # 输入密码 def input_password(self,password): self.password_input.send_keys(password) # 点击保持登录 def keep_login(self): self.keep_login_box.click() # 点击登录按钮 def click_login(self): self.login_button.click() if __name__ == '__main__': loginpage = LoginPage() loginpage.input_username('test01') loginpage.input_password('newdream123') loginpage.click_login() # 小结:页面=属性+方法 # 属性的命名:名称_元素类型 # 操作的命名:动作_元素名称
4. 新建一个main_page.py
思路:
1. 添加元素--属性
2. 添加动作--方法
3. 处理好driver,把login的driver 转给主页面
代码示例:
#第二个页面 # encoding: utf-8 # @author: Jeffrey # @file: main_page.py # @time: 2022/7/2 13:04 # @desc: import os import time from selenium import webdriver from selenium.webdriver.common.by import By from element_infos.login_page import LoginPage class MainPage: def __init__(self): login_page = LoginPage() login_page.input_username('test01') login_page.input_password('newdream123') login_page.click_login() # 把登录页面的driver转移到main_page self.driver = login_page.driver self.companyname_showbox = self.driver.find_element(By.XPATH, '//h1[@id="companyname"]') self.myzone_menu = self.driver.find_element(By.XPATH, '//li[@data-id="my"]') self.product_menu = self.driver.find_element(By.XPATH, '//li[@data-id="product"]') self.username_showbox = self.driver.find_element(By.XPATH, '//span[@class="user-name"]') # 获取公司名称 def get_companyname(self): value = self.companyname_showbox.get_attribute('title') return value # 选择我的地盘 def goto_myzone(self): self.myzone_menu.click() # 选择我的产品 def goto_product(self): self.product_menu.click() # 获取登录的用户名 def get_username(self): value = self.username_showbox.text return value if __name__ == '__main__': main_page = MainPage() print(main_page.get_username()) # pageobject模式是实例化页面之后,需要识别所有的元素,然后再去操作,导致有些元素不能识别的问题;如下三个方法会报错,可参考框架02 # print(main_page.get_companyname()) # main_page.goto_myzone() # # main_page.goto_product()
5、新建common的py文件夹再添加日志封装log_utils.py
前置条件:在项目根目录下新建一个logs的文件夹
代码示例:
import time import os import logging currrent_path = os.path.dirname(__file__) log_path = os.path.join(currrent_path,'../logs') class LogUtils: def __init__(self,log_path=log_path): self.logfile_path = log_path # 创建日志对象logger self.logger = logging.getLogger(__name__) # 设置日志级别 self.logger.setLevel(level=logging.INFO) # 设置日志的格式 formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') """在log文件中输出日志""" # 日志文件名称显示一天的日志 self.log_name_path = os.path.join(self.logfile_path, "UI_Test_%s" % time.strftime('%Y_%m_%d')) # 创建文件处理程序并实现追加 self.file_log = logging.FileHandler(self.log_name_path, 'a', encoding='utf-8') # 设置日志文件里的格式 self.file_log.setFormatter(formatter) # 设置日志文件里的级别 self.file_log.setLevel(logging.INFO) # 把日志信息输出到文件中 self.logger.addHandler(self.file_log) # 关闭文件 self.file_log.close() """在控制台输出日志""" # 日志在控制台 self.console =logging.StreamHandler() # 设置日志级别 self.console.setLevel(logging.INFO) # 设置日志格式 self.console.setFormatter(formatter) # 把日志信息输出到控制台 self.logger.addHandler(self.console) # 关闭控制台日志 self.console.close() # 添加该方法可避免每个日志重复打印两次 def get_log(self): return self.logger logger = LogUtils().get_log() if __name__ == '__main__': logger.info('123') logger.error('error') # 把封装的日志引入到login_page.py文件中
把日志添加到login_page.py文件中
小结:
1. 封装log_utils工具类
2. 避免一行日志出现多行的情况,在log_utils.py添加了一个logger对象
框架02--pageobject模型优化使用
界面层: 界面的元素
控件层: 单独验证每个控件的功能
功能层:单个或者多个功能组合操作形成的功能
业务层:单个或多个功能形成业务
PO模式分层和线性脚本的比较
线性脚本:按操作识别元素,识别一个元素操作一个,风险比较小
pageobject模式模型:先全部识别元素,然后再进行操作
注意:由于po模式是要先识别全部元素,才能进行操作所以会引出下面的问题
问题引出:
分析:加了这二句之后,出现问题,原因:pageobject模式是实例化页面之后,需要识别所有的元素,然后再去操作,导致有些元素不能识别的问题。
处理方法:识别数据分离,实例化对象的时候不识别元素,操作的时候再去识别。
思路:
1. 在common中创建一个base_page.py-->BasePage类
2. 页面层元素识别方法,改为不用识别好元素,只需要识别到元素的信息
3. 在BasePage类中封装元素识别方法
4. 完善login_page.py中的LoginPage类继承BasePage类,并完善代码
1、在common中创建一个base_page.py-->BasePage类
代码示例:
# encoding: utf-8
# @author: Jeffrey
# @file: base_page.py
# @time: 2022/7/2 17:18
# @desc: 封装页面的公共类和方法
# =============================
# BasePage() 页面基础类
# 作用:把每个页面的公共操作分离出来
# 1、封装浏览器操作
# 2、封装元素是被
# 3、元素操作
# =============================
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from common.log_utils import logger
class BasePage(object):
"""页面基础类"""
def __init__(self,driver):
self.driver = driver
# 该属性是用来编写下方代码时,可以调用find_element方法,编写完后还是要用上面的属性
# self.driver = webdriver.Chrome(driver)
def open_url(self,url):
"""打开网站"""
self.driver.get(url)
logger.info('打开网站,地址为 %s' % url)
def set_browser_max(self):
self.driver.maximize_window()
logger.info('浏览器窗口最大化')
def refresh(self):
"""浏览器刷新"""
self.driver.refresh()
logger.info('浏览器刷新')
def get_title(self):
"""获取网页标题"""
title_value = self.driver.title
logger.info('获取网页的标题,标题为: %s' % title_value)
return title_value
def get_url(self):
"""获取url地址"""
url_value = self.driver.current_url
logger.info('获取网站的地址,地址为:%s' % url_value)
return url_value
def close_tab(self):
"""关闭当前浏览器的tab页"""
self.driver.close()
logger.info('关闭当前浏览器的tab页')
def exit_driver(self):
"""退出浏览器"""
self.driver.quit()
logger.info('退出浏览器')
"""封装等待时间(显式,隐式,固定等待)"""
def implicitly_wait(self, seconds):
"""封装隐式等待"""
self.driver.implicitly_wait(seconds)
logger.info('浏览器隐式等待:%s' % seconds)
# 固定等待封装
def wait(self, seconds):
time.sleep(seconds)
logger.info('固定等待时间:%s 秒' % seconds)
2、 在login_page.py页面层元素识别方法,改为不用识别好元素,只需要识别到元素的信息
代码示例:
# encoding: utf-8
# @author: Jeffrey
# @file: login_page.py
# @time: 2022/7/17 21:39
# @desc: 页面是类 控件:属性 控件操作:方法
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from common.log_utils import logger
current_path = os.path.dirname(__file__)
driver_path = os.path.join(current_path, '../webdriver/chromedriver.exe')
class LoginPage:
def __init__(self):
logger.info('禅道系统自动化测试')
chrome_driver_path = Service(driver_path)
self.driver = webdriver.Chrome(service=chrome_driver_path)
self.driver.get('http://47.107.178.45/zentao/www/index.php?m=user&f=login')
logger.info('打开网站:%s' % self.driver.current_url)
self.driver.maximize_window()
self.driver.implicitly_wait(30)
# 控件:属性
# self.username_input = self.driver.find_element(By.XPATH,'//input[@name="account"]')
# self.password_input = self.driver.find_element(By.XPATH,'//input[@name="password"]')
# self.keep_login_box = self.driver.find_element(By.XPATH,'//input[@id="keepLoginon"]')
# self.login_button = self.driver.find_element(By.XPATH,'//button[@id="submit"]')
# 用户名输入框
self.username_inputbox = {
'element_name': '用户名输入框',
'locator_type': 'xpath',
'locator_value': '//input[@name="account"]',
'timeout': 5}
# 密码输入框
self.password_inputbox = {
'element_name': '密码输入框',
'locator_type': 'xpath',
'locator_value': '//input[@name="password"]',
'timeout': 5}
# 保持登录
self.keep_login_box = {
'element_name': '保持登录按钮',
'locator_type': 'xpath',
'locator_value': '//input[@id="keepLoginon"]',
'timeout': 5}
# 登录按钮
self.login_button = {
'element_name': '登录按钮',
'locator_type': 'xpath',
'locator_value': '//button[@id="submit"]',
'timeout': 5}
# 输入用户名
def input_username(self,username):
self.username_inputbox.send_keys(username)
# 输入密码
def input_password(self,password):
self.password_inputbox.send_keys(password)
# 点击保持登录
def keep_login(self):
self.keep_login_box.click()
# 点击登录按钮
def click_login(self):
self.login_button.click()
if __name__ == '__main__':
loginpage = LoginPage()
loginpage.input_username('test01')
loginpage.input_password('newdream123')
loginpage.click_login()
# 小结:页面=属性+方法
# 属性的命名:名称_元素类型
# 操作的命名:动作_元素名称
3、在BasePage类中封装元素识别方法
前置条件:导包
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from common.log_utils import logger
代码示例:
# self.username_inputbox = {'element_name': '用户名输入框',
# 'locator_type': 'xpath',
# 'locator_value': '//input[@name="account"]',
# 'timeout': 5}
# 元素的识别,通过分离处理的元素识别字典信息,返回一个元素
# self.username_inputbox=self.driver.find_element(By.XPATH,'//input[@name="account"]') #属性-->控件
# element=WebDriverWait(driver,3).until(lambda x: x.find_element_by_css_selector(div.red_box"))
def find_element(self, element_info):
"""
通过元素信息字典数据,查找元素
:param element_info: 元素识别信息字典
:return: 返回识别到的元素
"""
locator_type_name = element_info['locator_type']
locator_value_info = element_info['locator_value']
locator_timeout = element_info['timeout']
if locator_type_name == 'name':
locator_type = By.NAME
elif locator_type_name == 'id':
locator_type = By.ID
elif locator_type_name == 'xpath':
locator_type = By.XPATH
elif locator_type_name == 'css':
locator_type = By.CSS_SELECTOR
elif locator_type_name == 'class':
locator_type = By.CLASS_NAME
element = WebDriverWait(self.driver, locator_timeout).until(lambda x:x.find_element(locator_type,locator_value_info))
logger.info('[%s]元素识别成功' % element_info['element_name'])
return element
BasePage类:浏览器操作、元素识别、元素操作(切框架、切句柄等等)
4、完善login_page.py中的LoginPage类继承BasePage类,并完善代码
完善login_page.py
代码示例:
# encoding: utf-8
# @author: Jeffrey
# @file: login_page.py
# @time: 2022/7/2 12:39
# @desc: 页面是类 控件:属性 控件操作:方法
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from common.base_page import BasePage
from common.log_utils import logger
from common.element_data_utils import ElementDataUtils
current_path = os.path.dirname(__file__)
driver_path = os.path.join(current_path, '../webdriver/chromedriver.exe')
class LoginPage(BasePage):
def __init__(self,driver):
# 调用父类的初始化
super().__init__(driver)
# 控件:属性
# self.username_inputbox = self.driver.find_element(By.XPATH,'//input[@name="account"]')
# self.password_inputbox = self.driver.find_element(By.XPATH,'//input[@name="password"]')
# self.keep_login_box = self.driver.find_element(By.XPATH,'//input[@id="keepLoginon"]')
# self.login_button = self.driver.find_element(By.XPATH,'//button[@id="submit"]')
# 用户名输入框
self.username_inputbox = {
'element_name':'用户名输入框',
'locator_type':'xpath',
'locator_value':'//input[@name="account"]',
'timeout':5}
# 密码输入框
self.password_inputbox = {
'element_name':'密码输入框',
'locator_type':'xpath',
'locator_value':'//input[@name="password"]',
'timeout':5}
# 保持登录
self.keep_login_box = {
'element_name':'保持登录按钮',
'locator_type':'xpath',
'locator_value':'//input[@id="keepLoginon"]',
'timeout':5}
# 登录按钮
self.login_button = {
'element_name':'登录按钮',
'locator_type':'xpath',
'locator_value':'//button[@id="submit"]',
'timeout':5}
# 输入用户名
def input_username(self,username):
self.input(self.username_inputbox, username)
# 输入密码
def input_password(self,password):
self.input(self.password_inputbox, password)
# 点击保持登录
def keep_login(self):
self.click(self.keep_login_box)
# 点击登录按钮
def click_login(self):
self.click(self.login_button)
if __name__ == '__main__':
chrome_driver_path = Service(driver_path)
driver = webdriver.Chrome(service=chrome_driver_path)
loginpage = LoginPage(driver)
loginpage.open_url('http://47.107.178.45/zentao/www/index.php?m=user&f=login')
loginpage.set_browser_max()
loginpage.implicitly_wait(30)
loginpage.input_username('test01')
loginpage.input_password('newdream123')
loginpage.click_login()
小结:
在页面类拿到元素的识别信息字典-->通过BasePage的元素识别方法获取一个元素-->再操作元素--->再页面类再根据具体的业务封装操作
框架03--元素信息分离设计
思路:元素识别信息数据放代码中是否好维护?
数据的类型:1. 元素识别数据 2. 配置数据(url,路径,邮箱) 3. 测试用例数据
数据与代码的分离:
1. 数据放ini配件文件中
2. 放excel表中
3. 放 mysql
4. 放yaml文件中
数据放Excel表中思路
1. 新建一个element_info_datas文件夹,把元素识别的数据放Excel表中
2. 步骤2:编写测试代码,读取excel表中数据元素识别数据
3. 把读取excel的代码封装element_data_utils.py文件中
4. 步骤4:改造LoginPage,从Excel中拿元素识别数据
1.新建一个element_info_datas文件夹,把元素识别的数据放Excel表中
数据结构:一个字典套一层字典:{ {},{},{}}
{
‘username_inputbox’:{
'element_name':'用户名输入框',
'locator_type':'xpath','
locator_value':'//input[@name="account"]',
'timeout':5}
}
2、编写测试代码,读取excel表中数据元素识别数据
安装一个1.2.0版本的xlrd pip install xlrd==1.2.0
在samples文件下在demo中编写线性代码
3、把读取excel的代码封装element_data_utils.py文件中
代码示例:
# encoding: utf-8
# @author: Jeffrey
# @file: element_data_utils.py
# @time: 2022/7/2 18:40
# @desc: 读取excel工具类
import os
import xlrd
current_paht = os.path.dirname(__file__)
excel_path =os.path.join(current_paht, '../element_info_datas/element_info_datas.xlsx')
class ElementDataUtils(object):
def __init__(self,page_name,excel_path = excel_path):
self.excel_path = excel_path
self.workbook = xlrd.open_workbook(self.excel_path)
self.sheet = self.workbook.sheet_by_name(page_name)
self.row_count = self.sheet.nrows
def get_element_info(self):
element_infos = {}
for i in range(1,self.row_count):
element_info = {}
element_info['element_name'] = self.sheet.cell_value(i, 1)
element_info['locator_type'] = self.sheet.cell_value(i, 2)
element_info['locator_value'] = self.sheet.cell_value(i, 3)
element_info['timeout'] = self.sheet.cell_value(i, 4)
element_infos[self.sheet.cell_value(i, 0)] = element_info
return element_infos
if __name__ == '__main__':
elements = ElementDataUtils('login_page').get_element_info()
print(elements)
4、改造LoginPage,从Excel中拿元素识别数据
代码示例:
# encoding: utf-8
# @author: Jeffrey
# @file: login_page.py
# @time: 2022/7/2 12:39
# @desc: 页面是类 控件:属性 控件操作:方法
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from common.base_page import BasePage
from c