前段时间记录了一下pytest接口自动化测试,今天来记录pytest+allureUI自动化了 ,还是直接上代码吧。
目录
case#存放测试用例 common#存放测试公共方法 data#存放测试数据、定位元素 logs#存放日志文件 pages#存放页面页面元素,操作步骤 report#存放测试报告 getpathinfo.py#读取当前目录 pytest#pytest配置文件 requirements.txt#依赖包
学习框架
pytest单元测试框架+allure生成测试报告
结构设计
1.每一个页面的所有用例组合在一个测试类里面生成一个py文件
2.将每个页面用例的操作步骤封装在一个测试类里面生成一个py文件
3.将测试数据,定位元素存放在yml文件中
4.通过allure生成测试报告
学习内容
1.pytes单元测试框架
2.allure生成测试报告
3.yml文件存放测试数据通过parametrize进行参数化
4.无界面运行测试用例
练习代码
getpathinfo.py #获取当前路径
import os def get_path(): curpath = os.path.dirname(os.path.realpath(__file__)) return curpath if __name__ == '__main__': print("测试路径",get_path())
pytest.ini #存放pytest配置文件
#pytest.ini [pytest] #addopts = -v --reruns 1 --html=./report/report.html --self-contained-html #addopts = -v --reruns 1 --alluredir ./report/allure_raw #addopts = -v -s -p no:warnings --reruns 1 --pytest_report ./report/Pytest_Report.html
requirements.txt #存放导入依赖包(pip install -r requirements.txt安装依赖包)
allure-pytest==2.8.15 allure-python-commons==2.8.15 appdirs==1.4.4 APScheduler==3.6.3 atomicwrites==1.3.0 attrs==19.3.0 BeautifulReport==0.1.2 beautifulsoup4==4.8.1 black==19.10b0 certifi==2019.9.11 cffi==1.14.0 chardet==3.0.4 Click==7.0 colorama==0.4.3 colorlog==4.1.0 crypto==1.4.1 cryptography==2.9.2 filetype==1.0.7 Flask==1.1.1 har2case==0.3.1 HttpRunner==1.5.8 idna==2.8 importlib-metadata==1.6.0 itsdangerous==1.1.0 Jinja2==2.10.3 jmespath==0.9.5 loguru==0.4.1 lxml==4.5.0 MarkupSafe==1.1.1 more-itertools==8.2.0 Naked==0.1.31 numpy==1.17.4 packaging==20.3 parameterized==0.7.1 ParamUnittest==0.2 pathspec==0.8.0 Pillow==6.2.1 pluggy==0.13.1 py==1.8.1 pycparser==2.20 pycryptodome==3.9.4 pydantic==1.5.1 Pygments==2.6.1 PyMySQL==0.9.3 pyparsing==2.4.7 Pypubsub==4.0.3 pytest==5.4.2 pytz==2019.3 pywin32==227 PyYAML==5.1.2 regex==2020.5.14 requests==2.22.0 requests-toolbelt==0.9.1 robotframework==3.1.2 robotframework-ride==1.7.4.1 schedule==0.6.0 selenium==3.141.0 send-email==201212 shellescape==3.4.1 six==1.13.0 soupsieve==1.9.5 toml==0.10.1 typed-ast==1.4.1 tzlocal==2.0.0 urllib3==1.25.7 wcwidth==0.1.9 Werkzeug==0.16.0 win32-setctime==1.0.1 wxPython==4.0.7.post2 xlrd==1.2.0 zipp==3.1.0
common/base.py #存放公共方法
from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.select import Select from selenium.webdriver.common.action_chains import ActionChains from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoSuchFrameException from selenium.common.exceptions import StaleElementReferenceException from selenium.common.exceptions import ElementNotVisibleException from selenium.common.exceptions import TimeoutException from common.log import Log class LocatorTypeError(Exception): pass class ElementNotFound(Exception): pass class Base(): '''基于原生selenium二次封装''' log = Log() def __init__(self,driver:webdriver.Chrome,timeout = 10,t = 0.5): url = "http://********" self.base_url = url self.driver = driver self.timeout = timeout self.t = t def find(self, locator): """定位到元素,返回元素对象,没定位到,Timeout异常""" if not isinstance(locator, tuple): raise LocatorTypeError("参数类型错误,locator必须是元祖类型:loc = ('id','value1')") else: self.log.info("正在定位元素信息:定位方式->%s,value值->%s" % (locator[0], locator[1])) #print("正在定位元素信息:定位方式->%s,value值->%s" % (locator[0], locator[1])) try: ele = WebDriverWait(self.driver, self.timeout, self.t).until(EC.presence_of_element_located(locator)) except TimeoutException as msg: raise ElementNotFound("定位元素出现超时!") return ele def finds(self,locator): '''复数定位,返回elements对象 list''' if not isinstance(locator,tuple): raise LocatorTypeError('参数类型错误,locator必须是元组类型:loc = ("id","value")') else: self.log.info("正在定位元素信息:定位方式->%s,value值->%s" % (locator[0], locator[1])) #print("正在定位元素信息:定位方式->%s,value值->%s"%(locator[0],locator[1])) try: eles = WebDriverWait(self.driver, self.timeout, self.t).until(EC.presence_of_all_elements_located(locator)) except TimeoutException as msg: raise ElementNotFound("定位元素出现超时!") return eles def writein(self,locator,text = ""): '''写入文本''' ele = self.find(locator) if ele.is_displayed(): ele.send_keys(text) else: raise ElementNotVisibleException("元素不可见或者不唯一无法输入") def click(self,locator): '''点击元素''' ele = self.find(locator) if ele.is_displayed(): ele.click() else: raise ElementNotVisibleException("元素不可见或者不唯一无法点击") def clear(self,locator): '''清空输入框文本''' ele = self.find(locator) if ele.is_displayed(): ele.clear() else: raise ElementNotVisibleException("元素不可见或者不唯一") def is_selected(self,locator): '''判断元素是否被选中,返回bool值''' ele = self.find(locator) r = ele.is_selected() return r def is_element_exist(self,locator): '''是否找到''' try: self.find(locator) return True except : return False def is_title(self,title = ""): '''返回bool值''' try: result = WebDriverWait(self.driver,self.timeout,self.t).until(EC.title_is(title)) return result except : return False def is_title_contains(self, title=''): """返回bool值""" try: result = WebDriverWait(self.driver, self.timeout, self.t).until(EC.title_contains(title)) return result except: return False def is_text_in_element(self,locator,text = ''): '''返回bool值''' if not isinstance(locator,tuple): raise LocatorTypeError("参数类型错误,locator必须是元祖类型:loc = ('id','value1')") try: result = WebDriverWait(self.driver, self.timeout, self.t).until( EC.text_to_be_present_in_element(locator, text)) return result except : return False def is_value_in_element(self,locator,value = ""): if not isinstance(locator, tuple): raise LocatorTypeError("参数类型错误,locator必须是元祖类型:loc = ('id','value1')") try: result = WebDriverWait(self.driver, self.timeout, self.t).until( EC.text_to_be_present_in_element_value(locator, value)) return result except: return False def is_alert(self,timeout = 8): try: result = WebDriverWait(self.driver, timeout, self.t).until(EC.alert_is_present()) return result except: return False def get_title(self): """获取title""" return self.driver.title def get_text(self, locator): """获取文本""" if not isinstance(locator, tuple): raise LocatorTypeError("参数类型错误,locator必须是元祖类型:loc = ('id','value1')") try: t = self.find(locator).text return t except: self.log.info("获取text失败,返回''") #print("获取text失败,返回''") return "" def get_attribute(self, locator, name): """获取属性""" if not isinstance(locator, tuple): raise LocatorTypeError("参数类型错误,locator必须是元祖类型:loc = ('id','value1')") try: element = self.find(locator) return element.get_attribute(name) except: self.log.info("获取%s属性失败,返回''" % name) #print("获取%s属性失败,返回''" % name) return '' def js_focus_element(self,locator): '''聚焦元素''' if not isinstance(locator,tuple): raise LocatorTypeError("参数类型错误") target = self.find(locator) self.driver.execute_script("arguments[0].scrollIntoView();", target) def js_scroll_top(self): '''滚到顶部''' js = "window.scrollTo(0,0)" self.driver.execute_script(js) def js_scroll_end(self,x = 0): '''滚到底部''' js = "window.scrollTo(%s, document.body.scrollHeight)" % x self.driver.execute_script(js) def select_by_index(self,locator,index =0): '''通过索引,index是索引第几个,从0开始,默认第一个''' if not isinstance(locator,tuple): raise LocatorTypeError("参数类型错误") element = self.find(locator) Select(element).select_by_index(index) def select_by_value(self, locator, value): """通过value属性""" if not isinstance(locator, tuple): raise LocatorTypeError("参数类型错误") element = self.find(locator) Select(element).select_by_value(value) def select_by_text(self,locator,text): """通过文本值定位""" element = self.find(locator) Select(element).select_by_visible_text(text) def switch_iframe(self, id_index_locator): """切换iframe""" try: if isinstance(id_index_locator, int): self.driver.switch_to.frame(id_index_locator) elif isinstance(id_index_locator, str): self.driver.switch_to.frame(id_index_locator) elif isinstance(id_index_locator, tuple): ele = self.find(id_index_locator) self.driver.switch_to.frame(ele) except: self.log.info("iframe切换异常") #print("iframe切换异常") def switch_handle(self,window_name): self.driver.switch_to.window(window_name) def switch_alert(self): r = self.is_alert() if not r: self.log.info("alert不存在") #print("alert不存在") else: return r def move_to_element(self, locator): """鼠标悬停操作""" if not isinstance(locator, tuple): raise LocatorTypeError("参数类型错误") ele = self.find(locator) ActionChains(self.driver).move_to_element(ele).perform() if __name__ == '__main__': driver = webdriver.Chrome() web = Base(driver) driver.get("https://www.baidu.com") loc_1 = ("id", "kw") web.writein(loc_1, "hello") driver.close()
common/log.py #生成日志文件
import logging,time import os import getpathinfo path = getpathinfo.get_path()#获取本地路径 log_path = os.path.join(path,'logs')# log_path是存放日志的路径 # 如果不存在这个logs文件夹,就自动创建一个 if not os.path.exists(log_path):os.mkdir(log_path) class Log(): def __init__(self): #文件的命名 self.logname = os.path.join(log_path,'%s.log'%time.strftime('%Y_%m_%d')) self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) #日志输出格式 self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') def __console(self,level,message): #创建一个fileHander,用于写入本地 fh = logging.FileHandler(self.logname,'a',encoding='utf-8') fh.setLevel(logging.DEBUG) fh.setFormatter(self.formatter) self.logger.addHandler(fh) #创建一个StreamHandler,用于输入到控制台 ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) ch.setFormatter(self.formatter) self.logger.addHandler(ch) if level == 'info': self.logger.info(message) elif level == 'debug': self.logger.debug(message) elif level == 'warning': self.logger.warning(message) elif level == 'error': self.logger.error(message) #避免日志重复 self.logger.removeHandler(fh) self.logger.removeHandler(ch) #关闭打开文件 fh.close() def debug(self,message): self.__console('debug',message) def info(self,message): self.__console('info',message) def warning(self,message): self.__console('warning',message) def error(self,message): self.__console('error',message) if __name__ == '__main__': log = Log() log.info('测试') log.debug('测试') log.warning('测试') log.error('测试')
common/read_yml.py #读取yml文件
import os import yaml import getpathinfo class ReadYaml(): def __init__(self,filename): path = getpathinfo.get_path()#获取本地路径 self.filepath = os.path.join(path,'data')+"/"+filename#拼接定位到data文件夹 def get_yaml_data(self): with open(self.filepath,'r',encoding='utf-8')as f: #调用load方法加载文件流 return yaml.load(f,Loader=yaml.FullLoader) if __name__ == '__main__': data = ReadYaml("login_page.yml").get_yaml_data() username = data["test_login_element"][0] print(tuple(username))
pages #存放测试页面操作步骤、断言(写了简单两个)
from common.base import Base from common.read_yml import ReadYaml testelement = ReadYaml("login_page.yml").get_yaml_data() class LoginPage(Base): loc1 = tuple(testelement["test_login_element"][0])#用户名 loc2 = tuple(testelement["test_login_element"][1])#密码 loc3 = tuple(testelement["test_login_element"][2])#登录 # 判断元素 loc4 = tuple(testelement["test_login_element"][3])#登录成功断言 loc5 = tuple(testelement["test_login_element"][4])#登录失败断言 def input_username(self, text="admin"): '''输入用户名''' self.writein(self.loc1, text) def input_password(self, text="yoyo123456"): '''输入用户名''' self.writein(self.loc2, text) def click_button(self): '''点击登录按钮''' self.click(self.loc3) def login(self, user="admin", password="yoyo123456"): '''登录''' self.driver.get(self.base_url) self.input_username(user) self.input_password(password) self.click_button() def is_login_success(self, expect_text='后台页面'): text = self.get_text(self.loc4) self.log.info("获取到断言元素的文本内容:%s"%text) return expect_text == text def is_login_fail(self, expect_text='请输入正确的用户名和密码'): text = self.get_text(self.loc5) self.log.info("获取到断言元素的文本内容:%s"%text) return expect_text in text if __name__ == '__main__': from selenium import webdriver driver = webdriver.Chrome() web = LoginPage(driver) web.login() result = web.is_login_fail() print("登录结果:", result) assert result driver.quit()
from common.base import Base from common.read_yml import ReadYaml testelement = ReadYaml("add_account_page.yml").get_yaml_data() class Add_Account(Base): loc1 = tuple(testelement["test_account_element"][0]) # 银行卡账户 loc2 = tuple(testelement["test_account_element"][1]) # 添加银行卡账户 loc3 = tuple(testelement["test_account_element"][2]) # 卡号 loc4 = tuple(testelement["test_account_element"][3]) # 姓名 loc5 = tuple(testelement["test_account_element"][4]) # 电话 loc6 = tuple(testelement["test_account_element"][5]) # 邮箱 loc7 = tuple(testelement["test_account_element"][6]) # 城市 loc8 = tuple(testelement["test_account_element"][7]) # 性别 loc9 = tuple(testelement["test_account_element"][8]) # 保存 loc10 = tuple(testelement["test_account_element"][9]) # 新增成功校验 loc11 = tuple(testelement["test_account_element"][10]) #账号为空校验 loc12 = tuple(testelement["test_account_element"][11]) #姓名为空校验 def click_account(self): '''点击银行卡账号''' self.finds(self.loc1)[0].click() def click_add_account(self): '''点击添加银行卡账号''' self.click(self.loc2) def input_card_num(self,text = "123456"): '''输入卡号''' self.writein(self.loc3,text) def input_name(self,text = " "): '''输入姓名''' self.writein(self.loc4,text) def input_phone(self,text = "123456"): '''输入电话''' self.writein(self.loc5,text) def input_mail(self,text = "123456"): '''输入邮箱''' self.writein(self.loc6,text) def input_city(self,text = "测试"): '''输入城市''' self.writein(self.loc7,text) def input_sex(self,text = "男"): '''输入性别''' self.writein(self.loc8,text) def click_save(self): '''点击保存''' self.click(self.loc9) def is_add_success(self, expect_text='添加成功'): text = self.get_text(self.loc10) self.log.info("获取到断言元素的文本内容:%s" %text) return expect_text in text def is_add_fail1(self, expect_text='这个字段是必须的'): '''账号为空断言''' text = self.get_text(self.loc11) self.log.info("获取到断言元素的文本内容:%s" %text) return expect_text in text def is_add_fail2(self, expect_text='这个字段是必须的'): '''姓名为空断言''' text = self.get_text(self.loc12) self.log.info("获取到断言元素的文本内容:%s" %text) return expect_text in text if __name__ == '__main__': from pages.login_page import LoginPage from selenium import webdriver driver = webdriver.Chrome() driver.maximize_window() web = LoginPage(driver) web.login() account = Add_Account(driver) account.click_account() account.click_add_account() account.input_card_num() account.input_name() account.input_phone() account.input_mail() account.input_city() account.input_sex() account.click_save() account.is_add_fail() driver.quit()
case/conftest.py #存放前置操作,设置无界面模式
import platform from selenium import webdriver from pages.login_page import LoginPage import pytest import time from common.log import Log from selenium.webdriver.chrome.options import Options log = Log() @pytest.fixture(scope="session") def login_fixtrue(driver): #登录前置操作 #driver = webdriver.Chrome() #driver.maximize_window() web = LoginPage(driver) web.login() return driver def pytest_addoption(parser): '''添加命令行参数''' parser.addoption('--headless', action = "store", default = 'no', help = 'set chrome headless option yes or no' ) @pytest.fixture(scope="session") def driver(request): """定义全局driver fixture,给其它地方作参数调用""" if platform.system()=='Windows': chrome_options = Options() chrome_options.add_argument('--window-size=1920,1080') # 设置当前窗口的宽度,高度 chrome_options.add_argument('--headless') # 无界面 print("当前运行的操作系统为windows") _driver = webdriver.Chrome(chrome_options=chrome_options) else: print('当前运行的操作系统为linux') chrome_options = Options() chrome_options.add_argument('--window-size=1920,1080') # 设置当前窗口的宽度,高度 chrome_options.add_argument('--no-sandbox')#解决DevToolsActivePort文件不存在报错问题 chrome_options.add_argument('--disable-gpu')#禁用GPU硬件加速,如果软件渲染器没有就位,则GPU进程将不会启动 chrome_options.add_argument('--disable-dev-shm-usage') chrome_options.add_argument('--headless') # 无界面 _driver = webdriver.Chrome(chrome_options=chrome_options) def end(): print("全部用例执行完后 teardown quit dirver") time.sleep(5) _driver.quit() request.addfinalizer(end) return _driver
case/test_login.py #存放登录页面测试用例
import allure import pytest from common.log import Log from common.read_yml import ReadYaml from pages.login_page import LoginPage from selenium import webdriver testdata = ReadYaml('login_page.yml').get_yaml_data()#读取数据 class Test_login(): log = Log() @allure.feature("功能点:用户登录页面") @allure.story("用例:用户登录") @pytest.mark.parametrize("username,password,msg",testdata["test_login_success_data"], ids = ["正确用户名密码登录"]) @pytest.mark.skip('跳过该成功用例') def test_success_login(self,driver,username,password,msg): #driver = webdriver.Chrome() web = LoginPage(driver) web.login(user=username,password=password) result = web.is_login_success(expect_text=msg) self.log.info("登录结果:%s"%result) assert result #driver.quit() @allure.feature("功能点:用户登录页面") @allure.story("用例:用户登录") @pytest.mark.parametrize("username,password,msg", testdata["test_login_fail_data"], ids=["正确用户名错误密码登录", "错误用户名正确密码登录"]) def test_fail_login(self,driver,username,password,msg): #driver = webdriver.Chrome() web = LoginPage(driver) web.login(user=username,password=password) result = web.is_login_fail(expect_text=msg) self.log.info("登录结果:%s"%result) assert result
case/test_add_account.py #存放添加账户页面测试用例
import allure import pytest from common.log import Log from common.read_yml import ReadYaml from pages.add_account_page import Add_Account testdata = ReadYaml('add_account_page.yml').get_yaml_data()#读取测试数据 class Test_Add_Account(): log = Log() @allure.feature("功能点:添加银行卡账户") @allure.story("用例:添加银行卡账户") @pytest.mark.parametrize("card_num,name,phone,mail,city,sex,msg",testdata["test_add_account_data1"], ids=["正常添加"]) def test_add_account(self, login_fixtrue,card_num, name, phone,mail,city,sex,msg): driver = login_fixtrue account = Add_Account(driver) with allure.step("点击银行卡账户,跳转添加账户页面"): account.click_account() with allure.step("点击添加账户按钮,进入编辑页面"): account.click_add_account() with allure.step("输入账户号"): account.input_card_num(text=card_num) with allure.step("输入姓名"): account.input_name(text=name) with allure.step("输入手机号"): account.input_phone(text=phone) with allure.step("输入邮箱"): account.input_mail(text=mail) with allure.step("输入城市"): account.input_city(text=city) with allure.step("输入性别"): account.input_sex(text=sex) with allure.step("点击保存"): account.click_save() with allure.step("获取结果: 获取页面实际结果,判断是否添加成功"): result = account.is_add_success(expect_text=msg) self.log.info("断言结果:%s"%result) with allure.step("断言:判断是否添加成功"): assert result == True #driver.quit() @allure.feature("功能点:添加银行卡账户") @allure.story("用例:添加银行卡账户") @pytest.mark.parametrize("card_num,name,phone,mail,city,sex,msg",testdata["test_add_account_data2"], ids=["账号为空添加"]) def test_add_account1(self, login_fixtrue,card_num, name, phone,mail,city,sex,msg): driver = login_fixtrue account = Add_Account(driver) with allure.step("点击银行卡账户,跳转添加账户页面"): account.click_account() with allure.step("点击添加账户按钮,进入编辑页面"): account.click_add_account() with allure.step("输入账户号"): account.input_card_num(text=card_num) with allure.step("输入姓名"): account.input_name(text=name) with allure.step("输入手机号"): account.input_phone(text=phone) with allure.step("输入邮箱"): account.input_mail(text=mail) with allure.step("输入城市"): account.input_city(text=city) with allure.step("输入性别"): account.input_sex(text=sex) with allure.step("点击保存"): account.click_save() with allure.step("获取结果: 获取页面实际结果,判断是否添加成功"): result = account.is_add_fail1(expect_text=msg) self.log.info("断言结果:%s"%result) with allure.step("断言:判断是否添加成功"): assert result == True #driver.quit() @allure.feature("功能点:添加银行卡账户") @allure.story("用例:添加银行卡账户") @pytest.mark.parametrize("card_num,name,phone,mail,city,sex,msg", testdata["test_add_account_data3"], ids=["姓名为空添加"]) def test_add_account2(self, login_fixtrue, card_num, name, phone, mail, city, sex, msg): driver = login_fixtrue account = Add_Account(driver) with allure.step("点击银行卡账户,跳转添加账户页面"): account.click_account() with allure.step("点击添加账户按钮,进入编辑页面"): account.click_add_account() with allure.step("输入账户号"): account.input_card_num(text=card_num) with allure.step("输入姓名"): account.input_name(text=name) with allure.step("输入手机号"): account.input_phone(text=phone) with allure.step("输入邮箱"): account.input_mail(text=mail) with allure.step("输入城市"): account.input_city(text=city) with allure.step("输入性别"): account.input_sex(text=sex) with allure.step("点击保存"): account.click_save() with allure.step("获取结果: 获取页面实际结果,判断是否添加成功"): result = account.is_add_fail2(expect_text=msg) self.log.info("断言结果:%s" % result) with allure.step("断言:判断是否添加成功"): assert result == True # driver.quit()
data/add_account_page.yml #存放添加账户页面测试数据,定位元素
test_account_element: - ["xpath",'//*[@href="/xadmin/hello/card/"]'] - ["xpath",'//*[@id="content-block"]/div[1]/div[2]/div/a'] - ["xpath",'//*[@id="id_card_id"]'] - ["xpath",'//*[@id="id_card_user"]'] - ["xpath",'//*[@id="id_carddetail-0-tel"]'] - ["xpath",'//*[@id="id_carddetail-0-mail"]'] - ["xpath",'//*[@id="id_carddetail-0-city"]'] - ["xpath",'//*[@id="id_carddetail-0-address"]'] - ["xpath",'//*[@id="card_form"]/div[2]/button'] - ["xpath",'//*[@id="content-block"]/div[2]'] - ["xpath",'//*[@id="error_1_id_card_id"]'] - ["xpath",'//*[@id="error_1_id_card_user"]'] test_add_account_data1: - ["123456","秋水1","123456","123456","测试","男","添加成功"] test_add_account_data2: - [" ","秋水2","123456","123456","测试","男","这个字段是必须的"] test_add_account_data3: - ["123456"," ","123456","123456","测试","男","这个字段是必须的"]
report/allure_raw #测试报告(运行命令:allure serve report/allure_raw)
记录完毕,生成allure报告在pytest.ini中有写,解开注释即可,pytest运行生成报告,或者pytest --alluredir ./report/allure_raw