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

python+selenium+pytest 自动化测试框架pytest的应用 再新建一个目录初始化清除,在test文件下面新建conftest.py文件,文件名要固定conftest.py。pytest中用例检查直接用python的assert断言,上述表明两个通过,一个不通过,并标明详细的位置。(3)在这些目录中,搜索由其测试包名称导入的 test_*.py 或 *_test.py 文件。在(一)中,简单对selenium常用问题方法进行介绍,在二中详细介绍结合框架的使用。需要打印的信息则加上-s。将上述的test_mod.py文件放在新建test目录中,执行。 阅读详情

前言

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

环境准备

序号库、插件、工具版本号
1Python3.11
2Pycharm22.2.3
3pytest7.2.0
4pywin32305
5selenium34.6.0
6openpyxl3.0.10
7Chromedriver与当前浏览器版本对应即可
8allure2.20.1

项目简介

测试地址

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

测试范围

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

项目设计

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

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

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

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

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

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

7.通过第三方插件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测试报告,其中报告里面附带用例执行日志明细,及用例失败自动截图(部分报告展示)
在这里插入图片描述
在这里插入图片描述

项目源码

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

总而言之

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

PS:最后还是附上我们的[QQ交流群]:516846105 真心希望所有对测试感兴趣,想入门,想提升自己测试能力的小伙伴加入!

Selenium+Pytest自动化测试框架实战 1、测试框架的优点代码复用率高,如果不使用框架的话,代码会显得很冗余。可以组装日志、报告、邮件等一些高级功能。提高元素等数据的可维护性,元素发生变化时,只需要更新一下配置文件。使用更灵活的PageObject设计模式。2、测试框架的整体目录【注意】 __init__.py文件用以标识此目录为一个python包。 阅读详情

相关推荐

Selenium3+Pytest+Allure落地Python Web自动化测试(15章已完结)

可以是你正在学习的东西,也可以是你认为很酷的东西,更可以是你正在工作的东西,皆可。可以是你正在学习的东西,也可以是你认为很酷的东西,更可以是你正在工作的东西,皆可。但是,最重要的是,你将了解到其他人是如何看待编程的。还有你遇到的那些小问题,已经有无数前辈也曾遇到过,他们总结了并分享在互联网上了,前人的经验是你的宝贵资源,要善于利用谷歌等软件。还有你遇到的那些小问题,已经有无数前辈也曾遇到过,他们总结了并分享在互联网上了,前人的经验是你的宝贵资源,要善于利用谷歌等软件。你需要挑战自我,并超越熟悉的领域。

m0_59287515的博客 491

高级自动化测试框架(Pytest+Selenium)

本课程全面重点讲解了在测试开发中使用的Pytest框架,在使用Pytest框架的同时整合selenium,完成测试。在企业中如何编写自动化测试脚本?如何灵活运行测试用例?本课程都有详细的答案。本课程可以做手工测试人员向自动化测试人员转型的经典课程。

python+selenium+pytest+allure框架

python编辑器 PyCharm 是一款功能强大的 Python 编辑器,具有跨平台性,鉴于目前最新版 PyCharm 使用教程较少,为了节约时间,来介绍一下 PyCharm 在 Windows下是如何安装的。 这是 PyCharm 的下载地址:http://www.jetbrains.com/pycharm/download/#section=windows ...

白凤银刺的博客 2092

python+pytest+selenium+allure实战

selenium是一个针对web端项目的模拟鼠标和键盘操作的自动化测试工具,pytest是一个和unittest类似的自动化测试框架,但它比unittest更加方便,并且可以兼容unittest框架。 项目结构 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210427164511804.png common:存放公共方法,比如读取配置文件 config:存放配置文件。 logs:存放日志。 page:对selenium方法进行二次封装。 page_element:

tianshuiyimo的博客 2万+

自动化测试摸索:python+selenium+pytest(持续更新.....)

(定制安装),以便接下来选硬盘其它分区来安装Python。安装过程会拷贝必要的文件,以及预编译Python标准库等;此刻,已经完成了Python 3.12 for Windows的安装过程。,即添加Python安装路径到环境变量;同时,为了使用便利,增加选项“(可选特征)对话框,保留默认勾选的四个选项,点击Next进入下一步。”(为所有用户安装Python 3.12);”(定制安装路径),修改默认路径到。(高级选项)对话框中,增加选项。文件,双击启动安装向导。(安装成功)对话框,点击。

CociJava的博客 1735

解决SolidWorks启动报错问题:SolidWorks2020显示无法获得下列许可 SolidWorks standard 期望的SolidWorks监控程序已经关闭(-97,121,0)

解决SolidWorks启动报错问题:SolidWorks2020显示无法获得下列许可 SolidWorks standard 期望的SolidWorks监控程序已经关闭(-97,121,0) 问题描述 打开SolidWorks2020时,显示SolidWorks2020显示无法获得下列许可 SolidWorks standard 期望的SolidWorks监控程序已经关闭(-97,121,0) (急需看模型的我心里万马奔腾…) 截图如下: 有问题咱就上 看了其他博主的回答,自己尝试了几个,发现出现了新的

qq_44606358的博客 7万+

百度测试开发实习面试核心考点与应答策略

测试开发作为软件质量保障的关键环节,其核心在于构建自动化测试体系与精准缺陷定位能力。从技术原理看,自动化测试框架通常采用分层架构设计,结合数据驱动与关键字驱动模式提升用例复用率。在工程实践中,需要掌握持续集成工具链(如Jenkins)与性能测试方案(如JMeter梯度增压),这对提升研发效能具有显著价值。针对高频面试场景,候选人需重点准备Java/Python语言深度、TCP/IP网络协议等计算机基础,以及Appium跨平台测试等移动端专项技能。百度等大厂特别注重工程文化适配度,要求候选人能结合A/B测试等

weixin_34259232的博客 319

车规级非易失存储选型指南:从AEC-Q100到ISO 26262的可靠性设计

非易失存储器是电子系统中负责长期保存关键数据与代码的核心组件,其可靠性直接决定了系统的长期稳定运行能力。在汽车电子领域,这一要求被提升到了极致,必须满足严苛的车规级标准。从原理上看,存储器的数据保持能力、擦写寿命等关键参数在高温、振动等恶劣环境下会急剧衰减,因此需要专门的设计与验证。其技术价值在于保障车辆全生命周期的功能安全与数据完整性,避免因存储失效导致的安全隐患或频繁维护。应用场景涵盖从发动机控制单元、自动驾驶域控制器到智能座舱的各类电子控制单元。本文聚焦于车规级非易失存储的选型,深入解析AEC-Q10

weixin_30938149的博客 458

PYTHON自动化框架总结:SELENIUM+PYTEST+ALLURE

文章目录前言一、PYTEST1、使用pytest执行测试需要遵循的规则2、数据驱动3、结合ALLURE报告二、接口自动化内容1、接口自动化重要性2、实战项目框架3、准备工作二、软件理论补充1、DevOps 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章正文内容,下面案例可供参考 一、PYTEST 1、使用pytest执行测试需要遵循的规则 .py测试文件必须以

Cheryl_Xu的博客 5480

SPSS操作(五):主成分分析

1、将数据导入spss,并定义变量,将铁路运输业职工人数(人)、城市公共交通业职工人数(人)、邮政业职工人数(人)、电信和其他信息传输服务业职工人数(人)、客运量(万人)、接待入境旅游人数(万人次)、普通高校师生比(%)、星级住宿业营业额(亿元)分别定义为x1,x2,x3,x4,x5,x6,x7,x8 如图: 2、对数据进行标准化处理 ①选择【分析】—【描述统计】—【描述】 ②添加要标准化的变量,勾选【将标准化值另存为变量(Z)】,再点确定 ③返回原来的数据窗口就可以看见标准化后的变量了 3、SPS

My_daily_life的博客 8万+

VSAN 虚拟机丢失排查与恢复完整方案

VSAN虚拟机(含vCenter)显示无效 检查存储状态:登录ESXi Host Client,确认VSAN存储是否挂载。 若未挂载,通过SSH执行 手动挂载。 验证文件存在:检查虚拟机文件是否完整。 解除文件锁定:删除残留的 .lck 文件 手动注册虚拟机:强制注册虚拟机配置文件。 检查VSAN健康:验证集群节点在线、磁盘组状态正常,并测试节点间网络。 修复元数据:针对损坏对象,谨慎执行修复。 终极恢复:若数据丢失,从备份还原或联系VMware支持,提交日志

sido777的博客 2680

【Selenium+Pytest+allure报告生成自动化测试框架】附带项目源码和项目部署文档

首先呢,因为我们很多的模块会用到时间戳,或者日期等等字符串,所以我们先单独把时间封装成一个模块。 然后让其他模块来调用即可。在utils目录新建times.py模块

qq_48811377的博客 5637

【Python爬虫教程】第3篇-解决使用reqeusts遇到的ProxyError异常

Python request代理错误提示“HTTPSConnectionPool, Cannot connect to proxy, no such file or directory”

分享技术,记录思考和感悟 1508

【UI自动化测试技术】自动化测试研究:Python+Selenium+Pytest+Allure,详解UI自动化测试,自动化入门+元素的八大定位方式(精)(一)

当然,学会了以上八大定位给方式,肯定不等于大功告成,就定位来说,还有动态元素的处理,异常情况的排查,总之我们学习测试,就要学会自己去排查和解决问题,学习力对我们是十分重要的。但是,至此,我们已经完成了学习UI自动化测试的前期准备工作,下一篇文章我们就来具体讲一讲,如何编写UI自动化测试的线性脚本。最后一句,学习任何一种技术,不是一蹴而就的,我们要多思考、多练习才好。OK,我们下一篇文章,再见!

梦断南楼的博客 2087

基于RK3568的YOLOv11模型端侧推理优化与部署实战指南

在AI边缘计算领域,瑞芯微RK3568芯片凭借其强大的NPU算力和丰富的接口资源,成为了众多开发者的首选平台。然而,瑞芯微官方文档虽然提供了YOLO11预训练模型的部署方案,但对于需要自定义数据集训练的开发者来说,从模型训练到板端部署的完整流程却缺乏详细指导。本文将带你走完从环境搭建、数据准备、模型训练到最终板端部署的完整技术路线,并分享实际开发中遇到的各种坑点和解决方案。让我们一起探索如何在RK3568平台上实现30FPS稳定推理的YOLO11目标检测应用!

m0_64661770的博客 115

第21章—技术管理篇(上):身为技术人该怎样带好团队并做好管理者

上节讲了职业规划和学习提升这两个话题,随着职业规划的不断落地,或者自己能力的不断提升,大家也会逐渐被提拔成“职级更高”的人。企业规模扩张时,员工数量也会变多,意味着Boss需要更多的人来辅助管理,而管理者会从外部招聘吗?也许会,但大多企业更喜欢从内部选拔,毕竟人的天性更希望找的人值得信赖。此时,一些能力不错、做事靠谱的老员工,就会被“赶上”管理者的位子。作管理不难,但想要“”就需要一定情商。

运维老生常谈 1366

Selenium+Pytest+allure自动化测试框架实战

前言# selenium自动化+ pytest测试框架 本章你需要 一定的python基础——至少明白类与对象,封装继承 一定的selenium基础——本篇不讲selenium,不会的可以自己去看selenium中文翻译网 测试框架简介# 测试框架有什么优点呢: 代码复用率高,如果不使用框架的话,代码会很冗余 可以组装日志、报告、邮件等一些高级功能 提高元素等数据的可维护性,元素发生变化时,只需要更新一下配置文件 使用更灵活的PageObject设计模式 测试框架的整体目录 目录/文件 说...

weixin_54765042的博客 7473

黑猫带你学eMMC协议第22篇:emmc可靠写详解(Write Reliability)

1 前言 1.1 声明 本文依据eMMC JEDEC5.1、网络资料及个人工作经验整理而成,如有部分错误请留言。 本文部分内容可能源自网络,侵删。 本文结合eMMC JEDEC5.1协议手册查看效果更佳。 1.2 内容提要 1、什么是可靠性写属性(Write Reliability)? 2、可靠写和普通的写有什么区别? 3、设置可靠写的流程及流程框图。 4、可靠写需要遵循哪些规则? 1.3 参考链接 本文主要参考链接:http://www.up48.com/cn/support/8_37.html 2 什么

黑猫奥利奥的博客 3123

手把手教你从无到有搭建Python+Selenium+Pytest+Allure UI自动化测试框架搭建

通过以上步骤,我们成功搭建了一个基于Python、Selenium、Pytest和Allure的UI自动化测试框架。这个框架具有良好的扩展性和可维护性,可以帮助测试团队高效地进行UI自动化测试。

weixin_45754647的博客 2195

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

selenium pytest allure 自动化测试

selenium + pytest + allure 完成自动化测试封住,完成了可视化报告展示,失败截图 ,日志记录等功能,让自动化测试无比丝滑

杨海吉 794

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

从零开始搭建python+selenium+pytest+allure

1、首先安装python安装包:https://www.python.org/ 我这里选择的是windows3.9.6安装包 下载后正常双击安装就好 建议不要安装到C盘或非应为路径下可能会出现问题 1.1环境变量的配置 在环境变量将自己安装的python路径添加到环境变量中 通过cmd验证一下是否已经配置好 如果出现这个界面就表示已经安装好了 ...

weixin_44277941的博客 6847

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Python+Selenium+Pytest自动化框架

pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。

软件测试技术交流分享 925

Python测试框架——Python+Selenium+Pytest自动化测试,简单封装demo

Pytest是一个功能强大且易于使用的Python测试框架,它支持简单的单元测试、功能测试和自动化端到端测试。

weixin_43854299的博客 1791

python pytest selenium 自动化测试框架搭建

python pytest selenium 自动化测试框架搭建 公司一直有这个自动化测试需求,前期利用c++或者python进行了一些自动化脚本的编写。这几天没有版本更迭,基于前期的工作,把自动化测试整理了一部分功能模块。现在的状态基本达到预期:即搭建了一个AutoTest框架,基于此实现了login、设备管理两个模块的自动化测试,并基于实际,进行了合理的wait优化,后期的工作就是继续完善其它模块的内容。截止2020年7月1日11:41:37,该架构,可实现模块自动化测试、html报告输出、...

没有蛀牙lm的博客 1万+

Python+selenium+pytest 自动化测试环境搭建

Python + Selenium + Pytest 自动化测试环境搭建。

weixin_69649048的博客 1201
上一篇: Python OS 创建文件夹的两种方法
下一篇: Charles简单使用教程(持续更新)
高级拳师
博客等级 码龄14年 1297粉丝 21原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值