2.9 playwright之python实现

news2024/11/18 20:20:21

1、目录结构如下

2、main.py

import os
import shutil

from playwright.sync_api import sync_playwright
from config.setting import config
from utils.template import Template
from utils.md5 import Md5
from utils.delete import del_files
import pytest
from utils.dir_check import check_dir
from utils.baseurl import get_baseUrl


def run():
    check_dir()
    data = os.listdir('data')
    m = Md5('case', 'log', 'case_md5.json')
    n = Md5('utils', 'log', 'template_md5.json')
    filter_list = m.filter()
    utils_list = n.filter()
    if 'template.py' not in utils_list:
        filter_list = []
        n.write_md5()
    for i in data:
        file_path = 'data' + '/' + i
        if os.path.isfile(file_path):
            temp = 'test_' + i
            if temp not in filter_list:
                Template.create_test_file(file_path, 'case')
    m.write_md5()


if __name__ == "__main__":
    run()
    del_files('results')
    pytest.main(['-s', '--alluredir=results'])
    os.system('allure generate --clean ./results/ -o ./report/')
    for file_name in os.listdir('resource'):
        src_file = os.path.join('resource', file_name)
        dst_file = os.path.join('report', file_name)
        if os.path.exists(dst_file):
            os.remove(dst_file)
        shutil.copy(src_file, 'report')
    os.system('allure open -h 127.0.0.1 -p 8883 ./report/')

 3、conftest.py

import pytest
from playwright.sync_api import sync_playwright
from config.setting import config
from playwright.sync_api import Page
from utils.operate import operate
from utils.baseurl import get_baseUrl
import os
import allure
from utils.video import generate_video


@pytest.fixture(scope='session')
def page():
    browser = sync_playwright().start().chromium.launch(headless=False, slow_mo=500)
    page = browser.new_page(ignore_https_errors=True, record_video_dir='temp')
    page.goto(get_baseUrl(config))
    operate(config['username'], page)
    operate(config['password'], page)
    operate(config['submit'], page)
    return page


def log(request):
    with open('log/http.txt', 'a', encoding='utf-8') as w:
        w.write(f'{request}.url' + '\n')


@pytest.fixture(scope='function', autouse=True)
def after(page: Page):
    yield
    page.on("request", lambda request: log(request))


@pytest.fixture(scope='session', autouse=True)
def clear(page: Page):
    yield
    # page.close()
    p = generate_video('temp', 'video')
    allure.attach.file(p, f'{os.path.basename(p)}', attachment_type=allure.attachment_type.WEBM, extension='WEBM')

4、case目录,内容和目录都是自动生成

5、config目录,保存配置

dir_collection.py

配置中的目录都是自动生成

dir_collections = [
    'case',
    'log',
    'img',
    'video',
    'temp'
]

env.py

环境变量配置

env = {
    'prod': '',
    'dev': '',
    'test': 'http://test.lan'
}

setting.py

config = {
    'baseUrl': '',
    'url': '/user/login',
    'username': {
        'selector': '#userName',
        'type': 'input',
        'value': 'test'
    },
    'password': {
        'selector': '#password',
        'type': 'input',
        'value': '123'
    },
    'submit': {
        'selector': '#root > div > div > div:nth-child(1) > div > form > div:nth-child(3) > button',
        'type': 'button'
    },
}

6、data目录

case中的测试文件,便是依据data中的数据自动生成的

homepage.py

homepage_cfg = [
    {
        'name': 'homepage',
        'url': '',
        'step': [
        ],
        'assert': [
            {
                'selector': '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(1) > div '
                            '> div > div._3A9TZ-vnPrcf2IwqBmUPoX',
                'value': '违规告警数量'
            },
            {
                'selector': '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(2) > div '
                            '> div > div._3A9TZ-vnPrcf2IwqBmUPoX',
                'value': '确认违规告警数量1'
            },
            {
                'selector': '#content > div > div > div > div > div.react-grid-layout.layout > div:nth-child(3) > div '
                            '> div > div._3A9TZ-vnPrcf2IwqBmUPoX',
                'value': '未确认违规告警数量'
            },
        ]
    }

]

keyword.py

keyword_cfg = [
    {
        'name': 'keyword',
        'url': '/keyword/info',
        'step': [
            {
                "type": 'input',
                "selector": 'text=关键词组名称',
                "value": 'UI测试'
            },
            {
                "type": 'input',
                "selector": 'text=关键词组描述',
                "value": 'UI新建关键词'
            },
        ],
        'assert': [
            {
                'selector': '#content > div > div > div > h3',
                'value': '新增关键词策略'
            },
            {
                'selector': '#content > div > div > div > div > div > div > div > form > div:nth-child(1) > div.ant-form-item-label > label',
                'value': '关键词组名称'
            },
            {
                'selector': '#content > div > div > div > div > div > div > div > form > div:nth-child(2) > div.ant-form-item-label > label',
                'value': '关键词组描述'
            },
        ]
    }

]

7、img目录,保存错误截图的目录,自动生成

8、log目录,保存请求日志和两个md5文件,这两个md5文件主要用来辨别每次运行是否要重新生成case目录中的测试文件

9、report目录,allure命令自动生成

10、resource目录,由于对allure的报告进行了小量的修改,所以,需要保留resource目录,当生成report后,就会将resource目录中的内容和report中的内容替换

11、results目录,allure命令生成,保存测试结果数据

12、temp目录,自动生成临时目录,录制的视频文件会存到temp,然后会对视频进行改名另存到video目录,temp每次运行前自动生成,运行后,自动删除

13、utils目录,存储封装方法的目录

add_style.py

from playwright.sync_api import Page


def add_style(page: Page, elements, flag: int):
    if flag == 0:
        script = f"document.querySelector('{elements}').setAttribute('style','border-style:solid " \
                 f";border-color:green') "
    else:
        script = f"document.querySelector('{elements}').setAttribute('style','border-style:solid " \
                 f";border-color:red') "
    page.evaluate(script)

assert_element.py

from typing import List
from playwright.sync_api import Page
from utils.add_style import add_style
from utils.screenshot import error_screenshot


def assert_element(arr: List, page: Page):
    li = []
    if arr:
        for i in arr:
            if page.query_selector(i['selector']):
                text = page.query_selector(i['selector']).inner_text()
                if text == i['value']:
                    add_style(page, i['selector'], 0)
                    pass
                else:
                    add_style(page, i['selector'], 1)
                    li.append(i['value'])
            else:
                li.append(i['selector'])
    if li:
        error_screenshot(page, 'img')
        raise AssertionError(f"some elements in {str(li)} isn't matched or exists")

baseurl.py

from typing import Dict
from config.env import env
from utils.params_error import ParamsError


def get_baseUrl(conf: Dict):
    import sys
    if len(sys.argv) > 1:
        if sys.argv[1] == 'dev':
            conf['baseUrl'] = env['dev']
        elif sys.argv[1] == 'prod':
            conf['baseUrl'] = env['prod']
        elif sys.argv[1] == 'test':
            conf['baseUrl'] = env['test']
        else:
            raise ParamsError('python main.py [test]|[prod]|[dev]')
    else:
        raise ParamsError('python main.py [test]|[prod]|[dev]')
    url = conf['baseUrl'] + conf['url']
    return url

delete.py

import os


def del_files(dir_path: str):
    if os.path.exists(dir_path):
        for filename in os.listdir(dir_path):
            filepath = os.path.join(dir_path, filename)
            try:
                if os.path.isfile(filepath):
                    os.unlink(filepath)
            except Exception as e:
                print(f"Error deleting {filepath}: {e}")

dir_check.py

import os
from config.dir_collection import dir_collections


def check_dir():
    li = os.listdir()
    for i in dir_collections:
        if i not in li:
            os.mkdir(i)

md5.py

import hashlib
import json
import os
from json import JSONDecodeError


class Md5:
    def __init__(self, dir_path, md5_path, file_name):
        self.dir_path = dir_path  # 目录路径
        self.md5_path = md5_path  # MD5文件路径
        self.file_name = file_name  # MD5文件名
        file_path = os.path.join(md5_path, file_name)
        if not os.path.exists(file_path):
            open(file_path, mode='w+', encoding='utf-8').close()  # 如果MD5文件不存在,则创建该文件

    def generate_md5(self):
        temp = {}
        # 如果dir_path是文件而不是目录,则抛出IOError异常
        if os.path.isfile(self.dir_path):
            raise IOError(f'Message: parameter <dir_path:{self.dir_path}> must be directory')
        else:
            dir_list = os.listdir(self.dir_path)  # 获取目录下的文件列表
            if len(dir_list) != 0:
                for i in dir_list:
                    md5 = hashlib.md5()  # 创建MD5对象
                    file_path = os.path.join(self.dir_path, i)  # 获取文件路径
                    if os.path.isfile(file_path) and os.path.basename(file_path).endswith('.py'):  # 如果是文件
                        with open(file_path, mode='r', encoding='utf-8') as f:
                            md5.update(f.read().encode(encoding='utf-8'))  # 更新MD5值
                            hex_md5 = md5.hexdigest()  # 获取MD5值
                            temp[i] = hex_md5  # 将文件名和MD5值添加到字典中
            return temp  # 返回字典

    def write_md5(self):
        file_path = os.path.join(self.md5_path, self.file_name)
        # 将generate_md5()生成的字典写入到文件中
        json.dump(self.generate_md5(), open(file_path, mode='w+', encoding='utf-8'))

    def read_md5(self):
        file_path = os.path.join(self.md5_path, self.file_name)
        try:
            with open(file_path, mode='r', encoding='utf-8') as f:
                # 读取文件中的json数据并返回
                return json.load(f)
        except JSONDecodeError:
            # 如果文件中的json数据解析失败,则返回空字典
            return {}

    def filter(self):
        old_md5 = self.read_md5()  # 获取旧的MD5值
        new_md5 = self.generate_md5()  # 获取新的MD5值
        # 返回新旧md5值相同的文件名列表
        return [k for k, v in new_md5.items() if k in old_md5 and v == old_md5[k]]

operate.py

from playwright.sync_api import Page


def operate(d: dict, page: Page):
    if d.get('type') == 'input':
        page.query_selector(d.get('selector')).fill(d.get('value'))
    elif d.get('type') == 'button':
        page.query_selector(d.get('selector')).click()

params_error.py

class ParamsError(Exception):
    def __init__(self, msg: str):
        super(ParamsError, self).__init__(msg)

parse.py

from playwright.sync_api import Page
from config.setting import config


def parse(conf: dict, page: Page):
    url = config['baseUrl'] + conf['url']
    if url != '':
        page.goto(url)
    if conf['step']:
        for i in conf['step']:
            if i.get('type') == 'input':
                page.query_selector(i.get('selector')).fill(i.get('value'))
            elif i.get('type') == 'button':
                page.query_selector(i.get('selector')).click()

screenshot.py

import time

import allure
from playwright.sync_api import Page


def error_screenshot(page: Page, path: str):
    file_path = f'{path}/{int(time.time())}.png'
    page.screenshot(path=file_path, type='png', full_page=True)
    allure.attach.file(file_path, f'{path}/{int(time.time())}', attachment_type=allure.attachment_type.PNG,
                       extension='PNG')

template.py

import os


class Template:
    @staticmethod
    def check_todo_file(file_path: str) -> bool:
        """
        检查文件内容中是否包含 '# TODO' 字符串

        Args:
            file_path (str): 文件路径

        Returns:
            bool: 如果包含 '# TODO' 字符串则返回 True,否则返回 False
        """
        with open(file_path, mode='r+', encoding='utf-8') as file:
            return '# TODO' in file.read()

    @staticmethod
    def create_test_file(file_path: str, target_path: str) -> None:
        """
        创建测试文件

        Args:
            file_path (str): 文件路径
            target_path (str): 目标路径
        """
        if Template.check_todo_file(file_path):
            print(f'Message: 发现 <TODO> 标记,文件 <{file_path}> 尚未完成')
            return

        file_name = os.path.basename(file_path).replace('.py', '')
        import_name = f'{file_name}_cfg'
        test_file_path = os.path.join(target_path, f'test_{file_name}.py')

        with open(test_file_path, mode='w+', encoding='utf-8') as file:
            file.write(f'''import pytest
import allure
from data.{file_name} import {import_name}
from playwright.sync_api import Page
from utils.parse import parse
from utils.assert_element import assert_element


@allure.suite('{file_name}')
class Test_{file_name.capitalize()}:

    @allure.sub_suite('{import_name}')
    @pytest.mark.parametrize('cfg', {import_name})
    def test_{file_name}(self, cfg, page):  
        parse(cfg, page)
        allure.dynamic.title(cfg['name'])
        assert_element(cfg['assert'], page)
''')
        print(f"Message: 文件 <{test_file_path}> 创建成功")

 video.py

import os
import time


# def remove_video(path: str):
#     print(os.listdir(path))
#     if os.listdir(path):
#         for i in os.listdir(path):
#             os.remove(f'{path}/{i}')


def generate_video(source_path: str, target_path: str):
    p = f"{target_path}/{int(time.time())}.webm"
    while True:
        if os.listdir(source_path):
            for i in os.listdir(source_path):
                os.renames(f'{source_path}/{i}', p)
            break
    return p

14、video目录自动生成,存放录制视频的目录

15、报告效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/563121.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试被问麻了...

前几天组了一个软件测试面试的群&#xff0c;没想到效果直接拉满&#xff0c;看来大家对面试这块的需求还是挺迫切的。昨天我就看到群友们发的一些面经&#xff0c;感觉非常有参考价值&#xff0c;于是我就问他还有没有。 结果他给我整理了一份非常硬核的面筋&#xff0c;打开…

全网最全性能测试总结,分析性能测试问题+性能调优方案...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能分析和优化一…

【录用案例】2区毕业快刊仅34天录用,新增8篇录用、9篇见刊、13篇检索

2023年5月13日-2023年5月19日&#xff0c;经核实&#xff0c;由我处Unionpub学术推荐的8篇论文已被期刊部录用、9篇见刊、13篇检索&#xff1a; 2区系统类SSCI 【期刊简介】IF:2.5-3.0&#xff0c;JCR2区&#xff0c;中科院4区 【检索情况】SSCI 在检&#xff0c;正刊 【征稿…

本地项目上传到Git(Gitee)仓库

一、步骤解答&#xff08;详细图解步骤见第二大点&#xff09; 1、打开我们的项目所在文件夹&#xff0c;我们发现是不存在.git文件 2、在你的项目文件夹外层【鼠标右击】弹出菜单&#xff0c;在【鼠标右击】弹出的菜单中&#xff0c;点击【Git Bash Here】&#xff0c;弹出运…

循环队列+OJ题之设计循环队列

生命不是要等待风暴过去&#xff0c;而是要学会在风暴中跳舞。 ——卡莉尔吉布朗目录 &#x1f33a;前言&#xff1a; &#x1f341;一.循环队列是什么&#xff1f; &#x1f34f;二.循环队列有什么作用&#xff1f; &#x1f340;三.OJ题之设计循环队列 1…

实战演练 | Navicat 数据生成功能

数据生成的目的是依据某个数据模型&#xff0c;从原始数据通过计算得到目标系统所需要的符合该模型的数据。数据生成与数据模型是分不开的&#xff0c;数据生成的结果应该符合某个数据模型对于数据的具体要求。所以&#xff0c;随着数据模型的发展&#xff0c;数据生成的方法相…

window 利用Qt-windeployqt打包exe程序 一个简单的实例

用一个简单的实例展示下window 如何使用QT-windeployqt打包exe程序使得其可以在别的电脑上运行 一、release模式获得exe可执行文件 新建一个QT项目 构建选择使用CMake base class选择QMainWindow Kit Selection一定要注意&#xff0c;我选的是MinGW 32-bit UI设计 mainwindow.…

手机充电宝电子充气泵方案

该充气泵产品方案的运行原理是通过电动机将电能转化为机械能&#xff0c;带动电机做往复运动&#xff0c;从而产生大量压缩空气&#xff0c;达到快速充气的效果。该充气泵可用于气垫床、汽车轮胎、自行车轮胎、足球、游泳圈等各类充气物品。产品设计以人性化为主&#xff0c;简…

VMware重新安装后没有VMnet1和VMnet8网络

问题&#xff1a; VMware重新安装后&#xff0c;没有自动生成VMnet1和VMnet8网络, 并且使用VMware自带的虚拟网络编辑器也无法生成。 导致主机无法ping通虚拟机。 如下图&#xff1a;点击该选项&#xff0c;然后应用&#xff0c;转一会圈也没有产生对应的网络适配器。 问题原…

物联网技术助力物流智能化:从货物追踪到配送优化

目录 前言 物流领域的IoT设备 物流领域的应用 二、仓库管理 三、物流配送 IoT组合应用 区块链在物流领域应用 展望 前言 随着全球贸易和物流业的快速发展&#xff0c;物流领域的智能化和自动化已成为不可避免的趋势。而物联网技术作为一种重要的数字技术&#xff0c;已经在物流…

VIsual Studio内引用Lua解释器,编译Lua源码,执行Lua脚本

前言 本篇在讲什么 在Visual Studio中引入lua的解释器 使用C调用Lua文件 本篇适合什么 适合初学Lua的小白 适合需要C/C和lua结合开发的人 本篇需要什么 对Lua语法有简单认知 对C/C语法有简单认知 依赖Lua5.1的环境 依赖VS 2017编辑器 本篇的特色 具有全流程的图文…

Shellcode分离加载实现免杀的两种方式(VT免杀率:1/68)

简介 本文详细介绍了如何通过文件加载和远程URL加载方式实现Shellcode分离加载&#xff0c;以规避安全软件的检测。文章首先描述了通过Metasploit Framework生成的shellcode文件加载的过程&#xff0c;并提供了相关的C代码。 为了避免被杀毒软件检测&#xff0c;利用动态API调…

自动化测试-DevOps如何实施?看看10年测试大佬的总结...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Selenium4自动化测…

2023最新!软件测试高频面试题基础知识点分享

近期也算是抽取出大部分休息的时间&#xff0c;为大家准备了一份通往大厂面试的小捷径&#xff0c;准备了一整套软件测试复习面试的刷题以及答案&#xff0c;我知道很多同学不知道怎么复习&#xff0c;不知道学习过程中哪些才是重点&#xff0c;其实&#xff0c;你们经历过的事…

内网渗透(八十二)之 CVE-2019-1040 NTLM MIC 绕过漏洞

CVE-2019-1040 NTLM MIC 绕过漏洞 漏洞背景 2019年6月11日,微软发布6月份安全补丁更新。在该安全补丁更新中,对 CVE-2019-1040 漏洞进行了修复。该漏洞存在于Windwos 大部分版本中,当中间人攻击者能够成功绕过NTLM 消息完整性校验(MIC)时,Windows 存在可能可篡改的漏洞…

R语言实践——rWCVP生成可发表级别的物种发现记录矩阵

rWCVP生成可发表级别的物种发现记录矩阵 介绍1. 查询一组示例数据2. 生成和格式化出现矩阵3. 额外地对国家进行处理 介绍 世界维管植物名录&#xff08;WCVP&#xff09;提供了已知的>340&#xff0c;000种维管植物物种的分布数据。该分布数据可用于构建植物物种名录的发现…

解密报错-java.security.InvalidKeyException: Illegal key size(本机解密正常,服务器解密报错)

记录在对接微信接口时需要的问题&#xff0c;对微信消息进行解密时报错&#xff0c;在本地进行解密是正常的&#xff0c;但部署到服务器进行解密就会报错 报错信息 java.security.InvalidKeyException: Illegal key sizeat javax.crypto.Cipher.checkCryptoPerm(Cipher.java:…

windows 系统扩容C盘注意事项

windows系统大家都不陌生&#xff0c;是大家用的最多的操作系统。在实际的使用中&#xff0c;遇到需要扩容C盘的情况不是很多&#xff0c;但是如果遇到了&#xff0c;有以下几个事项需要大家注意&#xff1a; 剩余空间是否充足 不论当前服务器是物理服务器还是虚拟机&#xff…

Slack工作区SolidUI 集成Claude使用流程,替代GPT3.5

背景 Claude 由 OpenAI 副总裁离职创立的和chatGPT对位的AI机器人&#xff0c;号称是chatGPT一生的对手&#xff01; 比之前的一些模型如GPT-3 要强大得多&#xff0c;因此Claude 被认为是ChatGPT 最有力的竞争对手。Claude 的研发公司是专注人工智能安全和研究的初创公司Anth…

谷歌Bard_VS_Baize-7B_VS_文心一言体验对比

2023年4月4日&#xff0c;来自加州大学圣迭戈分校、中山大学和微软亚研的研究者提出了Baize&#xff0c;该模型是让ChatGPT 自我对话&#xff0c;批量生成高质量多轮对话数据集&#xff0c;利用该数据集对LLaMA进行微调得到的&#xff08;目前版本还没有RLHF&#xff09; 关于B…