支付平台UI自动化方案设计与实践应用
---姜家豪
支付平台是环球易购旗下各电商平台用户支付的服务系统,担任用户购物下单支付环节中非常重要的角色。支付服务的稳定性和质量要求较高,因此测试人员在质量控制和测试手段需要不断提高和改进,引入UI自动化测试。
背景
起初,支付平台只支持较少的支付方式,且仅接入电子(GB)一个站,演变到现在支持50多种支付方式,且接入GB,ZF,RG,RW,CB,D网等站点。过程的业务场景越来越复杂,特别是收银台信用卡类的表单校验,验证点比较多且关联所有网站,在每次发版前为保证质量,回归测试需要至少2人/天的时间,耗费过多的人力资源,甚至会有测试覆盖不全和测试漏测的场景,导致支付平台在上生产时,漏测的问题会相对的增加。
因此,想要解决人力时间和覆盖更全,所以引入UI自动化来作回归测试和校验表单功能。目的是为提高回归测试效率与节省测试资源,覆盖更多的业务测试场景,以保证每次上线的质量。
一.设计
1、设计思路分析
1、核心功能点火,核心业务流
1.常规下单支付流程;
2.跳转到第三方支付页面兼容环境的不稳定性。
2、支持重复操作的功能
如表单校验,重复提交等。
3、支持可扩展性。
综上因素分析,因此排除使用公司自研的自动化平台和自动化框架Robot Framework(ride),而使用更加灵活的Python+selenium+unittest 实 现UI自动化。
2、功能设计
1.支持多端。PC,m,android,ios
2.代码需健壮。加入多环境元素判断
3.执行时间要短。加入接口,数据库与实现逻辑优化
4.代码编写要便利。加入方法封装
5.代码后期维护成本要低。加入业务分离
6.代码兼容性要强。加入元素分离和数据分离
7.支持自动化需求测试。加入脚本测试需求
………
3.框架设计
1.介绍下整个框架目录结构:
2.每个目录与文件结构的作用
1.UI_AUTOMATION目录
Config:敏感数据分离存放地方,便于修改和管理。
Log:存放log,生成的日志。
driver:用于存放浏览器驱动。
data: 该目录用于存放测试相关的数据。
report:用于存放HTML测试报告。
utils: 用于存放自动化测试扩展包,例:config.py , HTMLTestRunner.py。
2.test目录
API:存放接口,用接口执行缩短UI执行的时间,提高用例执行效率。
page:分端文件集合,定位等selenium功能二次封装
test_case: 测试用例目录,存放用例及相关模块。
3.testcase目录
testsuie: 存放dubug调试,各端case集合在testcase.py
4.suie目录
run_test: 项目主程序。用于运行自动化用例。
此设计的思路为更好的实现支付自动化执行和表单校验执行,业务,数据,元素完全分离,层层递进,减少维护成本。
二.unittest框架应用实战
unittest的属性如下:
1.测试固件(test fixture)
TestCase类的属性如下:
setUp():setUp()方法用于测试用例执行前的初始化工作。如测试用例中需要访问数据库,可以在setUp中建立数据库连接并进行初始化。如测试用例需要登录web,可以先实例化浏览器。
tearDown():tearDown()方法用于测试用例执行之后的善后工作。如关闭数据库连接。关闭浏览器。
assert*():一些断言方法:在执行测试用例的过程中,最终用例是否执行通过,是通过判断测试得到的实际结果和预期结果是否相等决定的。
2.测试套件1 (test suite 集合TestCase)
unittest.TestCase:TestCase类,所有测试用例类继承的基本类。
BaseTestCase(unittest.TestCase):
unittest.main():使用这个方法可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法。
unittest.TestSuite():unittest框架的TestSuite()类是用来创建测试套件的。
3.测试套件2(test suite 独立TestCase)
TestSuite类的属性如下:(组织用例时需要用到)
将测试用例添加到测试套件中,如下方,是将test_GB模块下的BaseTestCase类下的test_GB_paypal测试用例添加到测试套件。
suite = unittest.TestSuite()
suite.addTest(BaseTestCase(‘test_GB_paypal’))
4.测试执行器 (test runner)
TextTextRunner的属性如下:(组织用例时需要用到)
run(): run()方法是运行测试套件的测试用例,入参为suite测试套件。
runner = unittest.TextTestRunner()
runner.run(suite)
三.实现用户下单支付的自动化case
【creditcard支付】
1.编写case
登陆:
def gb_login(self):
pc = HomePage(self.driver)
lg = LoginPage(self.driver)
pc.get(self.GBloginURL)
lg.login(self.username, self.password)
sleep(1)
商品购买:
def gb_goodtobuy(self):
sleep(0.5)
try:
self.driver.get(self.GBgood)
b = 0
while b != 5:
try:
self.find_element(*GoodsInfoPageLoc.buy_button)
b = 5
except:
b = b + 1
self.driver.refresh()
except TimeoutException:
self.driver.execute_script('window.stop()')
self.find_element(*GoodsInfoPageLoc.buy_button)
try:
self.click(*GoodsInfoPageLoc.num_plus)
self.click(*GoodsInfoPageLoc.buy_button)
except:
self.driver.refresh()
self.click(*GoodsInfoPageLoc.num_plus)
self.click(*GoodsInfoPageLoc.buy_button)
sleep(5)
订单确认:
def gb_placeorder(self):
sleep(1)
try:
self.driver.switch_to.alert
self.driver.switch_to.alert.accept() # 点击弹出上面的X按钮
except:
pass
try:
self.click(*PlaceInfoPageLoc.place_button) # pleace your order
except:
self.driver.refresh()
sleep(1)
self.driver.execute_script("var q=document.documentElement.scrollTop=500")
try:
self.click(*PlaceInfoPageLoc.place_button) # pleace your order
except:
self.driver.refresh()
sleep(1)
self.click(*PlaceInfoPageLoc.place_button) # pleace your order
sleep(4)
收银台支付:
def payment_creditcard(self):
self.find_element(*PaymentInfoPageLoc.pay_button)
self.driver.refresh()
sleep(0.5)
print('收银台url:', self.driver.current_url)
self.obs_credit_channel("checkout_credit")
self.click(*PaymentInfoPageLoc.creditcard_src)
self.send_keys("hao", *PaymentInfoPageLoc.creditcard_holder)
self.send_keys("6011511651111117", *PaymentInfoPageLoc.creditcard_Number)
self.send_keys("155", *PaymentInfoPageLoc.creditcard_Code)
self.send_Skeys("4", *PaymentInfoPageLoc.creditcard_mouth)
self.send_Skeys("2035", *PaymentInfoPageLoc.creditcard_day)
sleep(0.8)
self.click(*PaymentInfoPageLoc.pay_button)
sleep(4)
支付结果和断言:
def gb_checkorder(self):
try:
paymentresult = self.driver.find_element_by_xpath("//*[@class='pay_title']").text
print("支付结果提示语:", paymentresult)
global ordersn
try:
ordersn = self.driver.find_element_by_xpath("//*[@class='payOnline_sucess']/p[2]/b[1]").text except:
print("fail or no_result")
ordersn = str(ordersn)
if len(ordersn) == 20:
pass
else:
ordersn = ordersn[:-1]
print("订单号为:", ordersn)
connect = pymysql.connect(host=self.host, port=self.post, user=self.user, passwd=self.passwd, db=self.db,charset='utf8')
cursor = connect.cursor()
cursor.execute("SELECT pay_status FROM pay_gateway_16 WHERE parent_order_sn=('%s')" % (ordersn))
status = cursor.fetchall()
status = str(status)
status = status[2:3]
print("此订单状态为:【%s】" % (status), " (0-未支付 1-处理中 2-已支付 3-退款中 4-退款成功 5退款失败 6支付失败)")
cursor.close()
except Exception as msg:
print(u"异常原因%s" % msg)
self.save_screen_shot()
raise
2.执行case
# # 构造测试集
suite = unittest.TestSuite()
suite.addTest(BaseTestCase('login_GB'))
suite.addTest(BaseTestCase('test_GB_creditcard'))
now_time = time.strftime('%Y%m%d-%H%M%S', time.localtime())
report = REPORT_PATH + '\\' + 'PAY_Report ' + now_time + '.html'
### 从代码执行结果里面获取数据 HTMLTestRunner
with open(report, "wb") as outfile:
# runner = HTMLTestRunner(stream=outfile, title=u"GB-PC-PAY_UITest", description=u"用例执行情况:")
runner = HTMLTestRunner(stream=outfile, title=u"GB-PC-PAY_UITest", description=u"用例执行情况:", verbosity=2, retry=0, save_last_try=True)
aa = runner.run(suite)
e = Email(path=report, message='''
本邮件是支付UI自动化测试报告,请下载或者使用浏览器查看附件内容.
如果有任何问题,请及时联系:soa支付测试组. Thank you!
''')
e.send()
3.测试结果
最后自动发送执行结果邮件报告:
四.前端表单校验的设计和功能的实现
方法封装:
将常用的方法封装起来,包含等待,对比,输出对比结果。
def is_clickable(self, xpath, timeout=5):
try:
ui.WebDriverWait(self.driver, timeout).until(EC.element_to_be_clickable((By.XPATH, xpath)))
return True
except TimeoutException:
return False
def is_text_equal(self, xpath, text, Description, timeout=3):
sleep(0.3)
try:
ui.WebDriverWait(self.driver, timeout).until(EC.element_to_be_clickable((By.XPATH, xpath)))
locator = ("xpath", xpath)
text = text
a = EC.text_to_be_present_in_element(locator, text)(self.driver)
if a:
print(a, "【%s】:校验通过"%Description)
else:
print(a, "【%s】:校验失败请检查"%Description)
self.driver.get_screenshot_as_base64()
except TimeoutException:
self.driver.get_screenshot_as_base64()
print("超时,请检查环境是否异常")
sleep(0.2)
def form_input(self, xpath, send_keys): #第一个参数xpath:输入的位置,第二个参数send_key:输入的内容
self.driver.find_element_by_xpath(xpath).clear()
self.driver.find_element_by_xpath(xpath).send_keys(send_keys)
sleep(0.2)
self.driver.find_element_by_xpath(xpath_activation).click() # 激活校验
sleep(0.3)
def isElementExist(self, xpath, Description):
sleep(0.3)
a = self.driver.find_element_by_xpath(xpath).text
if a == '':
print("True", "【%s】:校验通过" % Description)
else:
print("False", "【%s】:校验失败请检查" % Description)
sleep(0.2)
表单校验:
表单校验代码实现:
思想:使用UI自动化自动执行文本的输入和激活校验,对比激活校验提示语。
这样实现双重校验,既检查校验的表单的功能,也检查校验提示语的正确性。
def test_formtest_creditcard(self):
self.is_clickable("//*[@class='placeOrder btn block toPayBtn']")
print(self.driver.current_url)
sleep(0.5)
self.driver.find_element_by_xpath('//*[@src="https://uidesign.zafcdn.com/ZF/image/z_promo/20190418_9281/discover.png"]').click()
sleep(1)
print("------------------------------------------------------------------")
print("billing address元素个数校验:")
self.driver.find_element_by_xpath('//*[@class="credit_editAddress"]').click()
sleep(1)
num = len(self.driver.find_elements_by_xpath("//*[@class='credit_editForm']/div"))
n = 10
if num == 10:
print("billing address元素%d个校验通过" %n)
else:
print("billing address元素%d个校验不通过,请检查!!!!!!!!!!!" %n)
print("------------------------------------------------------------------")
try:
sleep(0.5)
self.driver.find_element_by_xpath('//*[@src="https://uidesign.gbtcdn.com/GB/images/others/check_out/57x35/discover.png?imbypass=true"]').click()
sleep(0.5)
print("Card numbers校验:")
self.driver.find_element_by_xpath("//*[@class='placeOrder btn block toPayBtn']").click() # 激活校验
sleep(0.5)
self.is_text_equal('//*[@curchannel="CREDITCARD"]/div[1]/div/p', 'Please enter your card number.', 'Card numbers为空校验')
self.form_input("//*[@placeholder='Card Number']", "1234567890")
self.is_text_equal('//*[@curchannel="CREDITCARD"]/div[1]/div/p', 'Card numbers must contain between 12 and 20 numerical characters.' ,'Card numbers长度校验')
self.form_input("//*[@placeholder='Card Number']", "123456789012")
self.is_text_equal('//*[@curchannel="CREDITCARD"]/div[1]/div/p',
'Invalid card numbers. Please kindly make sure that your card numbers are correct.',
'Card numbers无效卡校验')
self.form_input("//*[@placeholder='Card Number']", "6011111111111117")
self.isElementExist('//*[@curchannel="CREDITCARD"]/div[1]/div/p', 'Card numbers有效卡校验')
print("------------------------------------------------------------------")
except Exception as msg:
print(u"异常原因%s" % msg)
self.save_screen_shot()
raise
执行的结果:
五.业务需求测试代码实现
举例子:有如下业务需求
代码实现:
driver = webdriver.Chrome()
driver.get(GBConnect().ZF_payurl())
driver.maximize_window()
driver.refresh()
sleep(0.5)
class CPF():
def baxicpf(cpf):
#巴西分期:
src = '//*[@src="https://uidesign.gbtcdn.com/GB/images/others/check_out/57x35/Hipercard.png?imbypass=true"]'
CPF = "//*[@placeholder='CPF']"
CPFerror = CPF + "/../../p"
sleep(0.5)
driver.find_element_by_xpath(src).click()
while 1:
try:
driver.find_element_by_xpath(src).click()
break
except:
driver.execute_script("var q=document.body.scrollTop=300")
sleep(0.2)
driver.find_element_by_xpath(src).click()
sleep(0.2)
driver.find_element_by_xpath(CPF).clear()
driver.find_element_by_xpath(CPF).clear()
driver.find_element_by_xpath(CPF).send_keys(cpf)
driver.find_element_by_xpath("//*[@class='placeOrder btn block toPayBtn']").click()
sleep(0.2)
c = driver.find_element_by_xpath(CPFerror).text
print("巴西分期CPF输入[%s] =======>> 校验结果: %s" % (cpf, c))
def boletocpf(cpf):
#boleto:
src = '//*[@src="https://icss1.gearbest.com/imagecache/GB2/images/boleto3.jpg"]'
CPF = "//*[@placeholder='CPF']"
CPFerror = CPF + "/../../p"
sleep(0.5)
driver.find_element_by_xpath(src).click()
while 1:
try:
driver.find_element_by_xpath(src).click()
break
except:
driver.execute_script("var q=document.body.scrollTop=300")
sleep(0.2)
driver.find_element_by_xpath(src).click()
sleep(0.2)
driver.find_element_by_xpath(CPF).clear()
driver.find_element_by_xpath(CPF).send_keys(cpf)
driver.find_element_by_xpath("//*[@class='placeOrder btn block toPayBtn']").click()
sleep(0.2)
c = driver.find_element_by_xpath(CPFerror).text
print("boileto CPF输入[%s] =======>> 校验结果: %s" % (cpf, c))
def run():
i = 0
while 1:
cpf = "".join(random.choice("0123456789") for i in range(11))
# cpf = '38518065305'
print(type(cpf), cpf)
j = (int(cpf[0])*10 + 9*int(cpf[1]) + 8*int(cpf[2]) + 7*int(cpf[3]) + 6*int(cpf[4]) + 5*int(cpf[5]) + 4*int(cpf[6]) + 3*int(cpf[7]) + 2*int(cpf[8]))%11
print(j)
if j == 0 or j == 1:
j = 0
else:
j = 11 - j
print("最终", j)
newcpf = list(cpf)
newcpf[9] = str(j)
cpf = "".join(newcpf)
k = (int(cpf[0])*11 + 10*int(cpf[1]) + 9*int(cpf[2]) + 8*int(cpf[3]) + 7*int(cpf[4]) + 6*int(cpf[5]) + 5*int(cpf[6]) + 4*int(cpf[7]) + 3*int(cpf[8]) + 2*int(cpf[9]))%11
print(k)
if k == 0 or k == 1:
k = 0
else:
k = 11 - k
print("最终", k)
newcpf = list(cpf)
newcpf[10] = str(k)
newcpf = ''.join(newcpf)
print("上面CPF合法应是:", newcpf)
print("---------------------------------")
if cpf[9] == str(j) and cpf[10] == str(k):
print("合法的CPF:", cpf)
print("尝试[%i]次后成功" %i)
strs = ['baxicpf','boletocpf']
CPF.boletocpf(cpf)
break
else:
strs = ['baxicpf', 'boletocpf']
# for s in strs:
CPF.boletocpf(cpf)
i += 1
run()
收到信用卡表单校验测试:
执行输出结果:
53215938442 10
最终 1
8
最终 3
上面CPF合法应是: 53215938413
---------------------------------
boileto CPF输入[98100879934] =======>> 校验结果: Entre um válido CPF
71939488764 8
最终 3
4
最终 7
上面CPF合法应是: 71939488737
---------------------------------
......
......
---------------------------------
boileto CPF输入[71939488734] =======>> 校验结果: Entre um válido CPF
62332668488 1
最终 0
8
最终 3
上面CPF合法应是: 62332668403
---------------------------------
合法的CPF: 96591066020
尝试[10]次后成功
boileto CPF输入[96591066020] =======>> 校验结果: 校验通过
业务需求规则,用代码编写测试规则,测试前端实现逻辑,实现需求的全覆盖。