支付平台UI自动化方案设计与实践应用

支付平台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()


收到信用卡表单校验测试:


执行输出结果:

<class 'str'> 53215938442

10

最终 1

8

最终 3

上面CPF合法应是: 53215938413

---------------------------------

boileto CPF输入[98100879934] =======>> 校验结果: Entre um válido CPF

<class 'str'> 71939488764

8

最终 3

4

最终 7

上面CPF合法应是: 71939488737

---------------------------------

......

......

---------------------------------

boileto CPF输入[71939488734] =======>> 校验结果: Entre um válido CPF

<class 'str'> 62332668488

1

最终 0

8

最终 3

上面CPF合法应是: 62332668403

---------------------------------

合法的CPF: 96591066020

尝试[10]次后成功

boileto CPF输入[96591066020] =======>> 校验结果: 校验通过

业务需求规则,用代码编写测试规则,测试前端实现逻辑,实现需求的全覆盖。

 

以上是我对支付平台UI自动化设计思想,实现框架,实践方案的简单介绍,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336