说明
1.项目采用PO设计思想,一个页面一个Page.py,并在其中定义元素和操作方法;在TestCase中直接调用页面中封装好的操作方法操作页面。
2.使用python+selenium+pytest+allure进行编写
3.目录结构:
common: 存放通用方法文件夹
- base.py 页面操作基础类封装
- driver.py 浏览器驱动类封装
- get_config.py 读取ini配置文件类封装
- get_yaml.py 读取yaml数据类封装
- Log.py log模块封装
- send_email.py 发送邮件模块封装
config 存放配置文件夹
- config.ini 存放全局配置数据
- setting_path.py 存放文件路径
local_data 存放定位元素数据文件夹
Log 存放日志文件
page_obj 存放页面操作类
- login_obj.py 封装登录界面的页面操作类
report 存放测试报告
screen_shop 存放截图
test_case 存放测试用例文件
test_data 存放测试用例数据文件
代码实现:
base负责封装页面的基础操作类,这里只填入了我当前页面的部分webdriver基础操作类,可以根据需要再进行添加;
base.py
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.Log import Get_log
from config.setting_path import screen_path
from selenium.common.exceptions import TimeoutException
import time
# 公用的基础操作类
class BasePage(object):
# 构造函数里面的参数就是类的所有参数
def __init__(self,driver,url):
self.driver = driver
self.url = url
self.log = Get_log()
# 打开对应的网页
def open_browser(self):
self.driver.get(self.url)
#查找元素,传入方法和元素两个参数
def wait_element(self,ele):
try:
WebDriverWait(self.driver,5,0.5).until(EC.visibility_of_element_located(ele))
return True
except TimeoutException:
self.log.log_error("没有定位到元素{0}".format(ele))
return False
except Exception as e:
raise e
def find_ele(self, ele):
if self.wait_element(ele):
try:
return self.driver.find_element(*ele)
except Exception as e:
raise e
else:
self.log.log_error("没有定位到元素")
return False
# 点击定位的元素
def click(self,div_click):
self.find_ele(div_click).click()
# 输入内容
def send_key(self,div,data):
self.find_ele(div).send_keys(data)
# 获取定位元素文本内容
def get_text(self,ele):
return self.find_ele(ele).text
# 获取当前网页url地址
def get_curr_url(self):
url = self.driver.current_url
return url
# 截图
def get_screen_shot(self):
current_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
file_name = screen_path + '\\' + current_time + '.png'
self.driver.get_screenshot_as_file(file_name)
self.driver.save_screenshot(file_name)
driver.py
from selenium import webdriver
def driver():
driver = webdriver.Chrome()
driver.implicitly_wait(3)
driver.maximize_window()
return driver
get_config.py
import configparser
from config import setting_path
class Get_config():
def __init__(self):
self.path = setting_path.conf_path
self.conf = configparser.ConfigParser()
self.conf.read(self.path)
#读取配置文件
def read_config(self,option,section):
data = self.conf.get(option,section)
return data
# 写入配置
def write_config(self,section,option,value):
self.conf.add_section(section)
self.conf.set(section,option,value)
# 修改文件后需要写入保存
self.conf.write(open(self.path,"w"))
def remove_config(self,section):
self.conf.remove_section(section)
# 修改文件后需要写入保存
self.conf.write(open(self.path, "w"))
get_yaml.py
import yaml
from config.setting_path import case_data
class Get_yaml_data():
def __init__(self,file_path):
self.path = file_path
# 读取yaml中所有数据,返回字典格式
def get_all_data(self):
with open(self.path,encoding="utf-8") as f:
data = yaml.load(f,Loader=yaml.FullLoader)
return data
# 获取元素
def get_element(self,i):
data = self.get_all_data()
return data["testcase"][i]["element_info"]
# 定位元素的方法
def find_type(self,i):
return self.get_all_data()["testcase"][i]["find_type"]
# 执行操作
def operate_type(self,i):
return self.get_all_data()["testcase"][i]["operate_type"]
log.py
import logging,os,time
from config.setting_path import log_path
class Get_log():
def __init__(self):
# 创建一个记录器
self.log = logging.getLogger()
# 设置记录器记录的BUG等级
self.log.setLevel(level=logging.INFO)
# 定义一个格式化输出的格式
self.formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
# log保存的路径及格式,每运行一次就根据时间创建一个log文件,会产生较多log文件
# self.log_path = os.path.join(log_path,'%s.log'%time.strftime('%Y-%m-%d %H_%M_%S'))
# 写死log文件名,直接在原本的基础上添加新的log数据
self.log_path = os.path.join(log_path,"log_result.log")
# 私有化,外部无法调用
def __get_log(self,level,message):
#创建一个handler,处理器,有FileHandler和StremaHandler,分别输出log到文件或者控制台
fh = logging.FileHandler(self.log_path,encoding="utf-8")
#设置handler的输出等级
fh.setLevel(logging.INFO)
#设置handler的输出格式
fh.setFormatter(self.formatter)
#把handler添加到记录器
self.log.addHandler(fh)
#创建一个输出log信息到控制台的StreamHandler
sh = logging.StreamHandler()
sh.setLevel(logging.INFO)
sh.setFormatter(self.formatter)
# 把这个处理器也添加到记录器中,一个记录器可以存在多个处理器,这样log文件记录的同时屏幕也会输出log信息
self.log.addHandler(sh)
#判断并输出对应的log信息
if level == "debug":
self.log.debug(message)
elif level == "info":
self.log.info(message)
elif level == "warning":
self.log.warning(message)
elif level == "error":
self.log.error(message)
elif level == "critical":
self.log.critical(message)
# 调用后需要移除处理器,否则会导致执行多个用例后输出重复的问题
self.log.removeHandler(sh)
self.log.removeHandler(fh)
#关闭log文件
fh.close()
# 创建对应的log输出函数
def log_debug(self,message):
self.__get_log("debug",message)
def log_info(self,message):
self.__get_log("info",message)
def log_warning(self,message):
self.__get_log("warning",message)
def log_error(self,message):
self.__get_log("error",message)
def log_critical(self,message):
self.__get_log("critical",message)
email模块我实际上没有去使用这里就不放了;
下面是元素定位文件的数据格式
Login_local_data.yaml
testinfo:
- id: test_login001
title: 小米帐号 - 登录
info: 打开抽屉首页
testcase:
- element_info: username
find_type: ID
operate_type: send_keys
info: 账号输入框
- element_info: pwd
find_type: ID
operate_type: send_keys
info: 密码输入框
- element_info: login-button
find_type: ID
operate_type: click
info: 登录按钮
- element_info: error-con
find_type: class
operate_type: text
info: 密码错误文本内容
- element_info: na-num
find_type: class
operate_type: text
info: 登录成功后个人账户信息
- element_info: logoutLink
find_type: ID
operate_type: click
info: 退出登录按钮
登录页面操作类的封装,每一个页面要封装一个页面操作类,此处是登录页面所以封装一个登录页面的操作
login_obj.py
from selenium.webdriver.common.by import By
from common.base import BasePage
from selenium import webdriver
from config.setting_path import yaml_local_path
from common.get_yaml import Get_yaml_data
file_path = yaml_local_path + "/" + "Login_local_data.yaml"
# 定位元素方法库
Testdata = Get_yaml_data(file_path)
# 登录操作的类
class LoginPage(BasePage):
# 账号
account_box = (By.ID, Testdata.get_element(0))
# 密码
password_box = (By.ID, Testdata.get_element(1))
# 登录按钮
click_btn = (By.ID, Testdata.get_element(2))
# 密码错误文本内容
error_pwd = (By.CLASS_NAME,Testdata.get_element(3))
# 登录成功后个人账户
account_num = (By.CLASS_NAME,Testdata.get_element(4))
# 退出登录按钮
logout_btn = (By.ID,Testdata.get_element(5))
# 打开浏览器
def login_index(self):
self.open_browser()
# 输入账号
def input_account(self,account):
self.send_key(self.account_box,account)
# 输入密码
def input_pwd(self,password):
self.send_key(self.password_box,password)
# 点击登录按钮
def click_login_btn(self):
self.click(self.click_btn)
#检查提示信息
def check_error_text(self):
return self.get_text(self.error_pwd)
# 获取账号信息
def get_account_num(self):
return self.get_text(self.account_num)
# 退出登录
def click_logout_btn(self):
self.click(self.logout_btn)
用例数据,使用数据驱动的方式去执行用例;
Login_data.yaml
-
id: login_01
detail : 正确登录
status : login_success
data:
account: "username"
pwd: "password"
check :
- "user_number"
-
id: login_02
detail : 输入错误的账号
status : login_fail
data:
account: "123456"
pwd: "xxxx"
check :
- 用户名或密码不正确
-
id: login_03
detail : 账号输入为空
status : login_fail
data:
account: ""
pwd: "123456"
check :
- 请输入帐号
-
id: login_04
detail : 密码输入为空
status : login_fail
data:
account: "123456"
pwd: ""
check :
- 请输入密码
登录测试用例
test_login.py
import pytest,sys,allure
# 把项目运行目录添加到path中,不加的话在terminal上运行会报错
sys.path.append('../')
from common.get_yaml import Get_yaml_data
from config.setting_path import case_data
from common.Log import Get_log
login_case_path = case_data + "/" + "Login_data.yaml"
login_case = Get_yaml_data(login_case_path).get_all_data()
# 登录用例的封装
@allure.feature("测试用例运行")
class Test_CaseRun():
# 测试步骤
@allure.story("测试登录")
@allure.title("登录用例")
@pytest.mark.parametrize("case_data",login_case)
def test_search(self,case_data,open_baidu):
log = Get_log()
# 打开登录页面
open_baidu.login_index()
log.log_info("执行用例:{0}".format(case_data["id"]))
if case_data["status"] == "login_success":
open_baidu.input_account(case_data["data"]["account"])
open_baidu.input_pwd(case_data["data"]["pwd"])
open_baidu.click_login_btn()
account_num = open_baidu.get_account_num()
try:
assert account_num == case_data["check"][0]
log.log_info("登录成功")
log.log_info("用例: {0}成功运行".format(case_data["id"]))
# 退出登录状态
open_baidu.click_logout_btn()
except Exception as e:
log.log_error("登录失败,原因{0}".format(e))
log.log_error("用例: {0}运行失败".format(case_data["id"]))
open_baidu.get_screen_shot()
raise e
else:
open_baidu.input_account(case_data["data"]["account"])
open_baidu.input_pwd(case_data["data"]["pwd"])
open_baidu.click_login_btn()
# 获取登录失败时的提示文本信息
error_text = open_baidu.check_error_text()
try:
assert error_text == case_data["check"][0]
log.log_info("断言成功")
log.log_info("用例: {0}成功运行".format(case_data["id"]))
except Exception as e:
log.log_error("{0}与{1}不一致".format(error_text,case_data["check"][0]))
log.log_error("用例: {0}运行失败".format(case_data["id"]))
# 断言失败时进行截图操作
open_baidu.get_screen_shot()
raise e
使用pytest去运行用例并生成allure报告
pytest -s --alluredir ../report/tmp/ #生成allure json文件
allure generate ../report/tmp -o ../report/allure_result --clean #生成html报告文件
执行用例完成并生成测试报告完成后可以打开html文件查看结果
此项目代码仅供学习参考,整体项目框架完善度不高,仅供个人学习记录使用;
项目源码地址: https://gitee.com/HZongJie/Auto_UI.git