发布时间:2023-11-20 08:30
二.测试地址
https://mail.126.com
三.测试范围
1.126电子邮箱登录功能测试-验证正确帐号密码登录成功-验证错误用户名密码登录失败(有很多情况,用例里面做了充分的校验)。
2.126电子邮箱添加联系人功能测试-验证正确填写必填项数据添加联系人成功-验证缺省必填项数据添加联系人失败-验证必填项字段数据格式错误添加联系人失败。
3.126电子邮箱发送邮件功能测试-验证普通邮件发送成功-验证带附件邮件发送成功。
四.项目设计
1.python编程语言设计测试脚本。
2.webdriver驱动浏览器并操作页面元素。
3.二次封装webdriver Api 操作方法。
4.采用PageObject设计模式,设计测试业务流程。
5.通过UI对象库存储页面操作元素。
6.通过数据文件存储数据,读取数据,参数化测试用例并驱动测试执行。
7.通过第三方插件pytest-html生成测试报告。
8.通过yagmail第三方库,编写发送报告接口,测试工作完成后自动发送测试报告。
六.代码实现
1.通过126邮箱测试范围分析,需要通过设计剪切板,模拟键盘完成附件上传操作。
clipboard.py-操作剪切板
# 定义登录输入框输入内容
import win32con
import win32clipboard as WC
from selenium import webdriver
class ClipBoard(object):
'''设置剪切板内容和获取剪切板内容'''
@staticmethod
def getText():
'''获取剪切板的内容'''
WC.OpenClipboard()
value = WC.GetClipboardData(win32con.CF_TEXT)
WC.CloseClipboard()
return value
@staticmethod
def setText(value):
'''设置剪切板的内容'''
WC.OpenClipboard()
WC.EmptyClipboard()
WC.SetClipboardData(win32con.CF_UNICODETEXT, value)
WC.CloseClipboard()
if __name__ == '__main__':
pass
'''
driver = webdriver.Chrome()
driver.maximize_window()
driver.get('http://www.baidu.com')
query = driver.find_element_by_id('kw')
value = u'python'
ClipBoard.setText(value)
clValue = ClipBoard.getText()
query.send_keys(clValue.decode('utf-8'))
'''
keyboard.py-模拟键盘
# 模拟按键键盘操作
import win32api
import win32con
import time
from selenium import webdriver
class KeyBoard(object):
""" 模拟按键 """
# 键盘码
vk_code = {
'enter' : 0x0D,
'tab' : 0x09,
'ctrl' : 0x11,
'v' : 0x56,
'a' : 0x41,
'x' : 0x58
}
@staticmethod
def keyDown(key_name):
"""按下键"""
key_name = key_name.lower()
try:
win32api.keybd_event(KeyBoard.vk_code[key_name], 0, 0, 0)
except Exception as e:
print(u'未按下enter键')
print(e)
@staticmethod
def keyUp(key_name):
"""抬起键"""
key_name = key_name.lower()
win32api.keybd_event(KeyBoard.vk_code[key_name], 0, win32con.KEYEVENTF_KEYUP, 0)
@staticmethod
def oneKey(key):
"""模拟单个按键"""
key = key.lower()
KeyBoard.keyDown(key)
time.sleep(2)
KeyBoard.keyUp(key)
@staticmethod
def twoKeys(key1, key2):
"""模拟组合按键"""
key1 = key1.lower()
key2 = key2.lower()
KeyBoard.keyDown(key1)
KeyBoard.keyDown(key2)
time.sleep(2)
KeyBoard.keyUp(key1)
KeyBoard.keyUp(key2)
if __name__ == '__main__':
pass
'''
driver = webdriver.Chrome()
driver.maximize_window()
driver.get('http://www.baidu.com')
driver.find_element_by_id('kw').send_keys(u'python')
time.sleep(3)
KeyBoard.twoKeys('ctrl', 'a')
time.sleep(3)
KeyBoard.twoKeys('ctrl', 'x')
'''
2.通过项目设计需要把测试数据存放在Excel文件中,把页面操作元素存在配置文件,那么需要对Excel和ini文件解析。
parseExcelFile.py-解析Excel文件
# 解析Excel文件data.xlsx
from openpyxl import load_workbook
from config.conf import excelPath
class ParseExcel(object):
def __init__(self):
self.wk = load_workbook(excelPath)
self.excelFile = excelPath
def getSheetByName(self, sheetName):
"""获取sheet对象"""
sheet = self.wk[sheetName]
return sheet
def getRowNum(self, sheet):
"""获取有效数据的最大行号"""
return sheet.max_row
def getColsNum(self, sheet):
"""获取有效数据的最大列号"""
return sheet.max_column
def getRowValues(self, sheet, rowNum):
"""获取某一行的数据"""
maxColsNum = self.getColsNum(sheet)
rowValues = []
for colsNum in range(1, maxColsNum + 1):
value = sheet.cell(rowNum, colsNum).value
if value is None:
value = ''
rowValues.append(value)
return tuple(rowValues)
def getColumnValues(self, sheet, columnNum):
"""获取某一列的数据"""
maxRowNum = self.getRowNum(sheet)
columnValues = []
for rowNum in range(2, maxRowNum + 1):
value = sheet.cell(rowNum, columnNum).value
if value is None:
value = ''
columnValues.append(value)
return tuple(columnValues)
def getValueOfCell(self, sheet, rowNum, columnNum):
"""获取某一个单元格的数据"""
value = sheet.cell(rowNum, columnNum).value
if value is None:
value = ''
return value
def getAllValuesOfSheet(self, sheet):
"""获取某一个sheet页的所有测试数据,返回一个元祖组成的列表"""
maxRowNum = self.getRowNum(sheet)
columnNum = self.getColsNum(sheet)
allValues = []
for row in range(2, maxRowNum + 1):
rowValues = []
for column in range(1, columnNum + 1):
value = sheet.cell(row, column).value
if value is None:
value = ''
rowValues.append(value)
allValues.append(tuple(rowValues))
return allValues
if __name__ == '__main__':
pass
'''
excel = ParseExcel()
sheet = excel.getSheetByName('login')
#print(u'总行号:',excel.getRowNum(sheet))
#print(u'总列号:',excel.getColsNum(sheet))
print(u'总行号%d'%excel.getRowNum(sheet))
print(u'总列号%d'%excel.getColsNum(sheet))
rowvalues = excel.getRowValues(sheet, 1)
print(u'第{}行数据{}'.format(1, rowvalues))
columnvalues = excel.getColumnValues(sheet, 2)
print(u'第{}列数据{}'.format(2, columnvalues))
valueofcell = excel.getValueOfCell(sheet, 1, 2)
print(u'{}和{}单元格的内容{}'.format(1, 2, valueofcell))
allvalues = excel.getAllValuesOfSheet(sheet)
print(u'login{}'.format(allvalues))
'''
'''
excel = ParseExcel()
sheet = excel.getSheetByName('mail')
print('行号:', excel.getRowNum(sheet))
print('列号:', excel.getColsNum(sheet))
allvalues = excel.getAllValuesOfSheet(sheet)
print(allvalues)
#print('sendmail{}'.format(allvalues))
'''
parseConFile.py-解析配置文件
# 解析配置文件ini
import configparser
from config.conf import configDir
class ParseConFile(object):
def __init__(self):
self.file = configDir
self.conf = configparser.ConfigParser()
self.conf.read(self.file, encoding='utf-8')
def getAllSections(self):
"""获取所有的section,返回一个列表"""
return self.conf.sections()
def getAllOptions(self, section):
"""获取指定section下所有的option, 返回列表"""
return self.conf.options(section)
def getLocatorsOrAccount(self, section, option):
"""获取指定section, 指定option对应的数据, 返回元祖和字符串"""
try:
locator = self.conf.get(section, option)
if ('->' in locator):
locator = tuple(locator.split('->'))
return locator
except configparser.NoOptionError as e:
print('error:', e)
return 'error: No option "{}" in section: "{}"'.format(option, section)
def getOptionValue(self, section):
"""获取指定section下所有的option和对应的数据,返回字典"""
value = dict(self.conf.items(section))
return value
if __name__ == '__main__':
pass
'''
cf = ParseConFile()
print(cf.getAllSections())
print(cf.getAllOptions('126LoginAccount'))
print(cf.getLocatorsOrAccount('126LoginAccount', 'username'))
print(cf.getOptionValue('126LoginAccount'))
'''
config.ini
[126LoginAccount];126邮箱正确的登录账号和密码;运行用例时请更换正确的用户名和密码
username=linuxxiaochao
password=xiaochao11520
[HomePageElements];126邮箱登录后首页菜单栏元素
account=xpath->//span[@id="spnUid"]
homePage=id->_mail_tabitem_0_133
mailList=id->_mail_tabitem_1_134text
applicationCenter=id->_mail_tabitem_2_135
inBox=id->_mail_tabitem_3_136
[LoginPageElements];126邮箱登录页面的元素(最后一个是登录提示语请先验证)
password_login_btn=xpath->//a[@id="switchAccountLogin"]
frame=xpath->//div[@id="loginDiv"]/iframe
username=xpath->//input[@name="email"]
password=xpath->//input[@name="password"]
loginBtn=xpath->//a[@id="dologin"]
errorHead=xpath->//div[@class="ferrorhead"]
ferrorHead=xpath->//div[@class="ferrorhead"]
[ContactPageElements];126邮箱通讯录添加联系人页面元素
new_contact=xpath->//span[text()="新建联系人"]
name=id->input_N
mail=xpath->//div[@id="iaddress_MAIL_wrap"]//input[@class="nui-ipt-input"]
star=xpath->//span[@class="nui-chk-text"]/preceding-sibling::span/b
#star=xpath->//span[text()="设为星标联系人"]
phone=xpath->//div[@id='iaddress_TEL_wrap']//input[@class='nui-ipt-input']
comment=id->input_DETAIL # 备注输入框
commit=xpath->//span[text()='确 定']
#commit=xpath->//span[contains(text(),'确 定')]
errortip=xpath->//span[text()='请正确填写邮件地址。']
[SendMailPageElements];126邮箱发送邮件页面元素
writeMail=xpath->//div[@id='dvNavContainer']//span[text()='写 信']
addressee=xpath->//input[@aria-label='收件人地址输入框,请输入邮件地址,多人时地址请以分号隔开']
#addressee=xpath->//div[@id='_mail_emailcontainer_0_667']
subject=xpath->//input[contains(@id, '_subjectInput')]
#subject=xpath->//input[@id='1653547574128_subjectInput']
iframe=xpath->//iframe[@class="APP-editor-iframe"]
text=xpath->/html/body
#iframe=xpath->//body/div[@id='dvContentContainer']/div[@id='dvContainer']/div[@id='_dvModuleContainer_compose.ComposeModule_0']/div[@id='1653547574128_main']/section[1]/section[1]/div[1]/div[1]/div[1]/div[2]/iframe[1]
sendBtn=xpath->//header//span[text()='发送'] # 左上角发送按钮
#sendBtn=xpath->//footer//span[text()='发送'] # 左下角发送按钮
send_success=xpath->//*[text()='发送成功']
expect=xpath->//h1[contains(@id,'_succInfo')]
uploadAttachment=xpath->//div[@title="添加超大附件"]
#uploadAttachment=xpath->//header/div[3]/div[1]/div[2]/input[1]
delete=xpath->//a[text()='删除']
#delete=xpath->//a[contains(text(),'删除')]
error_info_address_is_none=xpath->//div[@id='zoomMonitorDiv']/following-sibling::div/span[@class='nui-tips-text']
error_info_popup_window=xpath->//div[@class='nui-msgbox-title']
4.新建excel文件,分别为:login,contact,mail页签
5.通过PO模式编写每个页面的操作及封装126邮箱的功能。
BasePage.py-webdriver二次封装
# webdriver二次封装
import time
from selenium.webdriver.support.wait import WebDriverWait as wd
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchWindowException, TimeoutException, \
NoAlertPresentException, NoSuchFrameException
from util.clipboard import ClipBoard # 调用输入框剪切板操作方法
from util.keyboard import KeyBoard # 调用按键键盘操作方法
from util.parseConFile import ParseConFile # 调用读取配置文件的方法
from util.parseExcelFile import ParseExcel # 调用读取excel数据参数
class BasePage(object):
"""结合显示等待封装一些selenium 内置方法"""
# 对解析配置ini文件和解析Excel文件进行类的实例化
cf = ParseConFile()
excel = ParseExcel()
def __init__(self, driver, outTime=30):
# 定义一个定位表达方式字典
self.byDic ={
'id': By.ID,
'name': By.NAME,
'class_name': By.CLASS_NAME,
'xpath': By.XPATH,
'link_text': By.LINK_TEXT
}
self.driver = driver
# 定义outTime是显示等待定位的时间区值
self.outTime = outTime
# 定位单个元素
def findElement(self, by, locator):
try:
print('[Info:Starting find the element "{}" by "{}"!]'.format(locator, by))
element = wd(self.driver, self.outTime).until(lambda x : x.find_element(by, locator))
except TimeoutException as t:
print('error: found "{}" timeout!'.format(locator), t)
except NoSuchWindowException as e:
print('error: no such "{}"'.format(locator), e)
except Exception as e:
raise e
else:
#print('[Info:Had found the element "{}" by "{}"!]'.format(locator, by))
return element
# 定位一组元素
def findElements(self, by, locator):
try:
print('[Info:start find the elements "{}" by "{}"!]'.format(locator, by))
elements = wd(self.driver, self.outTime).until(lambda x : x.find_elements(by, locator))
except TimeoutException as t:
print('error: found "{}" timeout!'.format(locator), t)
except NoSuchWindowException as e:
print(e)
except Exception as e:
raise e
else:
#print('[Info:Had found the elements "{}" by "{}"!]'.format(locator, by))
return elements
# 使用定义的表达方式字典集,去显示等待定位操作元素,并返回定位表达方式
def isElementExsit(self, by, locator):
if by.lower() in self.byDic:
try:
wd(self.driver, self.outTime).until(EC.visibility_of_element_located((self.byDic[by], locator)))
except TimeoutException:
print('Error: element "{}" time out!'.format(locator))
return False
except NoSuchWindowException:
print('Error: element "{}" not exsit!'.format(locator))
return False
return True
else:
print('the "{}" error!'.format(by))
def isClick(self, by, locator):
"""判断是否可点击,返回元素对象"""
if by.lower() in self.byDic:
try:
element = wd(self.driver, self.outTime).until(EC.element_to_be_clickable((self.byDic[by], locator)))
except Exception:
print(u"元素不可以点击")
return False
else:
return element
else:
print('the "{}" error!'.format(by))
def isAlertAndSwitchToIt(self):
try:
re = wd(self.driver, self.outTime).until(EC.alert_is_present())
except NoAlertPresentException:
print("error:no found alert")
return False
except Exception:
return False
return re
def switchToFrame(self, by, locator):
"""判断frame是否存在,存在就跳到frame"""
print('info:switching to iframe "{}"'.format(locator))
if by.lower() in self.byDic:
try:
wd(self.driver, self.outTime).until(EC.frame_to_be_available_and_switch_to_it((self.byDic[by], locator)))
except TimeoutException as t:
print(u'error: found "{}" timeout!切换frame失败'.format(locator), t)
except NoSuchFrameException as e:
print('error: no such "{}"'.format(locator), e)
except Exception as e:
raise e
else:
print('the "{}" error!'.format(by))
def switchToDefaultFrame(self):
"""返回默认的frame"""
print('info:switch back to default iframe')
try:
self.driver.switch_to.default_content()
except Exception as e:
print(e)
def getAlertText(self):
"""获取alert的提示信息"""
# 调用isAlertAndSwitchToIt封装方法
if self.isAlertAndSwitchToIt():
alert = self.isAlertAndSwitchToIt()
return alert.text
else:
return None
def getElementText(self, by, locator, name=None):
"""获取某一个元素的text信息"""
try:
element = self.findElement(by, locator)
if name:
return element.get_attribute(name)
else:
return element.text
except:
print('get "{}" text failed return None'.format(locator))
return None
def loadUrl(self, url):
"""请求加载url"""
print('info: string upload url "{}"'.format(url))
self.driver.get(url)
def getSource(self):
"""获取页面源码"""
return self.driver.page_source
def sendKeys(self, by, locator, value=''):
"""输入框写数据"""
print('info:input "{}"'.format(value))
try:
element = self.findElement(by, locator)
element.send_keys(value)
except AttributeError as e:
print(e)
def clear(self, by, locator):
"""清理数据"""
print('info:clearing value')
try:
element = self.findElement(by, locator)
element.clear()
except AttributeError as e:
print(e)
def click(self, by, locator):
"""点击某个元素:使用上面的定义方法isClick先判断元素是否可以点击再操作"""
print('info:click "{}"'.format(locator))
element = self.isClick(by, locator)
if element:
element.click()
else:
print('the "{}" unclickable!')
@staticmethod
def sleep(self, num=0):
"""强制等待"""
print('info:sleep "{}" minutes'.format(num))
time.sleep(num)
# 调用封装的ClipBoard设置剪切板内容和获取剪切板内容方法和封装的KeyBoard模拟按键键盘操作方法
def ctrlV(self, value):
"""ctrl + V 粘贴"""
print('info:pasting "{}"'.format(value))
ClipBoard.setText(value)
self.sleep(3)
KeyBoard.twoKeys('ctrl', 'v')
# 调用封装的KeyBoard模拟按键键盘操作方法
@staticmethod
def enterKey(self):
"""enter 回车键"""
print('info:keydown enter')
KeyBoard.oneKey('enter')
def waitElementtobelocated(self, by, locator):
"""显示等待某个元素出现,且可见"""
print('info:waiting "{}" to be located'.format(locator))
try:
wd(self.driver, self.outTime).until(EC.visibility_of_element_located((self.byDic[by], locator)))
except TimeoutException as t:
print('error: found "{}" timeout!'.format(locator), t)
except NoSuchWindowException as e:
print('error: no such "{}"'.format(locator), e)
except Exception as e:
raise e
# 调用封装getSource获取页面源码方法,先获取页面源码再判断value
def assertValueInSource(self, value):
"""断言某个关键字是否存在页面源码中"""
print('info:assert "{}" in page source'.format(value))
source = self.getSource()
assert value in source, u'关键字"{}"不存在源码中!'.format(value)
def assertStringContainsValue(self, String, value):
"""断言某段字符串包含另一个字符串"""
print('info:assert "{}" contains "{}"'.format(String, value))
assert value in String, u'"{}"不包含"{}"!'.format(String, value)
# 调用封装parseExcelFile中的获取sheet对象方法,并使用对解析配置文件和解析Excel文件进行类的实例化excel
@staticmethod
def getSheet(sheetName):
"""获取某个sheet页的对象"""
sheet = BasePage.excel.getSheetByName(sheetName)
return sheet
if __name__ == "__main__":
pass
"""
from selenium import webdriver
driver = webdriver.Chrome()
driver.maximize_window()
driver.get('https://mail.126.com/')
wait = BasePage(driver)
frame = ('xpath', '//div[@id="loginDiv"]/iframe') # 登录页面的iframe
wait.switchToFrame(*frame) # 调用BasePage基类封装的switchToFrame切换iframe框架方法
# 输入账号
username = wait.findElement('xpath', '//input[@name="email"]') # 调用BasePage基类封装的findElement定位单个元素方法
username.send_keys('linuxxiaochao') # 调用BasePage基类封装的send_key输入框写数据方法
# 调用BasePage基类封装的isElementExsit(使用定义的表达方式字典集,去显示等待定位操作元素,并返回定位表达方式)方法
if wait.isElementExsit('xpath', '//input[@name="password"]'):
wait.findElement('xpath', '//input[@name="password"]').send_keys('xiaochao11520')
wait.click('xpath', '//a[@id="dologin"]') # 调用BasePage基类封装的click方法
"""
HomePage.py-邮箱首页选择菜单
# 邮箱首页选择菜单
from Page.BasePage import BasePage
from selenium import webdriver
from Page.PageObject.LoginPage import LoginPage
from util.parseConFile import ParseConFile
class HomePage(BasePage):
# 配置文件读取元素
# (第一种方法)此种方法不是调用基类BasePage里面的cf,下面的BasePage.cf是调用了基类本身定义了实例化类
#cf = ParseConFile()
# 首页
homePage = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'homePage')
# (第一种方法)
#homePage = cf.getLocatorsOrAccount('HomePageElements', 'homePage')
# 通讯录
mailList = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'mailList')
# 应用中心
applicationCenter = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'applicationCenter')
# 收件箱
inBox = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'inBox')
# 第一种方法
'''首页菜单选项'''
def selectMenu(self, Menu='mailList'):
"""邮箱首页选择菜单"""
if Menu == 'mailList':
self.click(*HomePage.mailList)
elif Menu == 'homePage':
self.click(*HomePage.homePage)
elif Menu == 'applicationCenter':
self.click(*HomePage.applicationCenter)
elif Menu == 'inBox':
self.click(*HomePage.inBox)
else:
raise ValueError(
u'''
菜单选择错误!
homePage->首页
mailList->通讯录
applicationCenter->应用中心
inBox->收件箱
'''
)
# 第二种方法
"""
def select_menu(self, Menu='mailList'):
if Menu == "mailList":
self.click_address_list_menu()
elif Menu == 'homePage':
self.click_home_page_menu()
elif Menu == 'applicationCenter':
self.click_application_center_menu()
elif Menu == 'inBox':
self.click_in_box_menu()
else:
raise ValueError(
u'''菜单选择错误!
homePage->首页
mailList->通讯录
applicationCenter->应用中心
inBox->收件箱'''
)
def click_home_page_menu(self):
return self.click(*HomePage.homePage)
def click_address_list_menu(self):
return self.click(*HomePage.mailList)
def click_application_center_menu(self):
return self.click(*HomePage.applicationCenter)
def click_in_box_menu(self):
return self.click(*HomePage.inBox)
"""
if __name__=='__main__':
pass
'''
driver = webdriver.Chrome()
login = LoginPage(driver)
login.login('linuxxiaochao', 'xiaochao11520')
home = HomePage(driver)
home.selectMenu()
'''
LoginPage.py-封装登录功能
# 封装登录功能
from Page.BasePage import BasePage
from selenium import webdriver
from util.parseConFile import ParseConFile
class LoginPage(BasePage):
# 配置文件读取元素
# (第一种方法)此种方法不是调用基类BasePage里面的cf,下面的BasePage.cf是调用了基类本身定义了实例化类
#cf = ParseConFile()
# 配置文件读取元素进行定位元素:由于BasePage基类中有调用parseConFile模块解析配置文件ini方法类,并且还对parseConFile进行实例化cf
# 使用此处直接使用cf调用parseConFile模块中的getLocatorsOrAccount方法
# 选择密码登录的按钮
password_login_btn = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'password_login_btn')
# (第一种方法)
#password_login_btn = cf.getLocatorsOrAccount('LoginPageElements', 'password_login_btn')
# 登录框外的iframe
frame = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'frame')
# 用户名输入框
username = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'username')
# 密码输入框
password = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'password')
# 登录按钮
loginBtn = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'loginBtn')
# 登录失败的提示信息1
ferrorHead = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'ferrorHead')
# 登录失败的提示信息2
error_head = BasePage.cf.getLocatorsOrAccount('LoginPageElements', 'errorHead')
# 登录成功后的用户显示元素
account = BasePage.cf.getLocatorsOrAccount('HomePageElements', 'account')
# 第一种方法
def login(self, userName, passWord):
'''登录流程'''
print('-------staring login-------')
self.loadUrl('https://mail.126.com')
self.switchToFrame(*LoginPage.frame) # 调用BasePage基类封装的switchToFrame切换iframe框架方法
self.clear(*LoginPage.username)
self.sendKeys(*LoginPage.username, userName)
self.clear(*LoginPage.password)
self.sendKeys(*LoginPage.password, passWord)
self.click(*LoginPage.loginBtn)
self.switchToDefaultFrame() # 退出返回默认的frame
print('---------end login---------')
def assertTextEqString(self, expected ):
'''断言提示信息是否与期望的值相等'''
self.switchToFrame(*LoginPage.frame)
text = self.getElementText(*LoginPage.ferrorHead)
text1 = self.getElementText(*LoginPage.error_head)
text2 = self.getElementText(*LoginPage.account)
self.switchToDefaultFrame()
print('info: assert "{}" == "{}"'.format(text, expected))
assert text == expected, '{} != {}'.format(text, expected)
assert text1 == expected, '{} != {}'.format(text1, expected)
assert text2 == expected, '{} != {}'.format(text2, expected)
# 第二种方法
'''
def login(self, username, password):
"""登录流程"""
self.open_url()
# 选择密码登录的按钮
self.click_password_login_btn()
self.switch_login_frame()
self.clear_username()
self.input_username(username)
self.clear_password()
self.input_password(password)
self.click_login_btn()
self.switch_default_frame()
def open_url(self):
return self.loadUrl('https://mail.126.com')
# 选择密码登录的按钮
def click_password_login_btn(self):
return self.click(*LoginPage.password_login_btn)
def switch_login_frame(self):
return self.switchToFrame(*LoginPage.frame)
def clear_username(self):
return self.clear(*LoginPage.username)
def input_username(self, username):
self.clear_username()
return self.sendKeys(*LoginPage.username, username)
def clear_password(self):
return self.clear(*LoginPage.password)
def input_password(self, password):
self.clear_password()
return self.sendKeys(*LoginPage.password, password)
def click_login_btn(self):
return self.click(*LoginPage.loginBtn)
def switch_default_frame(self):
return self.switchToDefaultFrame()
def get_error_text(self):
return self.getElementText(*LoginPage.error_head)
def get_login_success_account(self):
return self.getElementText(*LoginPage.account)
'''
if __name__=="__main__":
pass
'''
driver = webdriver.Chrome()
login = LoginPage(driver, 30)
login.login('linuxxiaochao', 'xiaochao11520')
login.assertTextEqString('请输入密码')
'''
ContactPage.py-封装添加联系人功能
# 封装添加联系人功能
from Page.BasePage import BasePage # 基类已经有继承处理读取配置文件的要素操作方法并进行了类的实例化
from selenium import webdriver
from Page.PageObject.LoginPage import LoginPage
from Page.PageObject.HomePage import HomePage
from util.parseConFile import ParseConFile # 使用这个是表示读取配置文件的元素操作
class ContactPage(BasePage):
# 配置文件读取元素
# (第一种)对解析配置ini文件和解析Excel文件进行类的实例化(这是直接调用)
#cf = ParseConFile()
# 新键联系人按钮
# 这是使用基类中已经封装定义好的调用解析配置ini文件方法的类实例化,并调用ParseConFile中的方法getLocatorsOrAccount。
# 并且值参数是调用了配置config.ini文件内的值数据。
new_contact = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'new_contact')
# 这是直接调用(第一种)
#new_contact_btn = cf.getLocatorsOrAccount('ContactPageElements', 'new_contact')
# 姓名输入框
name = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'name')
# 电子邮箱输入框
mail = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'mail')
# 标记为星级
star = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'star')
# 电话号码输入框
phone = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'phone')
# 备注输入框
comment = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'comment')
# 确定按钮
commit = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'commit')
# 添加失败的提示信息
errortip = BasePage.cf.getLocatorsOrAccount('ContactPageElements', 'tooltip')
# 第一种方法
def newContact(self, Name, Mail, Star, Phone, Comment):
"""添加联系人"""
print('--------string add contact--------')
self.click(*ContactPage.new_contact)
self.sendKeys(*ContactPage.name, Name)
self.sendKeys(*ContactPage.mail, Mail)
if Star == '1':
self.click(*ContactPage.star)
self.sendKeys(*ContactPage.phone, Phone)
self.sendKeys(*ContactPage.comment, Comment)
self.click(*ContactPage.commit)
print('--------end add contact--------')
def assertErrorTip(self, excepted):
"""断言联系人添加失败时是否有提示信息"""
text = self.getElementText(*ContactPage.errortip)
print('info: assert "{}"=="{}"'.format(text, excepted))
assert text == excepted
"""
# 第二种方法
def add_contact(self, Name, Mail, Star, Phone, Comment):
'''添加联系人'''
self.click_new_contact_btn()
self.input_name(Name)
self.input_mail(Mail)
if Star == '1':
self.select_str()
self.input_phone(Phone)
self.input_comment(Comment)
self.click_commit_btn()
self.get_error_text()
def click_new_contact_btn(self):
return self.click(*ContactPage.new_contact)
def input_name(self, name):
return self.sendKeys(*ContactPage.name, Name)
def input_mail(self, mail):
return self.sendKeys(*ContactPage.mail, Mail)
def select_str(self):
return self.click(*ContactPage.star)
def input_phone(self, phone):
return self.sendKeys(*ContactPage.phone, Phone)
def input_comment(self, comment):
return self.sendKeys(*ContactPage.comment, Comment)
def click_commit_btn(self):
return self.click(*ContactPage.commit)
def get_error_text(self):
return self.getElementText(*ContactPage.errortip)
"""
if __name__ == '__main__':
pass
'''
driver = webdriver.Chrome()
home = HomePage(driver)
login = LoginPage(driver)
contact = ContactPage(driver)
login.login('linuxxiaochao', 'xiaochao11520')
home.selectMenu()
contact.newContact('281754041@qq.com')
'''
SendMailPage.py-封装发送邮件功能
# 封装发送邮件功能
from Page.PageObject.LoginPage import LoginPage
from selenium import webdriver
from Page.BasePage import BasePage
from util.parseConFile import ParseConFile
from selenium.webdriver.common.keys import Keys
class SendMailPage(BasePage):
# 配置文件读取元素
# (第一种方法)此种方法不是调用基类BasePage里面的cf,下面的BasePage.cf是调用了基类本身定义了实例化类
#cf = ParseConFile()
# 写信按钮
writeMail = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'writeMail')
# (第一种方法)
#writeMail = cf.getLocatorsOrAccount('SendMailPageElements', 'writeMail')
# 收件人输入框
addressee = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'addressee')
# 邮件主题
subject = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'subject')
# 上传附件
uploadAttachment = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'uploadAttachment')
# 删除上传的附件
delete = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'delete')
# 正文外的iframe
iframe = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'iframe')
# 正文
text = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'text')
# 发送按钮
sendBtn = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'sendBtn')
# 邮件发送成功的提示信息
send_success = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'expect')
# 发送成功的提示信息
#send_success = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'send_success')
# 收件人为空的提示信息
error_info_address_is_none = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'error_info_address_is_none')
# 收件人格式或者主题为空的提示信息
error_info_popup_window = BasePage.cf.getLocatorsOrAccount('SendMailPageElements', 'error_info_popup_window')
# 第一种方法:
def sendMail(self, Address, Subject, Text, PFA=''):
"""发送邮件功能"""
self.click(*SendMailPage.writeMail)
self.sendKeys(*SendMailPage.addressee, Address)
self.sendKeys(*SendMailPage.subject, Subject)
self.switchToFrame(*SendMailPage.iframe)
self.sendKeys(*SendMailPage.text, Text)
self.switchToDefaultFrame()
if PFA:
self.click(*SendMailPage.uploadAttachment)
self.ctrlV(PFA)
self.enterKey() # (调用基类中的enterKey方法)此处为什么??
self.waitElementtobelocated(*SendMailPage.delete)
self.click(*SendMailPage.sendBtn)
self.assert_wait_success_info_element_located()
self.assert_get_error_address_is_none()
self.assert_get_error_popup_window()
def assert_wait_success_info_element_located(self):
return self.waitElementtobelocated(*SendMailPage.send_success)
def assert_get_error_address_is_none(self):
element = self.driver.find_element(*SendMailPage.error_info_address_is_none)
return element.text
def assert_get_error_popup_window(self):
return self.getElementText(*SendMailPage.error_info_popup_window)
# 第二种方法:
'''
def send_mail(self, address, subject, text, pfa):
self.click_write_mail_btn()
self.input_address(address)
self.input_subject(subject)
if pfa:
self.upload_file(pfa)
self.switch_frame()
self.input_main_text(text)
self.switch_default_frame()
self.click_send_btn()
def click_write_mail_btn(self):
return self.click(*SendMailPage.writeMail)
def input_address(self, address):
return self.sendKeys(*SendMailPage.addressee, address)
def input_subject(self, subject):
return self.sendKeys(*SendMailPage.subject, subject)
def upload_file(self, pfa):
return self.sendKeys(*SendMailPage.uploadAttachment, pfa)
def switch_frame(self):
return self.switchToFrame(*SendMailPage.iframe)
def input_main_text(self, text):
return self.sendKeys(*SendMailPage.text, text)
def switch_default_frame(self):
return self.switchToDefaultFrame()
def click_send_btn(self):
return self.click(*SendMailPage.sendBtn)
def wait_success_info_element_located(self):
return self.waitElementtobelocated(*SendMailPage.send_success)
def get_error_address_is_none(self):
element = self.driver.find_element(*SendMailPage.error_info_address_is_none)
return element.text
def get_error_popup_window(self):
return self.getElementText(*SendMailPage.error_info_popup_window)
'''
if __name__=='__main__':
pass
'''
driver = webdriver.Chrome()
login = LoginPage(driver)
login.login('linuxxiaochao', 'xiaochao11520')
sendMail = SendMailPage(driver)
sendMail.sendMail('281754043@qq.com', 'pytest', u'pytest实战实例', '1', 'D://KeyWordDriverTestFrameWork//geckodriver.log')
'''
6.添加联系人和发送邮件应该是在已经登录的前提下测试?
答案是肯定的。所以在用例目录下新建conftest.py文件并调用登录功能。
conftest.py-同用例目录下,调用登录功能
# 同用例目录下,调用登录功能
import pytest
from Page.PageObject.LoginPage import LoginPage
from Page.PageObject.HomePage import HomePage
from Page.PageObject.ContactPage import ContactPage
from Page.PageObject.SendMailPage import SendMailPage
from util.parseConFile import ParseConFile
# 第一种方法(直接定义类实例化)
# 配置文件读取元素
#cf = ParseConFile()
# 从配置文件中获取正确的用户名和密码
#userName = cf.getLocatorsOrAccount('126LoginAccount', 'username')
#passWord = cf.getLocatorsOrAccount('126LoginAccount', 'password')
# 第二种方法(LoginPage继承BasePage,而BasePage有继承并处理读取配置文件的要素操作方法并进行了类的实例化)
# 从配置文件中获取正确的用户名和密码
userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
# 登录方法一
@pytest.fixture(scope='function')
def login(driver):
'''除登录用例,每一个用例的前置条件'''
print(u'------------staring login------------')
loginFunc = LoginPage(driver, 30)
loginFunc.login(userName, passWord)
yield
print(u'------------end login------------')
driver.delete_all_cookies()
# 登录方法二
'''
@pytest.fixture(scope='class')
def ini_pages(driver):
login_page = LoginPage(driver)
home_page = HomePage(driver)
contact_page = ContactPage(driver)
send_mail_page = SendMailPage(driver)
yield driver, login_page, home_page, contact_page, send_mail_page
@pytest.fixture(scope='function')
def open_url(ini_pages):
driver = ini_pages[0]
login_page = ini_pages[1]
#login_page.open_url()
yield login_page
driver.delete_all_cookies()
@pytest.fixture(scope='class')
def login(ini_pages):
driver, login_page, home_page, contact_page, send_mail_page = ini_pages
#login_page.open_url()
login_page.login(userName, passWord)
login_page.switch_default_frame()
yield login_page, home_page, contact_page, send_mail_page
driver.delete_all_cookies()
@pytest.fixture(scope='function')
def refresh_page(ini_pages):
driver = ini_pages[0]
yield
driver.refresh()
'''
7.开始编写测试用例啦
test_loginCase.py-登录功能测试
import pytest
from Page.PageObject.LoginPage import LoginPage
@pytest.mark.loginTest
class TestLogin(object):
# 测试数据
loginSheet = LoginPage.getSheet('login')
data = LoginPage.excel.getAllValuesOfSheet(loginSheet)
# 正确的帐号和密码
userName = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'username')
passWord = LoginPage.cf.getLocatorsOrAccount('126LoginAccount', 'password')
@pytest.fixture()
def teardown_func(self, driver):
"""
执行每个用例之后要清除一下cookie,
否则你第一个账号登录之后,重新加载网址还是登录状态,无法测试后面的账号
"""
yield
driver.delete_all_cookies()
@pytest.mark.parametrize('username, password, expect', data)
def test_login(self, teardown_func, driver, username, password, expect):
"""测试登录"""
login = LoginPage(driver, 30)
login.login(username, password)
login.sleep(5)
# 增加登录失败时, 对提示信息的验证
if username == TestLogin.userName and password == TestLogin.passWord:
login.assertValueInSource(expect)
elif username == '':
login.assertTextEqString(expect)
elif username != '' and password == '':
login.assertTextEqString(expect)
elif username == '' and password == '':
login.assertTextEqString(expect)
else:
login.assertTextEqString(expect)
if __name__ == "__main__":
pytest.main(['-v', 'test_loginCase.py'])
test_contactCase.py-添加联系人功能测试
import re
import pytest
from Page.PageObject.HomePage import HomePage
from Page.PageObject.ContactPage import ContactPage
@pytest.mark.conatctTest
class TestAddContact(object):
# 测试数据
contactSheet = ContactPage.getSheet('contact')
data = ContactPage.excel.getAllValuesOfSheet(contactSheet)
@pytest.mark.newcontact
@pytest.mark.parametrize('Name, Mail, Star, Phone, Comment, expect', data)
def test_NewContact(self, driver, login, Name, Mail, Star, Phone, Comment, expect):
"""测试添加联系人"""
home_page = HomePage(driver)
contact_page = ContactPage(driver)
home_page.selectMenu()
contact_page.newContact(Name, Mail, Star, Phone, Comment)
home_page.sleep(5)
# 校验错误的邮箱是否提示信息正确
if re.match(r'^.{1,}@[0-9a-zA-Z]{1,13}\..*$', Mail):
contact_page.assertValueInSource(expect)
else:
contact_page.assertErrorTip(expect)
if __name__ == '__main__':
pytest.main(['-v', 'test_contactCase.py'])
test_sendMailCase.py-发送邮件功能测试
import pytest
from Page.PageObject.SendMailPage import SendMailPage
@pytest.mark.sendMailTest
class TestSendMail(object):
sendMailSheet = SendMailPage.getSheet('mail')
data = SendMailPage.excel.getAllValuesOfSheet(sendMailSheet)
@pytest.mark.sendmail
@pytest.mark.parametrize('Address, Subject, Text, PFA', data)
def test_sendMail(self, driver, login, Address, Subject, Text,PFA):
"""测试发送邮件,包括带附件的邮件"""
send_mail = SendMailPage(driver)
send_mail.sendMail(Address, Subject, Text, PFA)
send_mail.sleep(5)
assert send_mail.isElementExsit(*SendMailPage.expect)
if __name__=='__main__':
pytest.main(['-v', 'test_sendMailCase.py'])
sendMailForReport.py-发送邮件配置测试
import yagmail
class SendMailWithReport(object):
"""发送邮件"""
@staticmethod
def send_mail(smtp_server, from_user, from_pass_word, to_user, subject, contents, file_name):
# 初始化服务器等信息
yag = yagmail.SMTP(from_user, from_pass_word, smtp_server)
# 发送邮件
yag.send(to_user, subject, contents, file_name)
if __name__ == '__main__':
SendMailWithReport.send_mail('smtp.qq.com','413950612@qq.com','mhxvqpewblldbjhf','413950612@qq.com',u'python自动化测试',u'邮件正文','17_12_07.html')
9.问题
9.1.有没有发现报告是怎么生成的?也没有失败用例截图?
9.2.貌似并没有编写驱动浏览器的代码?
10.解决这个两个问题
10.1.可以把驱动浏览器的代码写在全局的conftest.py文件里面,报告生成其实是通过命令 pytest --html=‘report.html’ --self-contained-html生成的,但是这样的报告对用例的描述不是很清晰,且没有对失败用例截图,也不方便分析项目的缺陷,此时也可以把报告代码放到文件里面。
10.2.关于报告修改的文章:https://blog.csdn.net/hyq413950612/article/details/123743418?spm=1001.2014.3001.5502)
conftest.py全局文件
import pytest
from selenium import webdriver
from py._xmlgen import html
_driver = None
# 测试失败时添加截图和测试用例描述(用例的注释信息)
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
"""当测试失败的时候,自动截图,展示到html报告中"""
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
extra = getattr(report, 'extra', [])
if report.when == 'call' or report.when == "setup":
xfail = hasattr(report, 'wasxfail')
if (report.skipped and xfail) or (report.failed and not xfail):
file_name = report.nodeid.replace("::", "_") + ".png"
screen_img = _capture_screenshot()
if file_name:
html = ' \
'οnclick="window.open(this.src)" align="right"/>' % screen_img
extra.append(pytest_html.extras.html(html))
report.extra = extra
report.description = str(item.function.__doc__)
report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape")
@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
cells.insert(1, html.th('Description'))
cells.insert(2, html.th('Test_nodeid'))
cells.pop(2)
cells.pop() # modify by linuxchao at 2018.0803 delete link for report
@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):
cells.insert(1, html.td(report.description))
cells.insert(2, html.td(report.nodeid))
cells.pop(2)
cells.pop() # modify by linuxchao at 2018.0803 delete link for report
def _capture_screenshot():
"""截图保存为base64"""
return _driver.get_screenshot_as_base64()
# 这里我设置的级别是模块级别,也就是每个测试文件运行一次
# 可以设置为session,全部用例执行一次,但是针对126邮箱的话
# 登录次数太多会叫你验证,如果验证就没法执行用例了,我没有对验证处理(处理比较复杂)
@pytest.fixture(scope='module')
def driver():
global _driver
print('------------open browser------------')
_driver = webdriver.Chrome()
_driver.maximize_window()
yield _driver
print('------------close browser------------')
_driver.quit()
10.3.conf.py-全局配置文件
from datetime import datetime
import os
# 项目根目录
projectDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(projectDir)
# 报告目录
reportDir = os.path.join(projectDir, 'report')
print(reportDir)
# ui对象库config.ini文件所在目录(页面要素定位表达式)
configDir = os.path.join(projectDir, 'config', 'config.ini')
print(configDir)
# 测试数据所在目录
excelPath = os.path.join(projectDir, 'data', 'tcData.xlsx')
print(excelPath)
# 当前时间
currentTime = datetime.now().strftime('%H_%M_%S')
print(currentTime)
# 邮件配置信息
# 邮件服务器
smtpServer = 'smtp.qq.com'
# 发送者
fromUser = '413950612@qq.com'
# 发送者密码
fromPassWord = 'mhxvqpewblldbjhf'
# 接收者
toUser = ['413950612@qq.com'] # 可以同时发送给多人,追加到列表中
# 邮件标题
subject = '自动化测试报告'
# 邮件正文
contents = '测试报告正文'
# 报告名称
htmlName = r'{}\testReport{}.html'.format(reportDir, currentTime)
print(htmlName)
# 脚本执行命令
'''
args = r'pytest --html=' + htmlName+ ' ' + '--self-contained-html'
rgs_login = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'loginTest'+ ' --self-contained-html'
args_contact = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'contactTest'+ ' --self-contained-html'
args_sendmail = r'pytest --html='+ htmlName+ ' ' + '-m' + ' ' + 'sendMailTest'+ ' --self-contained-html'
'''
11.通过命令运行
11.1.cmd切换到项目的根目录,执行pytest --html=‘report.html’ --self-contained-html命令(无法发送测试报告邮件)。
RunTestCase.py-执行用例文件
import sys
sys.path.append('.')
from config.conf import *
from util.sendMailForReport import SendMailWithReport
def main():
# 判断项目的根目录是否在sys.path中,没有就添加
if projectDir not in sys.path:
sys.path.append(projectDir)
# 执行用例
os.system(args)
# 发送邮件
SendMailWithReport.send_mail(smtpServer, fromUser, fromPassWord,toUser, subject, contents,htmlName)
if __name__ == '__main__':
main()
13.其实运行用例不只使用pytest --html=‘report.html’ --self-contained-html命令运行,通常会添加很多的命令选项,比如-v,-q,-s等等,需要用到pytest.ini配置文件添加简单的命令选项
13.1.pytest.ini具体使用及原理:https://blog.csdn.net/hyq413950612/article/details/123758733?spm=1001.2014.3001.5501
14.pytest.ini-pytest配置文件
[pytest]
addopts=-vqs
testpaths=./TestCases
markers=
loginTest: Run login test cases
contactTest: Run add contact test cases
sendMailTest: Run send mail test cases