UI测试中,页面类的类名,建议使用驼峰命名 请不要写拼音,不同的页面可使用翻译软件翻译之后进行命名 py文件的命名需要小写,如果有多个单词,使用下划线隔开 测试用例使用test文件和函数开头,方便pytest识别 元素的命名,一定要有自己的规范,建议使用locator作为后缀,或者使用oc简写
web测试的数据驱动
数据驱动
参数化:因为数据数据的不同,从而产生不同的人结果
例如搜索商品,不同的分搜索关键字和搜索条件作为入惨,会得到不同的结果 数据驱动:测试数据的改变驱动自动化测试的执行,产生不同的测试结果
对于测试数据,我们可以将其放在代码的数据结构中(例如列表),也可以将其放在外部文件中 (例如json,excel,yaml文件)或者数据库中,结合pytest中的装饰器去实现
数据驱动的实现方式
结合pytest的驱动装饰器, 方式:@pytest.mark.parametrize("参数名",列表数据)
详细用法详见文章 代码示例:
import pytest
datas = [ ( "tom" , 1234 ) , ( "leo" , 2345 ) , ( "rose" , 3456 ) ]
@pytest. mark. parametrize ( 'username,password' , datas)
def test_one ( username, password) :
print ( username, password)
数据驱动和web测试的结合
什么时候适合做UI自动化的驱动测试
如果同一个元素,因为不同的场景产生的提示信息不同,可以将其参数化,通过列表进行传递 应用场景比较受限,常用于登录、搜索的场景 测试用例的代码如下:
import pytest
from PageObject. home_page import HomePage
from PageObject. login_pages import LoginPage
import time
cases_data = [
{ "name" : "" , "pwd" : "lemon123456" } ,
{ "name" : "lemon_auto1111111" , "pwd" : "lemon123456" } ,
{ "name" : "lem" , "pwd" : "lemon123456" } ,
]
@pytest. mark. parametrize ( "datas" , cases_data)
def test_login_input_error ( setUp_tearDown, datas) :
home = HomePage( setUp_tearDown)
home. click_login( )
LoginPage( setUp_tearDown) . login_operate( datas. get( "name" ) , datas. get( "pwd" ) )
time. sleep( 2 )
try :
assert LoginPage( setUp_tearDown) . get_user_input_error_tips( )
print ( "测试通过" )
except Exception:
print ( "测试未通过" )
from selenium. webdriver import ActionChains
from selenium. webdriver. support. wait import WebDriverWait
from selenium. webdriver. support import expected_conditions as EC
import time
import os
class BasicOperate :
def __init__ ( self, driver) :
self. driver = driver
def wait_located ( self, ele) :
try :
return WebDriverWait( self. driver, 20 ) . until( EC. visibility_of_element_located( ele) )
except Exception:
print ( "等待超时" )
def wait_click ( self, ele) :
try :
return WebDriverWait( self. driver, 20 ) . until( EC. element_to_be_clickable( ele) )
except Exception:
print ( "等待超时" )
def wait_presence ( self, ele) :
try :
return WebDriverWait( self. driver, 20 ) . until( EC. presence_of_element_located( ele) )
except Exception:
print ( "等待超时" )
from Common. basic_pages import BasicOperate
from selenium. webdriver. common. by import By
class HomePage ( BasicOperate) :
login_button_locator = ( By. XPATH, '//a[text()="登录"]' )
def click_login ( self) :
self. wait_click( self. login_button_locator) . click( )
from selenium. webdriver. common. by import By
from Common. basic_pages import BasicOperate
class LoginPage ( BasicOperate) :
login_box_locator = ( By. XPATH, '//div[@class="login-con"]//input[contains(@placeholder,"手机号")]' )
pwd_box_locator = ( By. XPATH, '//div[@class="login-con"]//input[contains(@placeholder,"密码")]' )
login_button_locator = ( By. XPATH, '//div[@class="login-con"]//a[text()="登录"]' )
user_input_error_tips_locator = ( By. XPATH, '//div[@class="login-con"]/div[2]' )
def login_operate ( self, phone, pwd) :
self. wait_located( self. login_box_locator) . send_keys( phone)
self. wait_located( self. pwd_box_locator) . send_keys( pwd)
self. wait_click( self. login_button_locator) . click( )
def get_user_input_error_tips ( self) :
return self. wait_located( self. user_input_error_tips_locator) . text
from selenium import webdriver
import pytest
@pytest. fixture ( scope= "class" )
def setUp_tearDown ( ) :
print ( "======用例开始执行=========" )
driver = webdriver. Chrome( )
driver. get( "XXX" )
driver. maximize_window( )
yield driver
driver. quit( )
web自动化之夹具的处理
夹具可以放在fonftest.py文件中,最好和测试用例在同一个层级 具体用法,详见pytest基础 首先,可以将打开浏览器,进入网站,将浏览器最大化放在前置条件中 将浏览器推出放在后置条件中 中间使用yield返回driver 然后将driver作为参数传递至测试用例中 这些上面的代码已经体现 运行UI自动化的时候,不建议所有的测试用例只打开/关闭一次浏览器,因为UI自动化不稳定
如果在运行某条用例失败了,后面的测试用例是从失败的页面执行的,导致后面的用例接连十倍,因为可能找不到对应的元素
如何将登录放在夹具中
可以在conftest中新增一个login的家具 家具中可以将打开浏览器的家具作为参数传入调用,中间使用yield返回打来浏览器家具的返回值 测试用例中,直接使用登录的夹具,driver使用登录的返回值代替 下面conftest代码中,后置先执行的是login的后置(如果有的话),然后才回去执行被调用者的后置 夹具的代码如下:
from selenium import webdriver
import pytest
import time
from PageObject. home_page import HomePage
from PageObject. login_pages import LoginPage
@pytest. fixture ( )
def setUp_tearDown ( ) :
print ( "======测试前置=========" )
driver = webdriver. Chrome( )
driver. get( "XXX" )
driver. maximize_window( )
yield driver
print ( "======测试后置=========" )
driver. quit( )
@pytest. fixture ( )
def login ( setUp_tearDown) :
home = HomePage( setUp_tearDown)
home. click_login( )
LoginPage( setUp_tearDown) . login_operate( "lemon_auto" , "lemon123456" )
time. sleep( 2 )
yield setUp_tearDown
from PageObject. user_pages import UserPage
from PageObject. address_pages import AddressPage
import time
def test_add_address ( login) :
UserPage( login) . user_center_click( )
AddressPage( login) . add_address_operate( "依依" , "18000000001" , "河南省" , "郑州市" , "巩义市" , "小关镇口头村" )
其他模块的代码可以不变化(基础类,其他页面操作或者元素定位方式等)
日志的封装
通过loguru第三方库,在自动化执行的过程中记录操作的轨迹,方便在测试完成之后,追溯到及定位问题 loguru的用法/logging的用法 日志记录有info,error,debug,warn,critical(崩溃) 直接在代码中进行添加 如果要进行跟踪,建议将普通的点击,输入信息,获取文本等操作进行二次封装 具体代码——基础页面
from selenium. webdriver import ActionChains
from selenium. webdriver. support. wait import WebDriverWait
from selenium. webdriver. support import expected_conditions as EC
import time
from loguru import logger
import os
class BasicOperate :
def __init__ ( self, driver) :
self. driver = driver
def wait_located ( self, ele) :
try :
return WebDriverWait( self. driver, 20 ) . until( EC. visibility_of_element_located( ele) )
except Exception as e:
logger. error( f"等待元素可见发生异常: { e} " )
raise
def wait_click ( self, ele) :
try :
return WebDriverWait( self. driver, 20 ) . until( EC. element_to_be_clickable( ele) )
except Exception as e :
logger. error( f"等待元素可被点击发生异常: { e} " )
raise
def wait_presence ( self, ele) :
try :
return WebDriverWait( self. driver, 20 ) . until( EC. presence_of_element_located( ele) )
except Exception as e:
logger. error( f"等待元素存在发生异常: { e} " )
raise
def scroll_operate ( self, ele_locator, text) :
'''
:param ele_locator:元素定位的元组
样式:(By.XPATH, '//a[text()="登录"]')
:param text: 判断代码在HTML中的元素
:return:
'''
try :
while True :
if text in self. driver. page_source:
break
ele = self. wait_located( ele_locator)
self. driver. execute_script( "arguments[0].scrollIntoView(true)" , ele)
time. sleep( 1 )
except Exception as e:
logger. error( f"滚动条操作发生异常: { e} " )
def js_click ( self, ele_locator) :
'''
:param ele_locator: ele_locator:元素定位的元组
样式:(By.XPATH, '//a[text()="登录"]')
:return:
'''
try :
ele = self. wait_located( ele_locator)
self. driver. execute_script( "arguments[0].click()" , ele)
except Exception as e:
logger. error( f"js_click发生异常: { e} " )
def remove_ele_att ( self, ele_locator, att_name) :
'''
:param ele_locator: ele_locator: ele_locator:元素定位的元组
样式:(By.XPATH, '//a[text()="登录"]')
:param att_name: 要去除的属性的名称
:return:
'''
try :
ele = self. wait_located( ele_locator)
self. driver. execute_script( f"arguments[0].removeAttribute( { att_name} )" , ele)
except Exception as e:
logger. error( f"js_去除元素属性发生异常: { e} " )
def mouse_click ( self, ele_locator) :
'''
:param ele_locator: ele_locator: ele_locator:元素定位的元组
样式:(By.XPATH, '//a[text()="登录"]')
:return:
'''
try :
ele = self. wait_located( ele_locator)
ActionChains( self. driver) . click( ele) . perform( )
except Exception as e:
print ( f"鼠标点击发生错误: { e} " )
def switch_win ( self, text) :
'''
:param text: 判断是否切换窗口,一般是窗口的title
:return:
'''
try :
logger. info( f"窗口切换到 { text} " )
handles = self. driver. handles
for handle in handles:
if self. driver. title == text:
break
self. driver. switch_to_window( handle)
except Exception as e:
logger. error( f"切换窗口失败: { e} " )
def upload_file ( self, file_path, exe_path) :
'''
:param file_path: 上传文件的路径
:return:
'''
try :
os. system( f' { exe_path} { file_path} ' )
except Exception as e:
print ( f"上传文件失败: { e} " )
def p_click ( self, ele) :
try :
logger. info( f"点击元素: { ele} " )
self. wait_click( ele) . click( )
except Exception as e:
logger. error( f"点击元素出现异常: { e} " )
def input_text ( self, ele, data) :
try :
logger. info( f"向输入框 { ele} 中输入数据 { data} " )
self. wait_located( ele) . send_keys( data)
except Exception as e:
logger. error( f"向输入框中输入信息异常: { e} " )
def get_ele_text ( self, ele) :
logger. info( f"获取 { ele} 的文本" )
return self. wait_located( ele) . text
def get_ele_is_display ( self, ele) :
logger. info( f"判断 { ele} 是否存在" )
return self. wait_located( ele) . is_displayed( )
from selenium import webdriver
import pytest
import time
from PageObject. home_page import HomePage
from PageObject. login_pages import LoginPage
@pytest. fixture ( )
def setUp_tearDown ( ) :
print ( "======测试前置=========" )
driver = webdriver. Chrome( )
driver. get( "XXXX" )
driver. maximize_window( )
yield driver
print ( "======测试后置=========" )
driver. quit( )
@pytest. fixture ( )
def login ( setUp_tearDown) :
home = HomePage( setUp_tearDown)
home. click_login( )
LoginPage( setUp_tearDown) . login_operate( "lemon_auto" , "lemon123456" )
time. sleep( 2 )
yield setUp_tearDown
import time
from Common. basic_pages import BasicOperate
from selenium. webdriver. common. by import By
class AddressPage ( BasicOperate) :
add_address_locator = ( By. XPATH, '//a[@class="add-btn"]' )
recipient_input_locator = ( By. XPATH, '//div[text()="收件人:"]/following-sibling::div/input' )
phone_input_locator = ( By. XPATH, '//div[text()="手机号码:"]/following-sibling::div/input' )
input_province_locator = ( By. XPATH, '//div[@prop="province"]//input' )
li_peovince_locator = ( By. XPATH, f'//li[text()="河南省"]' )
input_city_locator = ( By. XPATH, '//div[@prop="city"]//input' )
li_city_locator = ( By. XPATH, '//li[text()="郑州市"]' )
input_area_locator = ( By. XPATH, '//div[@prop="area"]//input' )
li_area_locator = ( By. XPATH, '//li[text()="巩义市"]' )
detail_address_locator = ( By. XPATH, '//div[text()="详细地址:"]/following-sibling::div/input' )
save_msg_locator = ( By. XPATH, '//a[text()="保存收件人信息"]' )
def add_address_operate ( self, name, phone, peovince, city, area, msg) :
self. p_click( self. add_address_locator)
self. input_text( self. recipient_input_locator, name)
self. input_text( self. phone_input_locator, phone)
self. p_click( self. input_province_locator)
self. scroll_operate( self. li_peovince_locator, peovince)
time. sleep( 1 )
self. p_click( self. li_peovince_locator)
self. p_click( self. input_city_locator)
time. sleep( 2 )
self. scroll_operate( self. li_city_locator, city)
time. sleep( 1 )
self. p_click( self. li_city_locator)
self. p_click( self. input_area_locator)
time. sleep( 2 )
self. scroll_operate( self. li_area_locator, area)
time. sleep( 1 )
self. p_click( self. li_area_locator)
self. input_text( self. detail_address_locator, msg)
time. sleep( 3 )
from PageObject. user_pages import UserPage
from PageObject. address_pages import AddressPage
def test_add_address ( login) :
UserPage( login) . user_center_click( )
AddressPage( login) . add_address_operate( "依依" , "18000000001" , "河南省" , "郑州市" , "巩义市" , "小关镇口头村" )