Python+Selenium+Pytest+Allure自动化测试框架实战实例(示例为我司网盘产品)

这篇具有很好参考价值的文章主要介绍了Python+Selenium+Pytest+Allure自动化测试框架实战实例(示例为我司网盘产品)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

应公司要求,组织员工培训自动化测试,所以也趁此机会把我所学习的自动化框架整理一下,虽说不是很完美,但也有所收获。

环境准备

序号 库、插件、工具 版本号
1 Python 3.11
2 Pycharm 22.2.3
3 pytest 7.2.0
4 pywin32 305
5 selenium3 4.6.0
6 openpyxl 3.0.10
7 Chromedriver 与当前浏览器版本对应即可
8 allure 2.20.1

项目简介

测试地址

由于是公司内部产品,外部访问不了,这里不做说明,大家想尝试可以选择其他网站地址即可

测试范围

1、网盘的登录功能测试-验证正确帐号密码登录成功-验证错误用户名密码登录失败(有很多情况,用例里面做了充分的校验)
2、创建文件夹功能测试-文件夹名称编辑框测试

项目设计

1.python编程语言设计测试脚本

2.webdriver驱动浏览器并操作页面元素

3.二次封装webdriver Api 操作方法

4.采用PageObject设计模式,设计测试业务流程

5.通过UI对象库存储页面操作元素

6.通过数据文件存储数据,读取数据,参数化测试用例并驱动测试执行

7.通过第三方插件allure生成测试报告

目录结构

Python+Selenium+Pytest+Allure自动化测试框架实战实例(示例为我司网盘产品)

代码实现

1、首先我们需要封装一些常用方法,比如键盘操作、剪切板操作、解析Excel文件、读取配置文件、生成日志的方法等等,这里我们一一列举。

clipboard.py(设计剪贴板操作)
import win32con
import win32clipboard as WC


class ClipBoard(object):
    '''设置剪切板内容和获取剪切板内容'''

    @staticmethod
    def getText():
        '''获取剪切板内容'''
        WC.OpenClipboard()
        value = WC.GetClipboardData(win32con.CF_TEXT)
        WC.CloseClipboard()
        return value

    @staticmethod
    def setText(value):
        '''设置剪切板内容'''
        WC.OpenClipboard()
        WC.EmptyClipboard()
        WC.SetClipboardData(win32con.CF_UNICODETEXT, value)
        WC.CloseClipboard()


if __name__ == '__main__':
    pass

keyboard.py(键盘操作)
import win32api
import win32con
import time



class KeyBoard(object):
    '''模拟按键'''
    # 键盘码
    vk_code = {
        'enter': 0x0D,
        'tab': 0x09,
        'ctrl': 0x11,
        'v': 0x56,
        'a': 0x41,
        'x': 0x58,
        'c': 67,
        "r": 82,
        'down': 40,
        'esc': 27,
        'del': 46,
        'left': 37,
        'right': 39,
        'Up': 38,
        'space': 32,
        'F5': 116,

    }

    @staticmethod
    def multiple_go_down(num, name):
        # 多次按下同一个键
        t = 1
        while True:
            if t <= num:
                KeyBoard().keyDown(name)
                t += 1
                time.sleep(0.5)
            else:
                break

    @staticmethod
    def keyDown(key_name):
        """按下键"""
        key_name = key_name.lower()
        try:
            win32api.keybd_event(KeyBoard.vk_code[key_name], 0, 0, 0)
        except Exception as e:
            print('未按下enter键')
            print(e)

    @staticmethod
    def keyUp(key_name):
        """抬起键"""
        key_name = key_name.lower()
        win32api.keybd_event(KeyBoard.vk_code[key_name], 0, win32con.KEYEVENTF_KEYUP, 0)

    @staticmethod
    def oneKey(key):
        """模拟单个键"""
        key = key.lower()
        KeyBoard.keyDown(key)
        time.sleep(0.1)
        KeyBoard.keyUp(key)

    @staticmethod
    def twoKeys(key1, key2):
        """模拟组合键"""
        key1 = key1.lower()
        key2 = key2.lower()
        KeyBoard.keyDown(key1)
        KeyBoard.keyDown(key2)
        KeyBoard.keyUp(key1)
        KeyBoard.keyUp(key2)


if __name__ == '__main__':
    pass

mylog.py(生成操作日志)
# -*- coding: utf-8 -*-
import logging
import os
import time
import config.conf


class MyLog(object):

    def __init__(self, logger=None):
        """
        phone_model 为手机型号
        指定保存日志的文件路径,日志级别,以及调用文件
        将日志存入到指定的文件中
        """
        # 日志文件夹,如果不存在则自动创建
        cur_path = config.conf.cur_path
        log_path = os.path.join(os.path.dirname(cur_path), f'Logs')
        if not os.path.exists(log_path):
            os.makedirs(log_path)
        # log 日期文件夹
        now_date = time.strftime('%Y-%m-%d', time.localtime(time.time()))
        phone_log_path = os.path.join(os.path.dirname(cur_path), f'Logs\\{now_date}')
        if not os.path.exists(phone_log_path):
            os.mkdir(phone_log_path)
        # 创建一个logger
        self.logger = logging.getLogger(logger)
        self.logger.setLevel(logging.INFO)
        # 创建一个handler,用于写入日志文件
        now_time = time.strftime('%Y%m%d-%H%M%S', time.localtime(time.time()))
        log_name = os.path.join(phone_log_path, f'{now_time}.log')
        fh = logging.FileHandler(log_name)
        fh.setLevel(logging.INFO)
        # 再创建一个handler,用于输出到控制台
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        # 定义handler的输出格式
        formatter = logging.Formatter('%(asctime)s - %(levelname)s %(filename)s [line:%(lineno)d]: %(message)s')
        fh.setFormatter(formatter)
        ch.setFormatter(formatter)
        # 给logger添加handler
        self.logger.addHandler(fh)
        self.logger.addHandler(ch)

    def getLog(self):
        return self.logger

通过测试项目设计,我们需要把测试数据存放在Excel文件中,把页面操作元素存在UI对象库中也就是一个配置文件,那么我们需要对Excel 和 ini文件解析,因此我们开始编写这两个方法,设计UI对象库和测试数据文件。

parseConfile.py(读取配置文件的方法)
import configparser
from config.conf import CONF_PATH


class ParseConFile(object):

    def __init__(self):
        self.file = CONF_PATH
        self.conf = configparser.ConfigParser()
        self.conf.read(self.file, encoding='utf-8')
        # print(self.conf.read(self.file, encoding='utf-8'))
        # print(12)


    def get_all_sections(self):
        """获取所有的section,返回一个列表"""
        return self.conf.sections()

    def get_all_options(self, section):
        """获取指定section下所有的option, 返回列表"""
        return self.conf.options(section)

    def get_locators_or_account(self, section, option):
        """获取指定section, 指定option对应的数据, 返回元祖和字符串"""
        try:
            locator = self.conf.get(section, option)
            if ('->' in locator):
                locator = tuple(locator.split('->'))
            return locator
        except configparser.NoOptionError as e:
            print('error:', e)
        return 'error: No option "{}" in section: "{}"'.format(option, section)

    def get_option_value(self, section):
        """获取指定section下所有的option和对应的数据,返回字典"""
        value = dict(self.conf.items(section))
        return value

    def get_option_appointed_int(self, section, option):
        """获取指定section下的指定option下的对应数据,返回int"""
        try:
            locator = self.conf.get(section, option)
            if ('=' in locator):
                # locator = int(locator.split('='))
                locator = int(locator.split('='))
                # print(locator)
                print("+++++++++++++++")
            return locator
        except configparser.NoOptionError as e:
            print('error:', e)
        return 'error: No option "{}" in section: "{}"'.format(option, section)


if __name__ == '__main__':
    pass

parseExcelFile.py(读取Excel表格的方法)
from openpyxl import load_workbook
from config.conf import DATA_Path


class ParseExcel(object):

    def __init__(self):
        self.wk = load_workbook(DATA_Path)
        self.excelFile = DATA_Path

    def get_sheet_by_name(self, sheet_name):
        """获取sheet对象"""
        sheet = self.wk[sheet_name]
        return sheet

    def get_row_num(self, sheet):
        """获取有效数据的最大行号"""
        return sheet.max_row

    def get_cols_num(self, sheet):
        """获取有效数据的最大列号"""
        return  sheet.max_column

    def get_row_values(self, sheet, row_num):
        """获取某一行数据"""
        max_cols_num = self.get_cols_num(sheet)
        row_values = []
        for colsNum in range (1, max_cols_num + 1):
            value = sheet.cell(row_num, colsNum).value
            if value is None:
                value = ''
            row_values.append(value)
        return tuple(row_values)

    def get_column_values(self, sheet, column_num):
        """获取某一列数据"""
        max_row_num = self.get_row_num(sheet)
        column_values = []
        for rowNum in range (2, max_row_num + 1):
            value = sheet.cell (rowNum, column_num).value
            if value is None:
                value = ''
            column_values.append(value)
        return tuple(column_values)

    def get_value_of_cell(self, sheet, row_num, column_num):
        """获取某一个单元格的数据"""
        value = sheet.cell(row_num, column_num).value
        if value is None:
            value = ''
        return value

    def get_all_values_of_sheet(self, sheet):
        """获取某一个sheet页的所有测试数据,返回一个元祖组成的列表"""
        max_row_num = self.get_row_num(sheet)
        column_num = self.get_cols_num(sheet)
        all_values = []
        for row in range(2, max_row_num + 1):
            row_values = []
            for column in range(1, column_num + 1):
                value = sheet.cell(row, column).value
                if value is None:
                    value = ''
                row_values.append(value)
            all_values.append(tuple(row_values))
        return all_values


if __name__ == '__main__':
    pass

新建config.ini文件

config.ini文件是用于存放页面操作的UI元素的。

[LoginAccount];正确的登录账号和密码
# 网盘地址
NetDisk_IP = https://192.168.0.***
username=admin
password=***
user1=test1
password1=***

[LoginPageElements];登录页面的元素
#用户名
username=xpath->//div[@id='login-targetEl']/div[2]/div/div/div/div/div[1]/div/div[1]/div/input
#密码
password=xpath->//div[@id='login-targetEl']/div[2]/div/div/div/div/div[2]/div/div[1]/div/input
#登录按钮
loginBtn=xpath->//div[@id='login-targetEl']/div[2]/div/div/div/div/a
#登录失败提示信息
errorText=xpath->//*[@id="messagebox-1001-msg"]
#用户名为空提示信息
username_none=xpath->//span[(text()="Input error. 用户名不允许为空.")]
#密码为空提示信息
passwd_none=xpath->//span[(text()="Input error. 密码不允许为空.")]


[HomePageElements];首页菜单栏元素
fileText=xpath->/html/body/div[1]/div/div/div[2]/div[1]/div/div/a[1]/span/span/span[2]


;注销登录页面元素

#注销下拉框按钮
logoutdrop_btn=xpath->/html/body/div[1]/div/div/div[3]/div/div/a[2]/span/span/span[2]
#注销按钮
logout_btn=xpath->//*[@id="menuitem-1013-itemEl"]


[Create_folderPageElements];创建文件夹页面元素
# 创建文件夹按钮
create_folder_btn=xpath->//span[(text()="创建文件夹")]
# 文件夹名称编辑框
folder_name_input=xpath->//*[@placeholder="请输入文件夹名称"]
# 提交按钮
submit_btn=xpath->//span[(text()="提交")]
# 取消按钮
cancel_btn=xpath->//span[(text()="取消")]
# 关闭创建弹窗按钮
close_alert_btn=xpath->//*[@id="tool-1210"]
# 创建成功提示信息
create_success_msg=xpath->//div[(text()="创建成功")]
# 创建失败提示信息
create_failure_msg=xpath->//*[@id="messagebox-1001-msg"]
# 提示信息确定按钮
msg_submit_btn=xpath->//*[@id="button-1005-btnInnerEl"]
# 提示信息关闭按钮
msg_close_btn=xpath->//*[@id="tool-1219"]

新建data.py文件,用json格式编写测试数据,驱动测试用例执行不同的数据进行测试。

Login_data.py(用户登录功能的测试用例数据)
class LoginData(object):
    """用户登录测试数据"""

login_success_data = [
        {
            "case": "用户名正确, 密码正确",
            "username": "admin",
            "password": "***",
            "expected": "文件"
        }
    ]

    #   登录失败
    login_fail_data = [
        {
            "case": "用户名正确, 密码错误",
            "username": "admin",
            "password": "admin",
            "expected": "登录失败次数已超过设置次数,账户已锁定"
        },
        {
            "case": "用户名错误, 密码正确",
            "username": "admin111",
            "password": "***",
            "expected": "账号或密码不正确"
        },
        {
            "case": "用户名错误, 密码错误",
            "username": "admin123",
            "password": "admin",
            "expected": "账号或密码不正确"
        }
    ]

    #   用户名、密码都为空
    login_user_pwd_none_data = [
        {
            "case": "用户名为空, 密码为空",
            "username": "",
            "password": "",
            "expected1": "Input error. 用户名不允许为空.",
            "expected2": "Input error. 密码不允许为空."
        }
    ]


if __name__ == '__main__':
    pass

Create_folder_data.py(创建文件夹功能的测试用例数据)
class CreateFolderData(object):
    """新建文件夹测试数据"""

    #   文件夹名称输入中文测试
    folder_name_input_zh_data = [
        {
            "case": "文件夹名称输入中文",
            "folder_name": "管理员",
            "expected": "创建成功"
        }
    ]
    #   输入大写英文
    folder_name_input_EN_data = [
        {
            "case": "文件夹名输入大写英文",
            "folder_name": "ADMIN",
            "expected": "创建成功"
        }
    ]
    #   输入小写英文
    folder_name_input_en_data = [
        {
            "case": "文件夹名输入小写英文",
            "folder_name": "admin",
            "expected": "创建成功"
        }
    ]
    #   文件夹名是否区分英文大小写
    folder_name_are_case_sensitive_data = [
        {
            "case": "文件夹名是否区分英文大小写",
            "folder_name": "Admin",
            "expected": "创建成功"
        }
    ]
    #   文件夹名输入数字
    folder_name_input_num_data = [
        {
            "case": "文件夹名输入数字",
            "folder_name": "123",
            "expected": "创建成功"
        }
    ]


if __name__ == '__main__':
    pass

数据,UI对象库,解析方法都已经有了,接下来通过PageObject模式设计编写每个页面的操作及封装网盘的功能,以便后续设计用例调用

BasePage.py(webdriver等方法的二次封装)
# coding=utf-8
import time
import os
import config.conf
import pywinauto
from pywinauto.keyboard import send_keys
from util.mylog import MyLog
from time import sleep
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait as WD
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.common.by import By
from selenium.common.exceptions import (
    TimeoutException,
    NoAlertPresentException,
)

from util.clipboard import ClipBoard
from util.keyboard import KeyBoard
from util.parseConFile import ParseConFile
from util.parseExcelFile import ParseExcel


class BasePage(object):
    """结合显示等待封装一些selenium内置方法"""
    cf = ParseConFile()
    excel = ParseExcel()

    def __init__(self, driver, timeout=10):
        self.byDic = {
            'id': By.ID,
            'name': By.NAME,
            'class_name': By.CLASS_NAME,
            'xpath': By.XPATH,
            'link_text': By.LINK_TEXT
        }
        self.driver = driver
        self.outTime = timeout
        self.logger = MyLog().getLog()

    # 查找元素
    def find_element(self, by, locator, model=None):
        """
        find alone element
        :param by: eg: id, name, xpath, css.....
        :param locator: id, name, xpath for str
        :return: element object
        """
        try:
            element = WD(self.driver, self.outTime).until(lambda x: x.find_element(by, locator))
        except TimeoutException as t:
            print('error: found "{}" timeout!'.format(locator), t)
            # 截图
            self.save_webImgs(f"查找元素[{model}]异常")
        else:
            return element

    # 查找元素集
    def find_elements(self, by, locator, model=None):
        """
        find group elements
        :param by: eg: id, name, xpath, css.....
        :param locator: eg: id, name, xpath for str
        :return: elements object
        """
        self.logger.info(f'查找"{model}"元素集,元素定位:{locator}')
        try:
            elements = WD(self.driver, self.outTime).until(lambda x: x.find_elements(by, locator))
        except TimeoutException:
            self.logger.exception(f'查找"{model}"元素集失败,定位方式:{locator}')
            # 截图
            self.save_webImgs(f"查找元素集[{model}]异常")
        else:
            return elements

    # 断言元素是否存在
    def is_element_exist(self, by, locator, model=None):
        """
        assert element if exist
        :param by: eg: id, name, xpath, css.....
        :param locator: eg: id, name, xpath for str
        :return: if element return True else return false
        """
        self.logger.info(f'断言"{model}"元素存在,元素定位:{locator}')
        if by.lower() in self.byDic:
            try:
                WD(self.driver, self.outTime). \
                    until(ec.visibility_of_element_located((self.byDic[by], locator)))
            except TimeoutException:
                self.logger.exception(f'断言"{model}"元素不存在,定位方式:{locator}')
                # 截图
                self.save_webImgs(f"断言元素[{model}]异常")
                return False
            return True
        else:
            print('the "{}" error!'.format(by))

    # 点击操作
    def is_click(self, by, locator, model=None):
        if by.lower() in self.byDic:
            try:
                element = WD(self.driver, self.outTime). \
                    until(ec.element_to_be_clickable((self.byDic[by], locator)))
            except TimeoutException:
                # 截图
                self.save_webImgs(f"[{model}]点击异常")
            else:
                return element
        else:
            print('the "{}" error!'.format(by))

    def is_alert(self):
        """
        assert alert if exsit
        :return: alert obj
        """
        try:
            re = WD(self.driver, self.outTime).until(ec.alert_is_present())
        except (TimeoutException, NoAlertPresentException):
            print("error:no found alert")
        else:
            return re

    #  切换 iframe
    def switch_to_frame(self, by, locator):
        """判断frame是否存在,存在就跳到frame"""
        # print('info:switching to iframe "{}"'.format(locator))
        self.logger.info('iframe 切换操作:')
        if by.lower() in self.byDic:
            try:
                WD(self.driver, self.outTime). \
                    until(ec.frame_to_be_available_and_switch_to_it((self.byDic[by], locator)))
                sleep(0.5)
                self.logger.info('切换成功')
            except TimeoutException:
                self.logger.exception('iframe 切换失败!!!')
                # 截图
                self.save_webImgs(f"iframe切换异常")
        else:
            print('the "{}" error!'.format(by))

    # 返回默认iframe
    def switch_to_default_frame(self):
        """返回默认的frame"""
        # print('info:switch back to default iframe')
        self.logger.info('切换到默认页面')
        try:
            self.driver.switch_to.default_content()
            self.logger.info('返回默认frame成功')
        except:
            self.logger.exception('返回默认窗口失败!!!')
            # 截图
            self.save_webImgs("切换失败_没有要切换窗口的信息")
            raise

    def get_alert_text(self):
        """获取alert的提示信息"""
        alert = self.is_alert()
        if alert:
            return alert.text
        else:
            return None

    def switch_to_alert_accept(self):
        """处理浏览器弹窗信息,点击确定"""
        self.logger.info('处理浏览器弹窗信息')
        try:
            self.driver.switch_to.alert.accept()
        except:
            self.logger.exception(f'浏览器弹窗信息处理失败!')
            # 截图
            self.save_webImgs(f"浏览器弹窗信息处理失败!")
            raise

    def switch_to_alert_dismiss(self):
        """处理浏览器弹窗信息,点击取消"""
        self.logger.info('处理浏览器弹窗信息')
        try:
            alert = self.driver.switch_to.alert.dismiss()
            alert.dismiss()
        except AttributeError:
            self.logger.exception(f'浏览器弹窗信息处理失败!')
            # 截图
            self.save_webImgs(f"浏览器弹窗信息处理失败!")
            raise

    # 获取某一个元素的text信息
    def get_element_text(self, by, locator, name=None, model=None):
        """获取某一个元素的text信息"""
        try:
            element = self.find_element(by, locator)
            if name:
                return element.get_attribute(name)
            else:
                self.logger.info(f'获取"{model}"元素文本内容为"{element.text}",元素定位:{locator}')
                return element.text
        except AttributeError:
            self.logger.exception(f'获取"{model}"元素文本内容失败,元素定位:{locator}')
            # 截图
            self.save_webImgs(f"获取[{model}]文本内容异常")

    # 获取多个元素的text信息
    def get_elements_text(self, by, locator, model=None):
        """获取多个元素的text信息"""
        try:
            element = self.find_elements(by, locator)
            text_list = []
            for i in element:
                text = i.text
                text_list.append(text)
            return text_list
        except AttributeError:
            self.logger.exception(f'获取多个"{model}"元素文本内容失败,元素定位:{locator}')
            # 截图
            self.save_webImgs(f"获取多个[{model}]文本内容异常")
            # print('get "{}" get_attribute failed return None'.format(locator))
            return None

    # 加载url
    def load_url(self, url):
        """加载url"""
        # print('info: string upload url "{}"'.format(url))
        self.driver.get(url)

    # 获取页面源码
    def get_source(self):
        """获取页面源码"""
        return self.driver.page_source

    # 写数据
    def send_keys(self, by, locator, value='', model=None):
        """写数据"""
        # print('info:input "{}"'.format(value))
        self.logger.info(f'在"{model}"输入"{value}",元素定位:{locator}')
        try:
            element = self.find_element(by, locator)
            element.send_keys(value)
        except AttributeError:
            self.logger.exception(f'"{model}"输入操作失败!')
            # 截图
            self.save_webImgs(f"[{model}]输入异常")

    # 清理数据
    def clear(self, by, locator, model=None):
        """清理数据"""
        self.logger.info(f'清除"{model}",元素定位:{locator}')
        try:
            element = self.find_element(by, locator)
            element.clear()
        except AttributeError:
            self.logger.exception(f'"{model}"清除操作失败')
            # 截图
            self.save_webImgs(f"[{model}]清除异常")

    # 点击某个元素
    def click(self, by, locator, model=None):
        """点击某个元素"""
        element = self.is_click(by, locator)
        self.logger.info(f'点击"{model}",元素定位:{locator}')
        if element:
            element.click()
        else:
            self.logger.exception(f'"{model}"点击失败')
            # 截图
            self.save_webImgs(f"[{model}]点击异常")
            # print('the "{}" unclickable!')

    # 双击元素
    def double_click(self, by, locator, model=None):
        """点击某个元素两次"""
        # print('info:double_click "{}"'.format(locator))
        element = self.is_click(by, locator)
        xpath = self.find_element(by, locator)
        self.logger.info(f'双击"{model}",元素定位:{locator}')
        if element:
            ActionChains(self.driver).double_click(xpath).perform()
        else:
            self.logger.exception(f'"{model}"双击失败')
            # 截图
            self.save_webImgs(f"[{model}]双击异常")
            # print('the "{}" unclickable!')

    @staticmethod
    def sleep(num=0):
        """强制等待"""
        # print('info:sleep "{}" minutes'.format(num))
        time.sleep(num)

    def ctrl_v(self, value):
        """ctrl + V 粘贴"""
        # print('info:pasting "{}"'.format(value))
        ClipBoard.setText(value)
        self.sleep(2)
        KeyBoard.twoKeys('ctrl', 'v')

    @staticmethod
    def enter_key():
        """enter 回车键"""
        # print('info:keydown enter')
        KeyBoard.oneKey('enter')

    def send_emoji(self, by, locator, value=''):
        # print('info:input "{}"'.format(value))
        js_add_text_to_input = """
          var elm = arguments[0], txt = arguments[1];
          elm.value += txt;
          elm.dispatchEvent(new Event('change'));
          """
        try:
            element = self.find_element(by, locator)
            self.driver.execute_script(js_add_text_to_input, element, value)
        except AttributeError as e:
            print(e)

    # 等待元素可见
    def wait_element_to_be_located(self, by, locator, model=None):
        """显示等待某个元素出现,且可见"""
        self.logger.info(f'等待"{model}"元素,定位方式:{locator}')
        try:
            return WD(self.driver, self.outTime).until(ec.presence_of_element_located((self.byDic[by], locator)))
        except TimeoutException:
            self.logger.exception(f'等待"{model}"元素失败,定位方式:{locator}')
            # 截图
            self.save_webImgs(f"等待元素[{model}]出现异常")

    def get_page_source(self):
        return self.get_source()

    def save_webImgs(self, model=None):
        # filepath = 指图片保存目录/model(页面功能名称)_当前时间到秒.png
        # 截图保存目录
        # 拼接截图文件夹,如果不存在则自动创建
        cur_path = config.conf.cur_path
        now_date = config.conf.CURRENT_TIME
        screenshots_path = os.path.join(os.path.dirname(cur_path), f'Screenshots\\{now_date}')
        if not os.path.exists(screenshots_path):
            os.makedirs(screenshots_path)
        # 当前时间
        datenow = time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time()))
        # 路径
        filepath = '{}\\{}_{}.png'.format(screenshots_path, model, datenow)
        try:
            self.driver.save_screenshot(filepath)
            self.logger.info(f"截屏成功,图片路径为{filepath}")
        except:
            self.logger.exception('截屏失败!')
            raise

    def upload_file(self, filePath, filename):
        """上传文件"""
        try:
            self.logger.info("上传文件")
            # 使用pywinauto来选择文件
            app = pywinauto.Desktop()
            # 选择文件上传的窗口
            dlg = app["打开"]
            # 选择文件地址输入框
            dlg["Toolbar3"].click()
            # 键盘输入上传文件的路径
            send_keys(filePath)
            # 键盘输入回车,打开该路径
            send_keys("{VK_RETURN}")
            # 选中文件名输入框,输入文件名
            dlg["文件名(&N):Edit"].type_keys(filename)
            # 点击打开
            dlg["打开(&O)"].click()
        except:
            self.logger.info("上传失败!")
            raise

    def upload_folder(self, filePath, filename):
        """上传文件夹"""
        try:
            self.logger.info("上传文件夹")
            # 使用pywinauto来选择文件夹
            app = pywinauto.Desktop()
            # 选择文件夹上传的窗口
            dlg = app["选择要上传的文件夹"]
            # 选择文件地址输入框
            dlg["Toolbar3"].click()
            # 键盘输入上传文件的路径
            send_keys(filePath)
            # 键盘输入回车,打开该路径
            send_keys("{VK_RETURN}")
            # 选中文件名输入框,输入文件名
            dlg["文件名(&N):Edit"].type_keys(filename)
            # 点击打开
            dlg["上传"].click()
        except:
            self.logger.info("上传文件夹失败!")
            raise


if __name__ == "__main__":
    pass

LoginPage.py(封装登录功能)
from Page.BasePage import BasePage
from util.parseConFile import ParseConFile
from util.keyboard import KeyBoard


# 网盘IP地址
Netdisk_ip = ParseConFile().get_locators_or_account('LoginAccount', 'NetDisk_IP')


class LoginPage(BasePage):
    # 配置文件读取元素
    do_conf = ParseConFile()

    # 用户名输入框
    username = do_conf.get_locators_or_account('LoginPageElements', 'username')
    # 密码输入框
    password = do_conf.get_locators_or_account('LoginPageElements', 'password')
     # 登录按钮
    loginBtn = do_conf.get_locators_or_account('LoginPageElements', 'loginBtn')
    # 登录失败的提示信息
    error_Text = do_conf.get_locators_or_account('LoginPageElements', 'errorText')
    # 登录成功后的显示信息
    fileText = do_conf.get_locators_or_account('HomePageElements', 'fileText')

    def login(self, username, password):
        """登录流程"""
        self.logger.info("【===登录操作===】")
        self.open_url()
        self.input_username(username)
        self.input_password(password)
        self.click_login_btn()
     
    def open_url(self):
        """加载URL"""
        self.logger.info("【===打开URL===】")
        return self.load_url(url=Netdisk_ip)
    def click_username(self):
        """点击用户名编辑框"""
        self.wait_element_to_be_located(*LoginPage.username, model='用户名框')
        return self.click(*LoginPage.username, model='用户名框')

    def input_username(self, username):
        """输入用户名"""
        self.wait_element_to_be_located(*LoginPage.username, model='用户名框')
        return self.send_keys(*LoginPage.username, username, model='用户名框')

    def click_password(self):
        """点击密码编辑框"""
        self.wait_element_to_be_located(*LoginPage.password, model='密码框')
        return self.click(*LoginPage.password, model='密码框')

    def input_password(self, password):
        """输入密码"""
        self.wait_element_to_be_located(*LoginPage.password, model='密码框')
        return self.send_keys(*LoginPage.password, password, model='密码框')

 	def click_login_btn(self):
        """点击登录按钮"""
        self.wait_element_to_be_located(*LoginPage.loginBtn, model='登录按钮')
        return self.click(*LoginPage.loginBtn, model='登录按钮')
        
    def get_error_text(self):
        """用户登录失败提示信息"""
        self.logger.info("【===获取登录失败报错信息===】")
        return self.get_element_text(*LoginPage.error_Text, model='登录失败的验证信息')

    def get_login_success_text(self):
        """用户登录成功验证信息"""
        self.logger.info("【===获取登录成功验证信息===】")
        return self.get_element_text(*LoginPage.fileText, model="登录成功的验证信息")

    def get_username_none_text(self):
        """登录用户名为空时,提示信息"""
        self.logger.info("【===获取用户名为空时,提示信息===】")
        return self.get_element_text(*LoginPage.username_none, model='用户名为空时,提示信息')

    def get_passwd_none_text(self):
        """登录密码为空时,提示信息"""
        self.logger.info("【===获取密码为空时,提示信息===】")
        return self.get_element_text(*LoginPage.password_none, model='密码为空时,提示信息')

CreatefolderPage.py(封装创建文件夹功能)
from Page.BasePage import BasePage
from util.parseConFile import ParseConFile
from util.keyboard import KeyBoard


class CreateFolderPage(BasePage):
    # 配置文件读取元素
    do_conf = ParseConFile()

    # 创建文件夹按钮
    create_folder_btn = do_conf.get_locators_or_account('Create_folderPageElements', 'create_folder_btn')
    # 文件夹名称编辑框
    folder_name_input = do_conf.get_locators_or_account('Create_folderPageElements', 'folder_name_input')
    # 提交按钮
    submit_btn = do_conf.get_locators_or_account('Create_folderPageElements', 'submit_btn')
    # 取消按钮
    cancel_btn = do_conf.get_locators_or_account('Create_folderPageElements', 'cancel_btn')
    # 关闭创建弹窗按钮
    close_alert_btn = do_conf.get_locators_or_account('Create_folderPageElements', 'close_alert_btn')
    # 创建成功提示信息
    create_success_msg = do_conf.get_locators_or_account('Create_folderPageElements', 'create_success_msg')
    # 创建失败提示信息
    create_failure_msg = do_conf.get_locators_or_account('Create_folderPageElements', 'create_failure_msg')

    def create_folder(self, folder_name):
        """创建文件夹操作流程"""
        self.logger.info('【===创建文件夹操作流程===】')
        self.click_create_folder_btn()
        self.input_folder_name(folder_name)
        self.click_submit_btn()

    def click_create_folder_btn(self):
        """点击创建文件夹按钮"""
        self.wait_element_to_be_located(*CreateFolderPage.create_folder_btn, model='新建文件夹按钮')
        return self.click(*CreateFolderPage.create_folder_btn, model='新建文件夹按钮')

    def input_folder_name(self, folder_name):
        """输入文件夹名称"""
        self.wait_element_to_be_located(*CreateFolderPage.folder_name_input, model='文件夹名称编辑框')
        return self.send_keys(*CreateFolderPage.folder_name_input, folder_name, model='文件夹名称编辑框')

    def click_submit_btn(self):
        """点击提交按钮"""
        self.wait_element_to_be_located(*CreateFolderPage.submit_btn, model='提交按钮')
        return self.click(*CreateFolderPage.submit_btn, model='提交按钮')

    def click_cancel_btn(self):
        """点击取消按钮"""
        self.wait_element_to_be_located(*CreateFolderPage.cancel_btn, model='取消按钮')
        return self.click(*CreateFolderPage.cancel_btn, model='取消按钮')

    def click_close_btn(self):
        """点击关闭按钮"""
        self.wait_element_to_be_located(*CreateFolderPage.close_alert_btn, model='关闭按钮')
        return self.click(*CreateFolderPage.close_alert_btn, model='关闭按钮')

    def get_error_msg(self):
        """获取创建文件夹失败提示信息"""
        self.wait_element_to_be_located(*CreateFolderPage.create_failure_msg, model='创建文件夹失败提示信息')
        return self.get_element_text(*CreateFolderPage.create_failure_msg, model='创建文件夹失败提示信息')

    def click_close_error_msg(self):
        """点击关闭错误提示信息按钮"""
        self.wait_element_to_be_located(*CreateFolderPage.msg_close_btn, model='关闭错误提示信息按钮')
        return self.click(*CreateFolderPage.msg_close_btn, model='关闭错误提示信息按钮')

    def click_submit_error_msg(self):
        """点击错误信息提示框确定按钮"""
        self.wait_element_to_be_located(*CreateFolderPage.msg_submit_btn, model='确定按钮')
        return self.click(*CreateFolderPage.msg_submit_btn, model='确定按钮')

    def get_success_msg(self):
        """创建成功提示信息"""
        self.wait_element_to_be_located(*CreateFolderPage.create_success_msg, model='创建成功信息')
        return self.get_element_text(*CreateFolderPage.create_success_msg, model='创建成功信息')


if __name__ == "__main__":
    pass

所有的准备工作都已经做好了,还有一个问题,我们的创建文件夹功能是否应该在已经登录的前提下测试呢?答案是肯定的。所以我们在用例同目录下新建conftest.py文件并调用登录功能(为什么这么做,不明白的小伙伴可以去搜一下关于conftest.py的原理,后面我也会单独写一篇文章讲一下这个原理。)

conftest.py(除登录用例外,其他用例运行的前置条件)
import pytest
from util.parseConFile import ParseConFile
from Page.PageObject.LoginPage import LoginPage
from Page.PageObject.CreatefolderPage import CreateFolderPage

do_conf = ParseConFile()
# 从配置文件中获取正确的用户名和密码
userName = do_conf.get_locators_or_account('LoginAccount', 'username')
passWord = do_conf.get_locators_or_account('LoginAccount', 'password')


@pytest.fixture(scope='class')
def ini_pages(driver):
    login_page = LoginPage(driver)
    create_folder_page = CreateFolderPage(driver)
    yield driver, login_page, create_folder_page


@pytest.fixture(scope='function')
def open_url(ini_pages):
    driver = ini_pages[0]
    login_page = ini_pages[1]
    # login_page.open_url()
    yield login_page
    driver.delete_all_cookies()


@pytest.fixture(scope='class')
def login(ini_pages):
    driver, login_page, create_folder_page = ini_pages
    login_page.login(userName, passWord)
    yield login_page, create_folder_page
    driver.delete_all_cookies()


@pytest.fixture(scope='function')
def refresh_page(ini_pages):
    driver = ini_pages[0]
    yield
    driver.refresh()

到这里,准备工作就全部完成了,就可以写测试用例了。

test_001_loginCase.py(登录功能测试用例)
import allure
import pytest
from data.Login.login_data import LoginData


@allure.feature("登录功能")
@allure.description("登录界面功能测试")
class TestLogin(object):

    # 测试数据
    login_data = LoginData

    @pytest.mark.Login
    @allure.story("正确的用户名密码测试")
    @allure.title("登录成功场景")
    @pytest.mark.parametrize("data", login_data.login_success_data)
    def test_login(self, open_url, data):
        login_page = open_url
        login_page.login(data["username"], data["password"])
        result = login_page.get_login_success_text()
        assert result == data["expected"]
        
	@allure.story("登录失败测试")
    @allure.title("登录失败场景")
    @pytest.mark.parametrize('data', login_data.login_fail_data)
    def test_fail(self, open_url, data):
        login_page = open_url
        login_page.login(data["username"], data["password"])
        actual = login_page.get_error_text()
        assert actual == data["expected"]

    @allure.story("用户名、密码都为空测试")
    @allure.title("用户名、密码均不输入")
    @pytest.mark.parametrize('data', login_data.login_user_pwd_none_data)
    def test_login_none(self, open_url, data):
        login_page = open_url
        login_page.login_none(data["username"], data["password"])
        actual1 = login_page.get_username_none_text()
        actual2 = login_page.get_passwd_none_text()
        assert actual1 == data["expected1"]
        assert actual2 == data["expected2"]

	@allure.story("登录界面编辑框复制粘贴测试")
    @allure.title("登录界面编辑框复制粘贴场景")
    @pytest.mark.parametrize("data", login_data.login_input_copy_data)
    def test_login_input_copy(self, open_url, data):
        login_page = open_url
        login_page.login_input_copy(data["username"], data["password"])
        result = login_page.get_error_text()
        assert result == data["expected"]

    @allure.story("登录界面编辑框剪切粘贴测试")
    @allure.title("登录界面编辑框剪切粘贴场景")
    @pytest.mark.parametrize("data", login_data.login_input_copy_data)
    def test_login_input_cut(self, open_url, data):
        login_page = open_url
        login_page.login_input_copy(data["username"], data["password"])
        result = login_page.get_error_text()
        assert result == data["expected"]


if __name__ == "__main__":
    pytest.main()
test_002_createfolderCase.py(新建文件夹测试用例)
import allure
import pytest
from data.Create_folder.create_folder_data import CreateFolderData


@allure.feature("新建文件夹功能")
@allure.description("新建文件夹功能测试")
class TestCreateFolder(object):

    # 测试数据
    create_folder_data = CreateFolderData

    @pytest.mark.CreateFolder
    @allure.story("新建文件夹名称编辑框测试")
    @allure.title("输入中文测试")
    @pytest.mark.parametrize("data", create_folder_data.folder_name_input_zh_data)
    def test_create_folder_name_input_zh(self, login, data):
        create_page = login[2]
        create_page.create_folder(data["folder_name"])
        result = create_page.get_success_msg()
        assert result == data["expected"]

    @allure.story("新建文件夹名称编辑框测试")
    @allure.title("输入大写英文测试")
    @pytest.mark.parametrize("data", create_folder_data.folder_name_input_EN_data)
    def test_create_folder_name_input_eng(self, login, data):
        create_page = login[2]
        create_page.create_folder(data["folder_name"])
        result = create_page.get_success_msg()
        assert result == data["expected"]

    @allure.story("新建文件夹名称编辑框测试")
    @allure.title("输入小写英文测试")
    @pytest.mark.parametrize("data", create_folder_data.folder_name_input_EN_data)
    def test_create_folder_name_input_en(self, login, data):
        create_page = login[2]
        create_page.create_folder(data["folder_name"])
        result = create_page.get_success_msg()
        assert result == data["expected"]

    @allure.story("新建文件夹名称编辑框测试")
    @allure.title("文件夹名是否区分英文大小写")
    @pytest.mark.parametrize("data", create_folder_data.folder_name_are_case_sensitive_data)
    def test_create_folder_name_input_sen(self, login, data):
        create_page = login[2]
        create_page.create_folder(data["folder_name"])
        result = create_page.get_success_msg()
        assert result == data["expected"]

    @pytest.mark.CreateFolder
    @allure.story("新建文件夹名称编辑框测试")
    @allure.title("文件夹名输入数字")
    @pytest.mark.parametrize("data", create_folder_data.folder_name_input_num_data)
    def test_create_folder_name_input_num(self, login, data):
        create_page = login[2]
        create_page.create_folder(data["folder_name"])
        result = create_page.get_success_msg()
        assert result == data["expected"]


if __name__ == "__main__":
    pytest.main()

问题

用例已经写完了,有两个问题

1.有没有发现我们的报告怎么生成的?也没有失败用例截图?

2.我们貌似并没有编写驱动浏览器的代码?

现在我们来解决这个两个问题。

根据pytest的conftest.py文件的原理,我们可以把驱动浏览器的代码写在一个全局的conftest.py文件里面。

conftest.py(全局conftest.py文件)
import allure
import pytest
from selenium import webdriver



driver = None

# 测试失败时添加截图

def allure_screenshot():
    # 添加allure失败截图
    allure.attach(driver.get_screenshot_as_png(), "失败截图", allure.attachment_type.PNG)


# 设置为session,全部用例执行一次
@pytest.fixture(scope='session')
def driver():
    global driver
    print('------------open browser------------')
    chromeOptions = webdriver.ChromeOptions()
    # 设定下载文件的保存目录,
    # 如果该目录不存在,将会自动创建
    prefs = {"download.default_directory": "E:\\testDownload"}
    # 将自定义设置添加到Chrome配置对象实例中
    chromeOptions.add_experimental_option("prefs", prefs)
    chromeOptions.add_argument("--ignore-certificate-errors")
    # chromeOptions.add_argument('--disable-gpu')
    chromeOptions.add_argument('--unlimited-storage')
    driver = webdriver.Chrome(options=chromeOptions)
    # driver = webdriver.Chrome()
    driver.maximize_window()
    # driver.implicitly_wait(10)

    yield driver
    print('------------close browser------------')
    driver.quit()
allure 生成自动化测试结果

安装:pip install allure-pytest

1.配置
只需要在测试用例的方法前: @allure.story(‘示例’)
需要查看步骤的方法前:@allure.step(‘示例步骤’)

2.生成结果

pytest ./TestCases/test_001_loginCase.py  --alluredir ./Report

执行testcases目录下的test_001_loginCase.py文件中的所有用例,生成结果到根目录下的Report目录下

3.生成报告

allure generate Report -o Report/html --clean

将report 目录下的测试结果整理,生成到html文件夹下。其中index.html 使用浏览器打开后查看结果。

是不是发现这样输命令这种方式感觉有点low,我们换另外一种方式,可以通过os模块自动执行相关命令,编写运行用例代码,一键运行生成测试报告。

Run_TestCase.py
import os
import pytest
import config.conf

if __name__ == '__main__':
    # 当前时间
    now_time = config.conf.CURRENT_TIME
    # allure 测试报告路径
    cur_path = config.conf.cur_path
    report_path = os.path.join(cur_path, f'..\\Report\\{now_time}')
    # -s : 打印信息
    # -m :运行含标签的用例
    # 指定某一模块的测试用例执行,例如:'./TestCases/test_003_createfolderCase.py'
    pytest.main(['./TestCases/test_002_uploadCase.py', "--alluredir", report_path])
    # 解析测试报告,执行: allure serve {report_path}
    os.system(f"allure serve {report_path}")

最后

为了减小项目维护成本,我们把一些全局的配置项,放到我们的功能配置文件中共全局使用,包括运行用例的一些命令字符串,可以自行修改

conf.py(全局配置文件)
import time
import os

# 项目根目录
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# ui元素对象库config.ini文件所在目录
CONF_PATH = os.path.join(ROOT_DIR, 'config', 'config.ini')
# 测试数据所在目录
DATA_Path = os.path.join(ROOT_DIR, 'data', 'tcData.xlsx')
# 上传文件数据所在目录
upload_data = os.path.join(ROOT_DIR, 'data', f'Upload\\files')
# 上传文件夹数据所在目录
upload_dir = os.path.join(ROOT_DIR, 'data', f'Upload\\folder')
# 测试用例所在目录
cases_dir = os.path.join(ROOT_DIR, 'TestCases')
# 当前时间
CURRENT_TIME = time.strftime('%Y%m%d-%H%M%S', time.localtime(time.time()))
# 报告目录
cur_path = os.path.dirname(os.path.realpath(__file__))
report_path = os.path.join(cur_path, f'Report\\{CURRENT_TIME}')

测试输出

1.自动生成allure测试报告,其中报告里面附带用例执行日志明细,及用例失败自动截图(部分报告展示)
Python+Selenium+Pytest+Allure自动化测试框架实战实例(示例为我司网盘产品)
Python+Selenium+Pytest+Allure自动化测试框架实战实例(示例为我司网盘产品)

项目源码

源码在我的Gitee上,想要源码的话,进QQ群看公告就可以啦。

总而言之

自动化测试是一个不断学习不断更新的东西,我们都需要时刻准备接受新的知识!

PS:最后还是附上我们的[QQ交流群]:516846105 真心希望所有对测试感兴趣,想入门,想提升自己测试能力的小伙伴加入!文章来源地址https://www.toymoban.com/news/detail-415086.html

到了这里,关于Python+Selenium+Pytest+Allure自动化测试框架实战实例(示例为我司网盘产品)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 使用pytest+selenium+allure实现web页面自动化测试

    使用pytest+selenium+allure实现web页面自动化测试

    测试文件 base 基本方法 data 测试数据 page web页面相关操作 image 测试截图 log 日志文件 report 测试报告文件 temp 临时文件 tool 文件读取,发邮件文件 TestCases 测试用例 在page下的__init__.py文件下配置 在base下创建一个webpage.py文件 在base下创建一个driver.py文件 在base下创建一个logger

    2024年02月03日
    浏览(44)
  • Pytest+selenium+allure+Jenkins自动化测试框架搭建及使用

    Pytest+selenium+allure+Jenkins自动化测试框架搭建及使用

    一、    环境搭建 1.    Python下载及安装 Python可应用于多平台包括windows, Linux 和 Mac OS X, 本文主要介绍windows环境下。你可以通过终端窗口输入 \\\"python\\\" 命令来查看本地是否已经安装Python以及Python的安装版本。     如未安装python, 推荐下载python 3.8.3以上版本,本文主要介绍window

    2024年01月18日
    浏览(41)
  • UI自动化测试:Selenium+PO模式+Pytest+Allure整合

    UI自动化测试:Selenium+PO模式+Pytest+Allure整合

    本人目前工作中未涉及到WebUI自动化测试,但为了提升自己的技术,多学习一点还是没有坏处的,废话不多说了,目前主流的webUI测试框架应该还是selenium,考虑到可维护性、拓展性、复用性等,我们采用PO模式去写我们的脚本,本文档也主要整合了Selenium+PO模式+Pytest+Allure,下

    2024年02月08日
    浏览(203)
  • Web UI 自动化测试框架(Pytest+Selenium+Allure+Loguru)

    本框架主要是基于 Python + pytest + selenium + Allure + loguru + 邮件通知/企业微信通知/钉钉通知 实现的WEB UI自动化框架。 基于PageObject设计模式结合,该平台可实现测试用例的自动化执行及自动化测试报告的生成同时包括自动化测试执行时,用例失败的截图操作。 使用webdriver_manag

    2024年02月04日
    浏览(153)
  • Web UI 自动化测试框架(Pytest+Selenium+Allure/Pytest-html+Loguru)

    本框架主要是基于 Python + pytest + selenium + Allure + loguru + 邮件通知/企业微信通知/钉钉通知 实现的WEB UI自动化框架。 基于PageObject设计模式结合,该平台可实现测试用例的自动化执行及自动化测试报告的生成同时包括自动化测试执行时,用例失败的截图操作。 使用webdriver_manag

    2024年02月12日
    浏览(42)
  • 接口自动化测试:Python+Pytest+Requests+Allure

    接口自动化测试:Python+Pytest+Requests+Allure

    本项目实现了对Daily Cost的接口测试: Python+Requests 发送和处理HTTP协议的请求接口 Pytest 作为测试执行器 YAML 管理测试数据 Allure 来生成测试报告。 本项目是参考了pytestDemo做了自己的实现。 项目结构 api : 接口封装层,如封装HTTP接口为Python接口 commom : 从文件中读取数据等各种

    2024年02月09日
    浏览(55)
  • Python+Appium+Pytest+Allure实战APP自动化测试框架

    Python+Appium+Pytest+Allure实战APP自动化测试框架

    Hi,大家好。今天我们来聊聊Python+Appium+Pytest+Allure实战APP自动化测试,pytest只是单独的一个单元测试框架,要完成app测试自动化需要把pytest和appium进行整合,同时利用allure完成测试报告的产出。 编写常规的 线性 脚本具体的步骤如下: 1、设计待测试APP的 自动化测试 用例 2、

    2023年04月09日
    浏览(67)
  • Python+Requests+PyTest+Excel+Allure 接口自动化测试实战

    Python+Requests+PyTest+Excel+Allure 接口自动化测试实战

    本文主要介绍了Python+Requess+PyTest+Excel+Allure 接口自动化测试实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 Unittest是Python标准库中自带的单元测试框架,Unittest有时候也被称为PyUnit,就像

    2024年02月07日
    浏览(39)
  • Python接口自动化测试-篇1(postman+requests+pytest+allure)

    Python接口自动化测试-篇1(postman+requests+pytest+allure)

    Python接口自动化测试是一种使用Python编程语言来编写脚本以自动执行针对应用程序接口(APIs)的测试过程。这种测试方法专注于检查系统的不同组件或服务之间的交互,确保它们按照预期规范进行通信,而不涉及用户界面(UI)的验证。 目录 一、接口测试基础 二、工具实现

    2024年04月17日
    浏览(39)
  • 一个简单的接口自动化测试框架:Python+Requests+Pytest+Allure

    一个简单的接口自动化测试框架:Python+Requests+Pytest+Allure

    project:api_test ——api_keyword ————api_key.py:接口驱动类 ——case ————test_cases.py:测试套件和测试用例 ——report_allure( 无需创建 ):allure报告 ——result( 无需创建 ):测试用例运行结果 ——VAR ————VAR.py:常量类 conftest.py:项目级别fixture main.py:主函数

    2024年02月03日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包