Bootstrap

UI自动化测试示例:python+pytest+selenium+allure

重点应用是封装、参数化:

比如在lib文件夹下,要存储封装好的方法和必要的环境变量(指网址等)

1.cfg.py:封装网址和对应的页面

SMP_ADDRESS = 'http://127.0.0.1:8234'

SMP_URL_LOGIN        = f'{SMP_ADDRESS}/login.html'
SMP_URL_DEVICE_MODEL = f'{SMP_ADDRESS}/index.html#/devicemodel'
SMP_URL_SERVICE_RULE = f'{SMP_ADDRESS}/index.html#/svcrule'

二、登录部分 

2.1 lib.py-登录模块的封装

主要就是输入账号名和密码进行测试。 

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
import time
from lib.cfg import *

class SMP_UI:
    def __init__(self):
        options = webdriver.ChromeOptions()
        #排除一些不需要的日志输出。
        options.add_experimental_option( 'excludeSwitches', ['enable-logging'])

        self.wd = webdriver.Chrome(options=options)
        self.wd.implicitly_wait(3) #隐式等待时间为5秒,等待元素加载完成

    #登录模块测试
    def login(self, username, password):
        self.wd.get(SMP_URL_LOGIN) #打开登录网址
        #time.sleep(1)#sleep方便观察
        if username is not None:
            self.wd.find_element(By.ID, 'username').send_keys(username)
        if password is not None:
            self.wd.find_element(By.ID, 'password').send_keys(password)
        self.wd.find_element(By.ID, 'loginBtn').click()#点击登录按钮

smpUI = SMP_UI()

2.2自动化测试脚本代码-登录

from selenium.webdriver.common.by import By
import time,pytest
from lib.webUI_smp import smpUI

def test_SMP_login_001():#正确账号和密码
    smpUI.login('byhy','sdfsdf' )
    nav = smpUI.wd.find_elements(By.TAG_NAME, 'nav') #登陆后就可以查看nav了,有信息代表登陆成功
    assert nav != []

@pytest.fixture
def clearAlert():
    yield
    try:
        smpUI.wd.switch_to.alert.accept()
    except Exception as e:
        print(e)

@pytest.mark.parametrize('username, password, expectedalert', [
        (None, 'sdfsdf', '请输入用户名'),#空用户名
        ('byhy', None, '请输入密码'),#空密码
        ('byhy', 'sdfsd',   '登录失败: 用户名或者密码错误'),#少输入密码
        ('byhy', 'sdfsdff', '登录失败: 用户名或者密码错误'),#多输入密码
        ('byh', 'sdfsdf',   '登录失败: 用户名不存在'),#少输入用户名
        ('byhyy', 'sdfsdf', '登录失败: 用户名不存在'),#多输入用户名
    ])
def test_SMP_login_002(username, password, expectedalert, clearAlert):
    smpUI.login(username, password)
    smpUI.wd.implicitly_wait(5)#隐式等待

    alert = smpUI.wd.switch_to.alert.text
    assert alert == expectedalert #对弹出框进行断言对比

三、商品部分

3.1 lib.py-商品服务模块的封装

主要就是添加商品、删除商品等,输出第一页的第一条的值(检查返回值) 

    #添加设备模块检测
    def add_device_model(self, devType, name, desc):
        # 实例化选择框做为对象
        select = Select(smpUI.wd.find_element(By.ID, "device-type"))
        # 选择对应列表中的种类
        select.select_by_visible_text(devType)
        #清除填充框后填-设备型号
        ele = smpUI.wd.find_element(By.ID, 'device-model')
        ele.clear()
        ele.send_keys(name)
        #清除填充框后填-型号描述
        ele = smpUI.wd.find_element(By.ID, 'device-model-desc')
        ele.clear()
        ele.send_keys(desc)
        #点提交
        smpUI.wd.find_element(By.CSS_SELECTOR, '.add-one-submit-btn-div .btn').click()
        # time.sleep(1)

    #得到第一页的设备信息(注:一页五条),是显示第一页的第一个数据
    def get_first_page_device_models(self):
        time.sleep(1)

        self.wd.implicitly_wait(0)
        values = self.wd.find_elements(By.CSS_SELECTOR,'.field-value')
        self.wd.implicitly_wait(10)

        deviceModels = []
        for idx, value in enumerate(values):
            if (idx+1) % 3 == 0:
                deviceModels.append(
                    [values[idx-2].text, values[idx-1].text, values[idx].text])

        return deviceModels


    #删除第一个项目
    def del_first_item(self) -> bool:
        self.wd.implicitly_wait(0)
        delBtn = self.wd.find_elements(By.CSS_SELECTOR,'.result-list-item:first-child .result-list-item-btn-bar span:first-child')
        self.wd.implicitly_wait(10)
        if not delBtn:
            return False
        delBtn[0].click()
        self.wd.switch_to.alert.accept()
        return True

3.2自动化测试脚本代码-商品服务

之间登录商品增删改查的界面。注意编完数据后要删除

from selenium.webdriver.common.by import By
import time,pytest
from lib.webUI_smp import smpUI
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from lib.cfg import *

@pytest.fixture(scope='module')
def inDeviceModelMgr():
    print('inDeviceModelMgr setup')
    smpUI.login('byhy','sdfsdf' ) #直接登录
    smpUI.wd.get(SMP_URL_DEVICE_MODEL)#登录后转到SMP_URL_DEVICE_MODEL这个主界面
    yield


@pytest.fixture()
def delAddedDeviceModel():
    yield
    print('删除添加的设备型号')
    smpUI.del_first_item()

@pytest.mark.parametrize('type, device, desc', [
        ("存储柜", 'hebut-cn-box-01', '河北工业大学菜鸟柜子01柜-10大20中40小'),#添加设备-存储柜测试
        ("存储柜", '河'*100, '河北工业大学菜鸟柜子02柜-10大20中40小'),#测试名称最大长度
("电瓶车充电站", 'hebut-dpccdz-es-01', '河北工业大学北辰充电站01站-220v电瓶车充电站'),#添加设备-电瓶车充电站
("洗车站", 'hebut-xcz-washcar-01', '河北工业大学洗车站东站'),#添加设备-洗车站测试
("汽车充电站", 'hebut-powercar-01', '河北工业大学7千瓦汽车充电站01'),#添加设备-汽车充电站
    ])
#添加设备-存储柜测试
def test_SMP_device_model_001(type,device,desc,inDeviceModelMgr, delAddedDeviceModel):
    # 点击添加按钮
    topBtn = smpUI.wd.find_element(By.CSS_SELECTOR, '.add-one-area > span')
    if topBtn.text == '添加':
        topBtn.click()
    smpUI.add_device_model(type,device,desc)
    dms = smpUI.get_first_page_device_models()
    assert  dms == [[type,device,desc    ]]
    smpUI.wd.implicitly_wait(3)



def test_SMP_device_model_501(inDeviceModelMgr, delAddedDeviceModel):
    # 点击添加按钮
    topBtn = smpUI.wd.find_element(By.CSS_SELECTOR, '.add-one-area > span')
    if topBtn.text == '添加':
        topBtn.click()
    smpUI.add_device_model("存储柜", 'daixiugai-01', '待修改测试数据-01')
    smpUI.wd.implicitly_wait(3)

    # 点击修改按钮
    changeBtn = smpUI.wd.find_element(By.XPATH,'/html/body/main/div[3]/div/div[2]/span[2]').click()
    # if changeBtn.text == '修改':
    #     changeBtn.click()
        # 实例化选择框做为对象
    select = Select(smpUI.wd.find_element(By.XPATH,'/html/body/main/div[3]/div/div[1]/div[1]/select'))
        # 选择对应列表中的种类
    select.select_by_visible_text("洗车站")
        # 清除填充框后填-设备型号
    ele = smpUI.wd.find_element(By.XPATH,'/html/body/main/div[3]/div/div[1]/div[2]/input')
    ele.clear()
    ele.send_keys('修改后的型号1')

        # 清除填充框后填-型号描述
    ele = smpUI.wd.find_element(By.XPATH,'/html/body/main/div[3]/div/div[1]/div[3]/input')
    ele.clear()
    ele.send_keys('修改后的型号描述')

        # 点提交
    smpUI.wd.find_element(By.XPATH,'/html/body/main/div[3]/div/div[2]/span[1]').click()
    dms = smpUI.get_first_page_device_models()
    assert  dms == [["洗车站",'修改后的型号1','修改后的型号描述' ]]
    smpUI.wd.implicitly_wait(3)



def test_SMP_device_model_601(inDeviceModelMgr, delAddedDeviceModel):
    # 点击添加按钮
    topBtn = smpUI.wd.find_element(By.CSS_SELECTOR, '.add-one-area > span')
    if topBtn.text == '添加':
        topBtn.click()
    smpUI.add_device_model("存储柜", 'daixshanchu-01', '待删除测试数据-01')

    time.sleep(1)

    smpUI.del_first_item()

    dms = smpUI.get_first_page_device_models()
    assert ['存储柜', 'daixshanchu-01', '待删除测试数据-01'] not in dms
    smpUI.wd.implicitly_wait(3)

四、业务规则部分

4.1 lib.py-业务规则模块的封装

    def add_svc_rule(self, ruleName:str, ruleType:str,minFee:str,
                     estFee:str, feeRate=None, desc:str=None):
        """
        添加业务规则
        :param ruleName: 规则名称
        :param ruleType: 规则类型,只能是:后付费-上报业务量、预付费-下发费用、预付费-下发业务量
        :param minFee: 最小费用,不需要时,填写空字符串
        :param estFee: 预计费用,不需要时,填写空字符串
        :param desc: 描述
        :param feeRate: 费率, 如果ruleType是
           预付费-下发费用: 不用填写
           预付费-下发业务量: 格式为 ['千瓦时', '1'], 元素分别是 单位、单价
           后付费-上报业务量: 格式为 [
                ['10L', '小时','1'],
                ['20L', '小时','2'],
            ], 每个元素里面分别是 : 业务码、单位、单价
        :return:
        """
        #名称输入
        ele = self.wd.find_element(By.CSS_SELECTOR, '.add-one-form > .field:nth-child(1) >input')
        ele.clear()
        ele.send_keys(ruleName)

        select = Select(self.wd.find_element(By.CSS_SELECTOR, ".add-one-form select"))
        select.select_by_visible_text(ruleType)

        if ruleType != '后付费-上报业务量': #预付费-下发业务量操作
            ele = self.wd.find_element(By.CSS_SELECTOR, '.add-one-form > .field:nth-child(3) >input')
            ele.clear()
            if minFee:
                ele.send_keys(minFee)#输入最小消费

            ele =  self.wd.find_element(By.CSS_SELECTOR, '.add-one-form > .field:nth-child(4) >input')
            ele.clear()
            if estFee:
                ele.send_keys(estFee)#输入预估消费

        # 三者都有描述, 用xpath而不用 .field:nth-child 因为后付费-上报业务量 次序会变
        if desc:
            ele =  self.wd.find_element(By.XPATH,"//*[@class='add-one-submit-btn-div']/preceding-sibling::*[1]/input")
            ele.clear()
            ele.send_keys(desc)#输入对应描述

        # 费率填写
        if ruleType == '预付费-下发费用':
            # 没有费率设置
            pass
        elif ruleType == '后付费-上报业务量':
            # 先删除上次添加遗留的费率
            self.wd.implicitly_wait(3)  # 先修改短等待时间
            while True:
                eles = self.wd.find_elements(By.CSS_SELECTOR, '.fee-rate span:last-child')
                if eles:
                    eles[0].click() #删除之前的费率
                    time.sleep(0.5)
                else:
                    break

            self.wd.implicitly_wait(3)  # 再改回来

            for one in feeRate:#遍历每个费率块
                self.wd.find_element(By.CSS_SELECTOR,'.fee-rate-list button').click()
                #锚定整个框
                entry = self.wd.find_element(By.CSS_SELECTOR,'div.fee-rate:nth-last-child(2)')

                # 输入业务码
                entry.find_element(By.CSS_SELECTOR, 'input:nth-of-type(1)').send_keys(one[0])
                # 输入计费单位
                entry.find_element(By.CSS_SELECTOR, 'input:nth-of-type(2)').send_keys(one[1])
                # 输入单位价格
                entry.find_element(By.CSS_SELECTOR, 'input:nth-of-type(3)').send_keys(one[2])

        elif ruleType == '预付费-下发业务量':
            #锚定整个框
            entry = self.wd.find_element(By.CSS_SELECTOR, 'div.fee-rate')
            # 输入计费单位
            entry.find_element(By.CSS_SELECTOR, 'input:nth-of-type(1)').send_keys(feeRate[0])
            # 输入单位价格
            entry.find_element(By.CSS_SELECTOR, 'input:nth-of-type(2)').send_keys(feeRate[1])
        else:
            raise Exception('ruleType 参数值错误')

        # 确定提交
        self.wd.find_element(By.CSS_SELECTOR, '.add-one-submit-btn-div .btn').click()
        self.wd.implicitly_wait(3)



    #业务部分查询首页
    def get_first_page_svc_rules(self):
        time.sleep(1)

        self.wd.implicitly_wait(0)
        items = self.wd.find_elements(By.CSS_SELECTOR,'.result-list-item-info')
        self.wd.implicitly_wait(10)

        rules = []
        for item in items:
            nameValueList = item.find_elements(By.CSS_SELECTOR, '.field>.field-name, .field>.field-value')
            itemInfo = []
            for idx, _ in enumerate(nameValueList):
                if (idx+1) % 2 == 0:
                    nameEle,valueEle = nameValueList[idx-1], nameValueList[idx]
                    if nameEle.text == '规则内容':
                        ruleContent = {}
                        sfns = valueEle.find_elements(By.CSS_SELECTOR,'.sub-field-name')
                        for sfnEle in sfns:
                            sfn = sfnEle.text
                            sfvEle = sfnEle.find_element(By.XPATH, "following-sibling::*[1]")
                            if sfn == '费率':
                                ruleContent[sfn] = sfvEle.text
                            else:
                                ruleContent[sfn] = sfvEle.text

                        itemInfo.append(ruleContent)
                    else:
                        itemInfo.append(valueEle.text)

            rules.append(itemInfo)

        return rules

    #删除第一个项目
    def del_first_item(self) -> bool:
        self.wd.implicitly_wait(0)
        delBtn = self.wd.find_elements(By.CSS_SELECTOR,'.result-list-item:first-child .result-list-item-btn-bar span:first-child')
        self.wd.implicitly_wait(10)
        if not delBtn:
            return False
        delBtn[0].click()
        self.wd.switch_to.alert.accept()
        return True

4.2自动化测试脚本代码-业务规则

注意事项跟3.2的商品的自动化测试脚本类似

from selenium.webdriver.common.by import By
import time,pytest
from lib.webUI_smp import smpUI
from lib.cfg import *

@pytest.fixture(scope='module')
def inServiceRuleMgr():
    print('** inServiceRuleMgr setup **')
    smpUI.login('byhy','sdfsdf' )
    smpUI.wd.get(SMP_URL_SERVICE_RULE)
    yield


@pytest.fixture()
def delAddedServiceRule():
    yield
    print('** 删除添加的业务规则')
    smpUI.del_first_item()

@pytest.mark.parametrize('svcname, svctype,paymin,pay, svcdesc,jifeidanwei,feilv', [
        ("全国-电瓶车充电费率1", "预付费-下发业务量", "0.1","2","全国-电瓶车充电费率1的描述",
        ['千瓦时', '1'],None
        ),#添加预付费-下发业务量

    ])
def test_SMP_service_rule_001(svcname, svctype,paymin,pay, svcdesc,jifeidanwei,feilv,inServiceRuleMgr, delAddedServiceRule):
    # 点击添加按钮
    topBtn = smpUI.wd.find_element(By.CSS_SELECTOR,'.add-one-area > span')
    if topBtn.text == '添加':
        topBtn.click()

    smpUI.add_svc_rule(
        svcname, svctype,paymin,pay, jifeidanwei,svcdesc)

    dms = smpUI.get_first_page_svc_rules()
    assert dms[0][:3] == [svcname, svctype, {'最小消费': paymin, '预估消费': pay, '费率': f'单位:{jifeidanwei[0]} \n单价:{jifeidanwei[1]}'}]


@pytest.mark.parametrize('svcname, svctype,paymin,pay, svcdesc,jifeidanwei,feilv', [
        ("南京-洗车机费率1", "预付费-下发费用", "2","10","南京-洗车机费率1的描述",
        None,None
        ),#预付费-下发费用

    ])
def test_SMP_service_rule_101(svcname, svctype,paymin,pay, svcdesc,jifeidanwei,feilv,inServiceRuleMgr, delAddedServiceRule):
    # 点击添加按钮
    topBtn = smpUI.wd.find_element(By.CSS_SELECTOR,'.add-one-area > span')
    if topBtn.text == '添加':
        topBtn.click()

    smpUI.add_svc_rule(
        svcname, svctype,paymin,pay,svcdesc)

    dms = smpUI.get_first_page_svc_rules()
    assert dms[0][:3] == [svcname, svctype, {'最小消费': paymin, '预估消费': pay}]


@pytest.mark.parametrize('svcname, svctype,paymin,pay, svcdesc,jifeidanwei,feilv', [
        ("南京-存储柜费率1", "预付费-下发业务量", None,None,None,
        None,[['100L','小时','2']]
        ),#添加预付费-下发业务量,['50L','小时','1'],['10L','小时','0.5']

    ])
def test_SMP_service_rule_201(svcname, svctype,paymin,pay, svcdesc,
                              jifeidanwei,feilv,inServiceRuleMgr, delAddedServiceRule):
    # 点击添加按钮
    topBtn = smpUI.wd.find_element(By.CSS_SELECTOR,'.add-one-area > span')
    if topBtn.text == '添加':
        topBtn.click()

    smpUI.add_svc_rule(svcname, svctype, paymin, pay,  svcdesc, feilv)

    dms = smpUI.get_first_page_svc_rules()
    assert dms[0][:3] == [svcname, svctype, {'业务码': f'单位:{feilv[0]} \n计费单位:{feilv[1]} \n单位价格:{feilv[2]}'}]



还有部分没粘上去,万变不离其宗,还要接口自动化测试,这里先不写,下次再写。 

;