ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

webUI自动化实现及封装

webUI自动化实现及封装 webUI自动化实现及封装从用户登录_进入商品详情_提交订单_确认订单_余额支付场景开始一、实例化webdriver打开chrome浏览器进入商城# main.py import time import pyperclip from pykeyboard.windows import PyKeyboard from selenium import webdriver driver webdriver.Chrome() driver.get(https://mall.jctesting.cn/pc/) open_JcMall() # 处理商城登录权限 driver.maximize_window() driver.implicitly_wait(5)二、登录_进入商品详情_余额支付#在商城首页点击【登录】后进行登录操作 driver.find_element(By.LINK_TEXT,登录).click() #在登录页面点击【账号密码登录】 driver.find_element(By.CSS_SELECTOR,[classheader-tab xxl]).click() driver.find_element(By.CSS_SELECTOR,[placeholder请输入账号/手机号码]).send_keys(15200000001) driver.find_element(By.CSS_SELECTOR,[placeholder请输入密码]).send_keys(111111.) driver.find_element(By.XPATH,//*[id__layout]/div/div[1]/div/div/div/div/div[1]/div[3]/div[2]/div[2]/button).click() # 登录后在首页进入第一个商品的详情页面 eles driver.find_elements(By.CSS_SELECTOR,[classgoods-item bg-white]) eles[0].click() # 在商品详情页面点击【立即购买】进入确认订单页面 driver.find_element(By.CSS_SELECTOR,[classbtn bg-primary white]).click() # 在确认订单页面点击【提交订单】进入待支付页面 driver.find_element(By.CSS_SELECTOR, [classwhite bg-primary lg btn row-center]).click() # 在待支付页面选择【余额支付】 eles driver.find_elements(By.CSS_SELECTOR,[classml16 xxl]) eles[0].click() driver.quit()对上述代码进行按PO模式进行封装1、新建一个py_page包存放base.py模块以及按页面建模商城页面对每个页面创建一个.py模块1.1、封装一个base模块该模块实现webdriver的初始化元素定位及元素操作的封装从yaml中读取元素及行为对元素进行操作来驱动业务# base.py import yaml from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from log import Logger from common.open_jcmall import open_JcMall logging Logger(__name__) class Base: _baseurl https://mall.jctesting.cn/pc/ def __init__(self,driver:webdriver None): if driver is None: self.driver webdriver.Chrome() self.driver.get(self._baseurl) open_JcMall() else: self.driver driver self.driver.maximize_window() self.driver.implicitly_wait(5) def find(self, by, locator): by_locator None if by id or by ID: by_locator (By.ID, locator) elif by xpath or by XPATH: by_locator (By.XPATH, locator) elif by name or by NAME: by_locator (By.NAME, locator) elif by css_selector or by CSS_SELECTOR: by_locator (By.CSS_SELECTOR, locator) elif by link_text or by LINK_TEXT: by_locator (By.LINK_TEXT, locator) WebDriverWait(self.driver, 5).until(EC.presence_of_element_located(by_locator)) ele self.driver.find_element(*by_locator) return ele def finds(self, by, locator): by_locator None if by id or by ID: by_locator (By.ID, locator) elif by xpath or by XPATH: by_locator (By.XPATH, locator) elif by name or by NAME: by_locator (By.NAME, locator) elif by css_selector or by CSS_SELECTOR: by_locator (By.CSS_SELECTOR, locator) elif by link_text or by LINK_TEXT: by_locator (By.LINK_TEXT, locator) WebDriverWait(self.driver, 10).until(EC.presence_of_all_elements_located(by_locator)) ele self.driver.find_elements(*by_locator) return ele def find_and_click(self, by, locator): ele self.find(by, locator) ele.click() def finds_and_click(self, by, locator, index): eles self.finds(by, locator) eles[index].click() def find_and_send(self, by, locator, text): self.find(by, locator).send_keys(text) def find_and_clear(self, by, locator): self.find(by, locator).clear() def run_steps(self, yaml_path, operation, sendText: list None, clickIndex: list None): with open(yaml_path, r, encodingUTF-8) as f: datas yaml.safe_load(f) steps datas[operation] send_index 0 click_index 0 for step in steps: action step[action] if action find_and_click: try: self.find_and_click(*step[locator]) except Exception as e: logging.logger.info(f{yaml_path}:{action}:{step[locator]}元素定位异常错误信息{e}) return None elif action find_and_send: if sendText[send_index]: try: self.find_and_send(*step[locator], sendText[send_index]) send_index 1 except Exception as e: logging.logger.info(f{yaml_path}:{action}:{step[locator]}元素定位异常错误信息{e}) return None else: logging.logger.info(find_and_send 方法中未传入arg参数) elif action find_and_clear: try: self.find_and_clear(*step[locator]) except Exception as e: logging.logger.info(f{yaml_path}:{action}:{step[locator]}元素定位异常错误信息{e}) return None elif action finds: try: ele self.finds(*step[locator]) return ele except Exception as e: logging.logger.info(f{yaml_path}:{action}:{step[locator]}元素定位异常错误信息{e}) return None elif action finds_and_click: if clickIndex ! None: try: self.finds_and_click(*step[locator], indexclickIndex[click_index]) click_index 1 except Exception as e: logging.logger.info(f{yaml_path}:{action}:{step[locator]}元素定位异常错误信息{e}) return None else: logging.logger.info(finds_and_click 方法中未传入index参数) elif action find: try: ele self.find(*step[locator]) return ele except Exception as e: logging.logger.info(f{yaml_path}:{action}:{step[locator]}元素定位异常错误信息{e}) return NoneBase类中的构造方法中有一个webdriver类型的driver参数当driver为None时会创建一个新的webdriver实例对象同时该模块中封装了find( ) 与 finds( ) 方法来实现元素的定位来返回单个元素对象或多个元素的对象集基于find( ) 与finds( ) 方法再封装了对元素和元素集的常用操作如click( )、send_keys( )、clear( )run_steps方法从yaml中读取页面元素根据yaml中记录的元素对应行为调用find( )、finds( )、find_and_click( )、 finds_and_click( )、find_and_send( )等方法实现对元素的操作run_steps参数yaml_path: 要读取的yaml文件地址及文件名operation: yaml文件中的模块sendText: 为list类型针对yaml文件的operation对应action为find_and_send的行为即对该定位到的元素发送字符一个operation支持多个actionclickIndex: 为list类型针对yaml文件的operation对应action为finds_and_click的行为即对定位到的元素集中某个元素进行点击操作1.2、对页面建模即每个页面就是一个.py文件在该py文件中把这个页面定义为一个类该页面类继承base模块的Base类把该页面对元素操作和实现的功能定义为一个方法# main_page.py import time from py_page.base import Base class MainPage(Base): path ./yaml_page/main_page.yaml def goto_loginPage(self): self.run_steps(self.path,login) from py_page.login_page import LoginPage return LoginPage(self.driver) def goto_registerPage(self): self.run_steps(self.path,register) from py_page.register_page import RegisterPage return RegisterPage(self.driver) def get_loginName(self): ele self.run_steps(self.path,loginName) return self, ele.get_attribute(text) def search_goods(self,goodsName): self.run_steps(self.path,searchGoods,[f{goodsName}]) self.run_steps(self.path, searchButton) goodsNameList list() time.sleep(2) goodsList self.run_steps(self.path, searchGoodsList) for goods in goodsList: goodsNameList.append(goods.get_attribute(innerHTML)) return self,goodsNameList def goto_goodsDetailPage(self,index): self.run_steps(self.path, goodsDetail, clickIndex[index]) from py_page.goodDetail_page import GoodDetailPage return GoodDetailPage(self.driver)# login_page.py import time from py_page.base import Base class LoginPage(Base): path ./yaml_page/login_page.yaml def use_accountLogin(self,account,password): self.run_steps(self.path,accountLogin) self.run_steps(self.path,inputAccount,sendText[account]) self.run_steps(self.path, inputPassword, sendText[password]) self.run_steps(self.path,clickLoginButtion) time.sleep(2) if self.run_steps(self.path,alert): alert_text self.run_steps(self.path,alert).get_attribute(innerHTML) print(alert_text) return self,alert_text else: from py_page.main_page import MainPage return self,MainPage(self.driver)# goodDetail_page.py from py_page.base import Base class GoodDetailPage(Base): path ./yaml_page/goodsDetail_page.yaml def buy_now(self): self.run_steps(self.path,buyNow) from py_page.confirmOrder_page import ConfirmOrderPage return ConfirmOrderPage(self.driver)# payOrder_page.py from py_page.base import Base class PayOrderPage(Base): path ./yaml_page/payOrder_page.yaml def pay_way(self,index): self.run_steps(self.path,PayWay,clickIndex[index]) from py_page.payResult_page import PayResultPage return PayResultPage(self.driver)# payResult_page.py import time from py_page.base import Base class PayResultPage(Base): path ./yaml_page/payResult_page.yaml def get_payResult(self): time.sleep(2) ele self.run_steps(self.path,payResult) result ele.get_attribute(innerHTML) return self,result编写方法的一个原则页面中的方法是为测试用例服务的测试用例中需要用到什么方法就在对应页面类中定义并返回需要的数据或跳转到对应页面上面场景登录_进入商品详情_余额支付 页面跳转顺序是商城首页-登录页面-商城首页-商品详情页面-确认订单页面-支付订单页面-支付结果页面对上述场景进行PO分层按建模思想对每个页面生成一个py模块每个模块定义一个页面类并继承Base类2、为了页面元素与代码的剥离适应UI自动化页面元素变化较频繁新建yaml_page按页面存放需要用到的页面元素以及对元素的行为login: # 点击首页【登录】 - action: find_and_click locator: [LINK_TEXT, 登录] register: # 点击首页【注册】 - action: find_and_click locator: [LINK_TEXT, 注册] loginName: # 定位首页登录后的姓名 - action: find locator: [CSS_SELECTOR, .el-popover__reference] searchGoods: # 向首页搜索框发送字符 - action: find_and_send locator: [CSS_SELECTOR, [placeholder请输入要搜索的商品名称]] searchButton: # 点击首页【搜索】 - action: find_and_click locator: [CSS_SELECTOR, [classsearch-btn bg-primary white row-center]] searchGoodsList: # 定位搜索结果集中的所有商品 - action: finds locator: [ CSS_SELECTOR, [classname line2] ] goodsDetail: # 进入首页商品详情 - action: finds_and_click locator: [ CSS_SELECTOR, [classgoods-item bg-white] ]上述各模块调用逻辑3、针对上述模块编写测试用例新建testCase包teatCase包中包含base_test.py用于封装所有case需要用到的公共方法每个测试模块可按业务模块区分以test_开头# base_test.py from py_page.main_page import MainPage class BaseTest: def login_to_main(self,account15200000001,pwd111111.): ele MainPage().goto_loginPage().use_accountLogin(account, pwd) return ele[1]# test_order.py import pytest import allure from testCase.base_test import BaseTest from log import Logger from screen_shot import getScreenShot logging Logger(__name__) allure.feature(测试订单功能) class TestOrder(BaseTest): allure.story(测试生成订单) allure.title(用余额购买商品生成订单) def test_payOrder(self): logging.logger.info(*******正在执行测试用例test_payOrder*******) with allure.step(登录_进入首页_进入商品详情_点击【立即购买】_点击【提交订单】_选择支付方式支付_获取支付结果): ele self.login_to_main().goto_goodsDetailPage(0).buy_now().submit_order().pay_way(0).get_payResult() result ele[1] logging.logger.info(f获取到的支付结果为{result}) try: assert result支付成功 except AssertionError as e: getScreenShot(__name__) logging.logger.info(fFAILE*******测试未通过*******{e}) raise e finally: ele[0].driver.quit()测试用例中通过调用py_page页面类中的方法通过链式调用来组织用例步骤例ele self.login_to_main().goto_goodsDetailPage(0).buy_now().submit_order().pay_way(0).get_payResult()首先调用base_test中的lotin_to_main( )用户登录用户登录后goto_DetailPage(0)即进入首页第一个商品的详情页面调用buy_now( )立即购买然后submit_order( )为确认订单页面的提交订单在待支付页面pay_way(0)选择第一种支付方式即余额支付进行支付支付完成后通过get_payResult( )获取支付结果。注get_payResult( ) 最后返回的ele对象是一个元祖self, result)self: 即指向PayResultPage类的实例我们在测试用例中需要调用该PayResultPage类的实例方法都可以通过ele[0].XXX来完成XXX为该类下的实例方法由于PayResultPage类继承了Base页面基类也可通过该方法调用Base中的实例方法或属性测试用例结束后ele[0].driver.quit( )关闭浏览器测试用例的单例设计模式每条测试用例执行首先实例化一个webdriver每次实例化一个webdriver都会生成一个独立的sessionid在py_page的页面类中涉及页面跳转的都会return 一个页面类该类中需要传入一个self.driver的参数被Base类接收处理来保证在测试用例链式调用中页面跳转后是同一个webdriver实例测试用例执行结束通过self.driver.quit( )来关闭这个webdriver释放资源4、创建common包存放框架的需要被引用的常用模块该包中包含日志模块log.py)截图模块screen_shot.py)发送邮件模块send_mail.py)压缩文件模块zip_file.py)4.1、引用已经封装好的log.py模块在common目录下放入log.py模块# log.py import logging import time import os class Logger: def __init__(self,name,CmdLevellogging.INFO, FileLevellogging.INFO): # 创建一个 logger self.logger logging.getLogger(name) # 设置Log等级总开关 self.logger.setLevel(CmdLevel) # 创建 handler用于日志写入 currTime time.strftime(%Y-%m-%d) log_path os.path.abspath(os.path.join(os.getcwd(), .)) /Logs/ log_name log_path currTime .log fh logging.FileHandler(log_name, modea) # mode a 为在原日志上追加w为覆盖 输出日志到文件 fh.setLevel(FileLevel) # 输出到 file 的日志等级 ch logging.StreamHandler() # 输出日志到控制台 ch.setLevel(FileLevel) # 输出到控制台的日志等级 # 定义日志handler的输出格式 fmt logging.Formatter(%(asctime)s - %(filename)s:[%(lineno)s] - [%(levelname)s] - %(message)s) fh.setFormatter(fmt) ch.setFormatter(fmt) # 添加 logger 到 handler里面 self.logger.addHandler(fh) self.logger.addHandler(ch)log模块在各其他模块中import后通过logging Logger(name) 进行实例化namepython的内置类属性主要存储模块的名称python的模块既可以被调用也可以独立运行。而被调用时__name__存储的是py文件名(模块名称)独立运行时存储的是main。4.2、增加错误截图模块 screen_shot.py在测试用例中import截图片模块在断言失败后调用该模块的getScreenShot( )方法实现错误页面的截图# screen_shot.py import os import allure import pyscreenshot as ImageGrab import time currTime time.strftime(%m月%d日%H时%M分%S秒) def getScreenShot(funcName): filename os.path.abspath(os.path.join(os.getcwd(), .)) \Screen_Shot\\ funcName currTime .jpg im ImageGrab.grab() im.save(filename) im.close() allure.attach.file(sourcefilename, namef{funcName} {currTime}, attachment_typeallure.attachment_type.JPG)通过调用 allure.attach.file( ) 方法把截图追加到allure测试报告中与测试报告集成4.3、增加 send_mail.py 模块实现测试结果的邮件发送# send_mail.py import smtplib from email.header import Header from email.mime.application import MIMEApplication from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart class SendEmail: # 初始化服务器信息 def __init__(self,mail_host, mail_user, mail_pass, sender, receives): self.mail_host mail_host self.mail_user mail_user self.mail_pass mail_pass self.sender sender self.receivers receives # 以文本的形式发送邮件 def make_email_by_text(self,context, subject, from_address, to_address): message MIMEText(context, plain, UTF-8) message[Subject] subject # 邮件标题 message[From] Header(from_address, utf-8) # 邮件主体中发送者名称 message[To] Header(to_address, utf-8) # 邮件主体中接收者名称 self.send_email(message) # 以文本和附件的形式发送邮件 def make_email_by_att(self,content,file_path,subject, from_address, to_address): message MIMEMultipart() message[Subject] subject # 邮件标题 message[From] Header(from_address, utf-8) # 邮件主体中发送者名称 message[To] Header(to_address, utf-8) # 邮件主体中接收者名称 body MIMEText(content,plain,utf-8) message.attach(body) att MIMEApplication(open(file_path, rb).read()) att.add_header(Content-Disposition,attachment,filenameallure测试报告.zip) message.attach(att) self.send_email(message) # 登录并进行发送 def send_email(self,message): # 进行登录发送 try: smtpobj smtplib.SMTP_SSL(self.mail_host) smtpobj.ehlo(self.mail_host) smtpobj.login(self.mail_user,self.mail_pass) smtpobj.sendmail(self.sender,self.receivers,message.as_string()) smtpobj.quit() print(success) except Exception as e: print(ferror: {e}) raise e4.4、zip_file.py模块实现把allure测试报告进行压缩在report_zip文件夹中生成report.zip文件作为send_mail.py邮件的附件打包发送# zip_file.py import os import zipfile from common.log import Logger logging Logger(__name__) def zip_files(dir_path,zip_path): :param dir_path: 需要压缩的文件夹地址 例D:\Buyer_test_code\apipotest_jcmall\report :param zip_path: 压缩后的文件存在地址压缩文件名 例D:\Buyer_test_code\apipotest_jcmall\report_zip\report.zip :return: 0 压缩失败 1 压缩成功 try: with zipfile.ZipFile(zip_path, w, zipfile.ZIP_DEFLATED) as f: for dirpath, dirnames, filenames in os.walk(dir_path): fpath dirpath.replace(dir_path, ) fpath fpath and fpath os.sep or for filename in filenames: f.write(os.path.join(dirpath, filename), fpathfilename) logging.logger.info(f文件{dir_path} 压缩成功) except Exception as e: logging.logger.error(f文件{dir_path} 压缩失败错误{e}) return 0 else: return 15、在项目根目录下创建一个run.py模块作为整个框架的执行入口模块# run.py from common.log import Logger from common.zip_file import zip_files logging Logger(__name__) def run_testCase(): test_allure python -m pytest ./testCase/ -s -q --alluredir./result/ logging.logger.info(frun_testCase::正在执行测试命令{test_allure}) try: os.system(test_allure) except Exception as e: logging.logger.error(fos执行命令{test_allure}错误{e}) else: make_report allure generate ./result/ -o ./report/ --clean try: logging.logger.info(frun_testCase::测试执行完毕正在执行生成报告命令{make_report}) os.system(make_report) except Exception as e: logging.logger.error(fos执行命令{make_report}错误{e}) else: dir_path r./report zip_path r./report_zip/report.zip zip_report zip_files(dir_path,zip_path) if zip_report: file_name r./config.yaml file_path zip_path content 测试执行结束Allure测试报告在邮件附件中请使用Pycharm打开查看。 Subject webUI自动化测试报告 # 邮件标题 From Jie的QQ邮箱 # 邮件主体中发送者名称 To Jie的163邮箱 # 邮件主体中接收者名称 try: with open (file_name,r,encodingUTF-8) as f: config yaml.safe_load(f) config_email config[Email] send SendEmail(mail_hostconfig_email[mail_host], mail_userconfig_email[mail_user], mail_passconfig_email[mail_pass], senderconfig_email[sender], receivesconfig_email[receives]) send.make_email_by_att(content,file_path, Subject, From, To) logging.logger.info(f邮件发送成功邮件标题{Subject}) except Exception as e: logging.logger.error(f读取配置文件::{file_name}::失败::{e}) raise e该模块作为一个框架执行入口实现以下功能通过os.system( )函数调用系统命令行执行python -m pytest ./testCase/ -s -q --alluredir./result/ 即通过pytest执行 testCase文件夹的所有以test开头的测试用例并在result文件夹下生成测试结果数据通过os.system( ) 函数调用系统命令行执行allure generate ./result/ -o ./report/ --clean 即根据result文件夹中的结果数据在report文件夹中生成allure测试报告调用zip_file.py模块中的zip_files( ) 方法对allure测试报告report文件夹进行压缩打包后存放report_zip文件夹中实例化send_mail.py模块中的SendEmail类对report_zip文件夹中的压缩包进行邮件发送
返回列表