底层实现原理(简易版):
代码示例:
1.先封装浏览器,创建单例模式(无论怎样,类里只会创建一个实例对象)
from selenium import webdriver
class Browser(object):
"""单例浏览器的类"""
__instance = None # 实例对象 #原始不存在 #单例模式:确保某一个类只有一个实例存在。即,无论创建多少个实例对象,都是同一个对象。
driver = None # 浏览器driver
def __new__(cls, browser_name, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls) #创建私有实例对象
# 打开浏览器
if browser_name in ("chrome", "Chrome", "gc", "谷歌"):
cls.driver = webdriver.Chrome() # 驱动在默认的python目录下,也可以实现指定项目的驱动文件
elif browser_name in ("firefox", "ff", "火狐"):
cls.driver = webdriver.Firefox()
else:
raise TypeError(f"你传入的浏览器类型 [ browser_name ] 不受支持")
return cls.__instance
# 关闭浏览器-->并且要结束单例模式
@classmethod
def close_all_browsers(cls):
if cls.driver is not None: # 不是空的时候才关闭,防止多次调用。
cls.driver.quit()
cls.driver = None
cls.__instance = None # 解除单例
2.创建basepage,指定浏览器,封装元素 *** 作和去网页
import time
import allure
from rtest.browser.browser import Browser #导入browser里面的类。
from selenium.webdriver.support.select import Select
class BasePage(object):
"""提供所有页面 *** 作的公共方法-->类似SeleniumLibrary"""
def __init__(self):
"""拿到driver"""
self.instance_browser=Browser("chrome") #创建浏览器实例
self.dr = self.instance_browser.driver #实现driver
def input_text(self, locator, text, clear=True): #两个参数,一个布尔做判断。
"""
定位元素,输入文本
:param locator: 定位器,例如("xpath","//input")、("id","username")
:param text: 输入的文本
:param clear: 默认清空
:return: None
"""
ele = self.dr.find_element(*locator) #因为元素通常是('id','name')这种格式的,localtor是个元组,要解包
if clear is True:
ele.clear()
ele.send_keys(text)
@allure.step("点击按钮")
def click_button(self, locator):
"""点击按钮"""
self.dr.find_element(*locator).click()
@allure.step("断言")
def element_text_should_be(self, locator, expected):
ele = self.dr.find_element(*locator)
assert expected in ele.text
@allure.step("去往一个url地址 {url}")
def goto_location(self,url):
"""去往一个url地址"""
self.dr.get(url)
@allure.step("清空输入框的文本",locotor)
def clear_text(self)
self.dr.find_element(*locotor).clear()
@allure.step("设置隐式等待 {implicitly_wait}")
def set_driver(self,implicitly_wait):
self.dr.maximize_window()
self.dr.implicitly_wait(implicitly_wait)
@allure.step("关闭浏览器")
def close_all_browsers(self):
filename=fr"F:\tester\image\screenshot\{int(time.time())}.png"
self.dr.get_screenshot_as_file(filename)
allure.attach.file(filename,"关闭前截图",attachment_type=allure.attachment_type.PNG)
#allure添加附件(allure.attach) ,语法:allure.attach(body, name, attachment_type, extension),对测试用例、测试步骤以及fixture的结果加以补充。
self.instance_browser.close_all_browsers()#结束单例模式
@allure.step("点击文本链接")
def click_link_text(self,text):
self.dr.find_element("link text",text).click()
@allure.step("按下拉列表的文本选择")
def select_from_list_by_label(self,locator,lable):
"""
根据参入的lable文本进行下拉列表的选择
:param locator: select 的定位器
:param lable: 根据文本选择
:return: None
"""
Select(self.dr.find_element(*locator)).select_by_visible_text(lable)
@def step("js查找元素并传入值")
def execute_javascript_date(self,locator,date):
"""执行js脚本"""
self.dr.execute_script('arguments[0].value=arguments[1]',self.dr.find_element(*locator),date)
#js *** 作,改值
def get_table_cell_text(self,locator:tuple,row,column):
"""给定第几行第几列都能查的出来
locator='xpath','//table[@]/tbody/tr[{row}]/td[{column}]'
row=1
column=2
expected=15888888888
"""
#第一步:修改定位器中的行的列
locator=list(locator)#变为列表即可修改
locator[1]=locator[1].replace('{row}',row)#替换行
locator[1] = locator[1].replace('{column}', column) # 替换列
cell_text=self.dr.find_element(*locator).text #获取指定行和列的值
return cell_text
3.用例page,以loginpage为例。传入元素。
import allure
from test.pages.basepage import BasePage
class LoginPage(BasePage):
"""很多页面的 *** 作,依赖basepage库---》继承"""
login_url="http://192.163.171.1:8080/abcbank"
username_locator = ("id", "username")
password_locator = ("id", "password")
verifycode_locator = ("id", "verifycode")
login_button_locator = ("xpath", '//button[@onclick="doLogin(\'null\')"]')
@allure.step("去往登录页面")
def goto_login_page(self):
"""去往登录页面"""
self.set_driver(5) # 设置浏览器
self.goto_location(self.login_url)
@allure.step("输入用户名 {text}")
def input_username(self, text):
"""输入用户名"""
self.input_text(self.username_locator, text) # 调用basepage库中的输入动作
@allure.step("输入密码 {text}")
def input_password(self, text):
"""输入密码"""
self.input_text(self.password_locator, text) # 调用basepage库中的输入动作
@allure.step("输入验证码 {text}")
def input_verifycode(self, text):
"""输入验证码"""
self.input_text(self.verifycode_locator, text) # 调用basepage库中的输入动作
@allure.step("点击登录按钮")
def click_login_button(self):
"""点击登录按钮"""
self.click_button(self.login_button_locator) # 调用basepage库中的输入动作
断言界面:
import allure
from rtester.pages.basepage import BasePage
class SellPage(BasePage):
logout_locator = "xpath", '//a[@href="/abcbank/user/logout"]'
customer_link_text="会员管理"
@allure.step("判断注销文本存在")
def assert_logout_text_should_expect(self):
"""判断注销文本存在"""
self.element_text_should_be(self.logout_locator,"注销")
@allure.step("去往会员管理页面")
def goto_customer(self):
"""去往会员管理页面"""
self.click_link_text(self.customer_link_text)
4.用例(case)界面,传入文本等参数。
import pytest
from rtester.pages.login.loginpage import LoginPage
from rtester.pages.home.sellpage import SellPage
class TestLogin(object):
"""拿登录页面,sell 页面 --- pytest框架"""
# def __init__(self): #不行的。---> pytest 里面不能有构造方法的。
# pass
def setup(self):
"""拿去页面"""
self.loginpage = LoginPage()
self.sellpage = SellPage()
def teardown(self):
self.sellpage.close_all_browsers() # 关闭浏览器
def test_login_success(self):
self.loginpage.goto_login_page() # 打开浏览器访问主页
self.loginpage.input_username("admin") # 调用页面的功能
self.loginpage.input_password("admin123")
self.loginpage.input_verifycode("0000")
self.loginpage.click_login_button()
self.sellpage.assert_logout_text_should_expect() # 调用页面的功能
进阶版:
举例:browser层,basepage层和上面简易模式一模一样。但因为有更多调用层(prettyallure装饰,包括步骤,标题,用例等级等的动态修饰;用例data的封装等等),使他们更加灵活和完美。
其中封装完的browser层,basepage层,common层,util层可以直接套用。其他层次也可以依葫芦画瓢。
执行顺序:主线:browser,封装浏览器----pages【basepage】,指定浏览器,定义元素 *** 作方法----pages【casepage】,用例界面,传入元素----{case调用caspage里的方法和data里的数据(data的数据调用路线:data【casedata.yaml】中放yaml数据----common【getcasedata】定义先将casedata.yaml数据拼接为自己本地的路径,再调用utils【doyaml】读取文件------case里面将getcasedata(路径)的数据作为pytest.mark.prameterize里的case的参数值。case调用prettyallure,取得模块中feature(模块名称),story(测试点),title(用例标题)的装饰。要传的测试的值放在step里面。再通过case.get(step).get('参数名')获取数据传参。}。
如果有需要常用的作为依赖的模块。(如传好参数的登录),可以封在business层中。作用类似于cookie
如果涉及到数据库,则要考虑config里面common里面设置config.yaml,设置数据库连接数字-----util层中,封装数据库连接和使用方法-----外层的config.py文件,引入doyaml,获得数据库中的数据。(类似于getcasedata,这里是获取的数据库的数据)
如果要更细致的话,可以将驱动器封装。
详解:
browser:封装浏览器
import time
#selenium 的版本是 4.1.0? pip show selenium
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from config import Config
class Browser(object):
"""单例浏览器的类"""
__instance = None # 实例对象
driver = None # 浏览器driver
def __new__(cls, browser_name, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
# 打开浏览器
if browser_name in ("chrome", "Chrome", "gc", "谷歌"):
cls.driver = webdriver.Chrome(service=ChromeService(Config.chrome_driver)) # 驱动在默认的python目录下,也可以实现指定项目的驱动文件
elif browser_name in ("firefox", "ff", "火狐"):
cls.driver = webdriver.Firefox(service=FirefoxService(Config.firefox_driver))
else:
raise TypeError(f"你传入的浏览器类型 { browser_name } 不受支持")
return cls.__instance
# 关闭浏览器-->并且要结束单例模式
@classmethod
def close_all_browsers(cls):
if cls.driver is not None: # 不是空的时候才关闭,防止多次调用。
cls.driver.quit()
cls.driver = None
cls.__instance = None # 解除单例
if __name__ == '__main__':
b1 = Browser("chrome") # 打开
time.sleep(2)
b1.close_all_browsers() # 实例调用类方法---> 可以的。
# 假设b1关闭,那么要新打开一个浏览器
b2 = Browser("ff") # 打开
# b3 = Browser("chrome")
# print(b1 is b2 is b3)
# b3.close_all_browsers() # 关闭
time.sleep(5)
b2.close_all_browsers()
pages【basepage】,指定浏览器,定义元素 *** 作方法
import time,os
import allure
from browser.browser import Browser
from selenium.webdriver.support.select import Select
from config import Config
class BasePage(object):
"""提供所有页面 *** 作的公共方法-->类似SeleniumLibrary"""
def __init__(self):
"""拿到driver"""
self.instance_browser = Browser("chrome")
self.dr = self.instance_browser.driver # 后面可以实现配置化
def input_text(self, locator, text, clear=True):
"""
定位元素,输入文本
:param locator: 定位器,例如("xpath","//input")、("id","username")
:param text: 输入的文本
:param clear: 默认清空
:return: None
"""
ele = self.dr.find_element(*locator)
if clear is True:
ele.clear()
ele.send_keys(text)
def click_button(self, locator):
"""点击按钮"""
self.dr.find_element(*locator).click()
def element_text_should_be(self, locator, expected):
ele = self.dr.find_element(*locator)
assert expected in ele.text
@allure.step("去往一个url地址 {url}")
def goto_location(self, url):
"""去往一个url地址"""
self.dr.get(url)
@allure.step("设置隐式等待 {implicitly_wait}")
def set_driver(self, implicitly_wait):
self.dr.maximize_window()
self.dr.implicitly_wait(implicitly_wait)
@allure.step("关闭浏览器")
def close_all_browsers(self):
filename = fr'{Config.root_path}\image\screenshot\{int(time.time())}.png"'
self.dr.get_screenshot_as_file(filename)
allure.attach.file(filename, "关闭前截图", attachment_type=allure.attachment_type.PNG)
self.instance_browser.close_all_browsers() # 结束单例模式
@allure.step("清空内容")
def clear_all_content(self,locotor):
self.dr.find_element(*locotor).clear()
def click_link_text(self, text):
self.dr.find_element("link text", text).click()
def select_from_list_by_label(self, locator, lable):
"""
根据参入的lable文本进行下拉列表的选择
:param locator: select 的定位器
:param lable: 根据文本选择
:return: None
"""
Select(self.dr.find_element(*locator)).select_by_visible_text(lable)
def execute_javascript_date(self, locator, date):
"""执行js脚本"""
self.dr.execute_script('arguments[0].value=arguments[1]', self.dr.find_element(*locator), date)
def get_table_cell_text(self, locator: tuple, row, column):
"""给定第几行第几列都能查的出来
locator='xpath','//table[@]/tbody/tr[{row}]/td[{column}]'
row=1
column=2
expected=15888888888
"""
# 第一步:修改定位器中的行的列
locator = list(locator) # 变为列表即可修改
locator[1] = locator[1].replace('{row}', row) # 替换行
locator[1] = locator[1].replace('{column}', column) # 替换列
cell_text = self.dr.find_element(*locator).text # 获取指定行和列的值
return cell_text
pages【casepage】,用例界面,传入元素
import allure
from pages.basepage import BasePage
class LoginPage(BasePage):
"""很多页面的 *** 作,依赖basepage库---》继承"""
login_url = "https://dsmall.csdeshang.com/home/Sellerlogin/login.html"
username_locator = ("name", "seller_name")
password_locator = ("name", "member_password")
verifycode_locator = ("id", "verifycode")
login_button_locator = ("xpath", '//input[@value="商家登录"]')
@allure.step("去往登录页面")
def goto_login_page(self):
"""去往登录页面"""
self.set_driver(5) # 设置浏览器
self.goto_location(self.login_url)
@allure.step("输入用户名 {text}")
def input_username(self, text):
"""输入用户名"""
self.input_text(self.username_locator, text) # 调用basepage库中的输入动作
@allure.step("输入密码 {text}")
def input_password(self, text):
"""输入密码"""
self.input_text(self.password_locator, text) # 调用basepage库中的输入动作
@allure.step("输入验证码 {text}")
def input_verifycode(self, text):
"""输入验证码"""
self.input_text(self.verifycode_locator, text) # 调用basepage库中的输入动作
@allure.step("点击登录按钮")
def click_login_button(self):
"""点击登录按钮"""
self.click_button(self.login_button_locator) # 调用basepage库中的输入动作
data--用例的yaml 前面一个-表示列表。 参数名后面记得跟空格,再填参数值。 下图中的content表示是step的下一级。
- skip: false
id: 1
feature: 客服管理
story: 客服设置
title: 新增客服在前台显示
serverity: Blocker
step:
content: 工作时间:AM10:00-PM 18:00
expect: 保存成功
utils.doyaml,定义读写数据的方法:
from yaml import load, dump
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
class DoYaml(object):
def __init__(self, filename):
self.filename = filename
def read(self):
with open(self.filename, "r", encoding="utf8") as f:
data = load(stream=f, Loader=Loader)
return data
def write(self, data):
with open(self.filename, "w", encoding="utf8") as f:
# data:写入的数据
# stream:准备写入的文件
# Dumper:写入器
# allow_unicode=True:允许中文
# sort_keys=False : 不排序
output = dump(data=data, stream=f, Dumper=Dumper, allow_unicode=True, sort_keys=False)
if __name__ == '__main__':
casedata = DoYaml("../data/usermanange/login_case.yaml")
print(casedata.read())
common【getcasedata】定义先将casedata.yaml数据拼接为自己本地的路径,再调用utils【doyaml】读取文件
from config import Config
import os
from utils.doyaml import DoYaml
class GetCaseData(object):
@classmethod
def get_case_data(cls, case_file):
root_path = Config.root_path # 拿到了
case_data_file = os.path.join(root_path, case_file) # 拼接文件路径
case_data = DoYaml(case_data_file).read() # 读取用例的数据
return case_data
if __name__ == '__main__':
print(GetCaseData.get_case_data("data/customer/customer_add_case.yaml"))
ommon【getcasedata】定义先将casedata.yaml数据拼接为自己本地的路径,再调用utils【doyaml】读取文件
from config import Config
import os
from utils.doyaml import DoYaml
class GetCaseData(object):
@classmethod
def get_case_data(cls, case_file):
root_path = Config.root_path # 拿到了
case_data_file = os.path.join(root_path, case_file) # 拼接文件路径
case_data = DoYaml(case_data_file).read() # 读取用例的数据
return case_data
if __name__ == '__main__':
print(GetCaseData.get_case_data("data/customer/customer_add_case.yaml"))
common.prettyallure,存allure的修饰语句:
import pytest, allure
class PrettyAllure(object):
@classmethod
def prettyallure(cls, case):
if case.get("skip") is True:
pytest.skip("用例数据指定跳过执行~~") # 跳过不执行
allure.dynamic.feature(case.get("feature")) # 用例模块 #dynamic表示动态修饰,能从case中拿到feature的数据后,放在@allure.feature里。
allure.dynamic.story(case.get("story")) # 测试点
allure.dynamic.severity(case.get("serverity"))#严重等级
allure.dynamic.title(f'编号_{case.get("id")}_{case.get("title")}') # 测试标题
在business层中放入快速登陆:
import time
from pages.login.bussinessloginpage import LoginPage
class FastLogin(object):
def __init__(self):
self.loginpage=LoginPage()
def user_fast_login(self):
self.loginpage.goto_login_page()
self.loginpage.input_username("buyer")
self.loginpage.input_password("123456")
self.loginpage.click_login_button()
if __name__ == '__main__':
FastLogin().user_fast_login()
case的最终呈现示例
# 需要登录
import time
import pytest
from business.fast_user_login import FastLogin
from pages.home.sellpage import SellPage
from pages.customer.customerpage import CustomerPage
from common.prettyallure import PrettyAllure
from common.getcasedata import GetCaseData
case_data = GetCaseData.get_case_data("data/customer/customer_add_case.yaml") # 目录是从根目录开始写。
class TestCustomerSearch(object):
def setup(self):
# 1.登录
FastLogin().admin_fast_login() # 使用admin快速登录
# 2.进入customer 页面
sellpage = SellPage()
sellpage.goto_customer()
# 3.获取customer页面
self.customerpage = CustomerPage()
def teardown(self):
self.customerpage.close_all_browsers()
@pytest.mark.parametrize("case", case_data)
def test_customer_search(self, case):
PrettyAllure.prettyallure(case) # 美化报告
search_phone = case.get("step").get("phone")
self.customerpage.input_phone(search_phone)
self.customerpage.input_customername("张三")
self.customerpage.choies_sex("女")
self.customerpage.input_childdate("1999-09-08")
self.customerpage.input_creditkids("80")
self.customerpage.input_creditcloth("70")
self.customerpage.click_search_button()
time.sleep(5)
self.customerpage.table_cell_row1_column2_should_be_customerphone(search_phone) # 断言
if __name__ == '__main__':
pytest.main(["-v", '-s', __file__])
runner
import pytest
import subprocess
allure_data_path = "report/allure_data"
allure_report_path = "report/allure_report"
pytest.main(["-v", "-s", f"--alluredir={allure_data_path}", "--clean-alluredir","cases"])
subprocess.run(f"allure generate {allure_data_path} --clean -o {allure_report_path}", shell=True,
universal_newlines=True)
:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)