Skip to content

Meituan-Dianping/lyrebird-bugit

Repository files navigation

Lyrebird - BugIt plugin

Unit Test Publish to pypi CodeQL

PyPI PyPI GitHub

Lyrebird 是一个基于拦截以及模拟HTTP/HTTPS网络请求的面向移动应用的插件化测试平台。

BugIt plugin是一个Lyrebird的插件,用于汇总Lyrebird各插件信息,提交Bug。


简介

BugIt是一个Lyrebird插件。

通过BugIt可以轻松的将Lyrebird中收集的人机交互数据作为描述或者附件提交到Bug管理系统中(如JIRA)。

检查器(Checker)结合,即可实现一键提交bug的功能。

快速开始

环境要求

  • macOS OR Linux

  • Python3.7及以上

  • Lyrebird 1.6及以上

安装

pip3 install lyrebird-bugit

启动

lyrebird

功能介绍

Bug提交

读取脚本文件后,BugIt会得到一个Bug信息填充的界面,称之为Bug表单。

用户可以自定义Bug字段的 名称、先后顺序、填写样式、默认值。

�Bug表单

BugIt可以通过配置服务向任意Bug管理系统提交Issue

API数据获取

BugIt支持自动填充Lyrebird运行过程中抓取到的数据信息。

BugIt 获取API信息

报警获取

检查器(Checker)捕获报警后,可以通过通知中心随时跳转至BugIt。

在右侧数据面板中,可以对历史消息总线中的信息进行回溯,补充至Bug中。

BugIt获取报警信息

此外,还支持针对指定检查器的报警进行自动开Bug。

通过配置可以指定检查器,BugIt会将这些检查器的报警信息送入配置指定的脚本内,在脚本内自定义对报警信息的处理,即可实现Bug的自动上报功能。

缓存功能

按下[Commond]+[s]键,会将Bug相关字段信息进行存储。

缓存信息不受Lyrebird服务开关、浏览器缓存、脚本/界面切换的影响。

BugIt缓存功能

有效的利用缓存功能,是提高Bug上报效率的关键。

插件在Bugit中的应用

Android iOS插件

安装Android插件iOS插件后,BugIt支持设备信息扩展服务。

  • 设备信息获取
    • BugIt 支持将设备名称、设备系统版本、被测 App 信息(Bundle ID(iOS)/PackageName(Android))填充到Issue内容中。
  • 实时设备截图
    • BugIt 可以实时获取设备截图,并支持在截图上进行涂鸦、文本标记。
    • 提交 Bug 时,截图将作为附件一并提交给脚本配置服务。
  • Crash Log 获取
    • 在Lyrebird运行过程中,如发生 被测 App 发生 Crash,BugIt 会捕获到 Crash Log。
    • 提交 Bug 时,Crash Log 将作为附件一并提交给脚本配置服务。
    • Crash 获取 暂不支持 iOS 设备

其他插件在BugIt中的应用

BugIt脚本

在 ~/.lyrebird/conf.json 中,BugIt会读取 “bugit.workspace”字段。并将此字段值作为读取模板的根目录。

该目录下所有模板都会加载到BugIt中,并在UI中可选。

刷新BugIt界面即可重新加载所有模板。

模板使用说明

BugIt通过模板定义UI以及提交的行为。通过不同的模板支持JIRA以及其他的Bug管理系统。

BugIt模板是一个Python文件,要求使用Python3.7及以上的版本编写。 BugIt 脚本工作原理示意图 配置脚本需包含以下三部分

  • init配置文件
  • form()
  • submit()

init配置文件

BugIt 通过 name 来标识配置文件。

注意:BugIt 脚本必须含有 'name' 属性

"""
Template name
"""        
name = 'TEST-JIRA'

name 用于在 BugIt 前端页面上展示脚本名称。

name

选中配置文件后,BugIt init 脚本文件,获取脚本文件中回调方法form()、submit()。

form()

form() 方法用于自定义Bug表单的字段与填写样式。

Bug 中的每一字段由一dict定义(通常称之为FormItem),表单配置时,通过dict的以下关键字去控制字段的名称、默认值、样式等内容。

form()方法应返回一由FormItem组成的list。

FormItem dict说明

key 说明 枚举值 是否必填
name 展示在页面上的字段名称 -- Y
value 对应字段填充的值 -- N
component 字段展示的组件形式 'input'、'select'、'compoundTextarea' Y
options 配合 select component使用,用于存放select component的选项内容 -- N
custom keys 自定义属性,不影响UI组件展示,可以帮住更好地处理数据,详细用法请参考脚本高级 -- N

BugIt 支持的 component

  • input
form_item_input = {
    'name': '主题',
    'component': 'input',
    'value': 'defalut value'
}

input组件

input 组件会生成一个文本输入框,在声明时如果 value 不为空,则会作为默认值填充在输入框中

  • select
form_item_select = {
    'name': '版本',
    'component': 'select',
    'options':[
        {'id':'001','name':'option_1'},
        {'id':'002','name':'option_2'}
    ],
    'value': '001'
}

select组件

select组件会生成一个筛选框,筛选项由 options 定义,options 为一个 list,其中每一元素为 dict 类型。

option dict说明

key 说明 是否必填
id 筛选项唯一标识 Y
name 筛选项在前端展示文本内容 Y

select 组件中,value应为option dict中对应 id 的值

  • compoundTextarea

compoundTextarea组件会生成一个文本框,在声明时如果 value 不为空,则会作为默认值填充在文本框中

form_item_text = {
    'name': '描述',
    'component': 'compoundTextarea',
    'value': 'defalut value'
}

compoundTextarea组件

compoundTextarea组件在 BugIt 中还用于支持 Lyrebird 其他信息的扩展。

选择消息总线或其他插件的数据时,该数据将作为附加信息展示在 compoundTextarea 中。

附加信息

对应的数据信息将作为 'extraMsg' 存放在 FormItem中。 此时From Item变成

form_item_text = {
    'name': '描述',
    'component': 'compoundTextarea',
    'value': 'defalut value',
    'extraMsg':[
        {'message':'Flow Info'},
        {'message':'Notice Info'},
        {'message':'Devices Info'}
    ]
}

form()方法示例代码

def form(context):
    """
    BugIt callback function

    BugIt will call this function when user select this template from UI.
    This function should return a array, list all FormItem dict.

    """
    form_item_input = {
        'name': '主题',
        'component': 'input',
        'value': 'defalut value'
    }
    form_item_select = {
        'name': '版本',
        'component': 'select',
        'options':[
            {'id':'001','name':'option_1'},
            {'id':'002','name':'option_2'}
        ],
        'value': '001'
    }
    form_item_text = {
        'name': '描述',
        'component': 'compoundTextarea',
        'value': 'defalut value'
    }
    form = [form_item_input,form_item_select,form_item_text]
    return form

示例代码生成的 Bug 表单如下图所示 测试脚本表单生成

form()方法入参 context ,可用于传递缓存数据,相关用法详见脚本高级

submit()

提交函数返回一个数组,数组中包含若干提交步骤需要回调的函数。

submit()方法返回一组用于处理 submit 行为的方法。

方法说明

方法名 入参 说明
issue context 处理 Bug表单中的信息,通过 API 向 Bug 管理系统提交 Issue
attachments context 处理附件信息(如 Android插件iOS插件提供的截图或 Log),通过 API 向 创建的 Issue 中添加附件

submit()方法示例代码(以JIRA服务为例)

def submit():
    """
    BugIt callback function

    BugIt will call this function when user tap submit button.
    This function should return a array, contains all submit actions.

    Each submit-action function have a argument. It contains form data and attachments info.
    """
    return [issue, attachments]

def issue(context):
    """
    This function is used to submit Issue 
    """
    form_data = context['issue']
    jira_fields = {}

    # transform from_data to payload submited to API of JIRA
    for form_item in form_data:
        if form_item['name'] == '主题' :
            jira_fields['summary'] = form_item['value']
        elif form_item['name'] == '版本':
            jira_fields['version'] = {
                id:form_item['value']
                }
        elif form_item['name'] == '描述':
            jira_fields['description'] = form_item['value']
            # add extraMsg to description 
            if form_item['extraMsg']:
                for add_des in form_item['extraMsg']:
                    jira_fields['description'] +='\n'
                    jira_fields['description'] +='------------------------------\n'
                    jira_fields['description'] +=add_des['message']

    url = 'http://www.example.com/jira/rest/api/2/issue'
    header = {
        'Content-Type': 'application/json;charset=utf-8'
    }
    resp = requests.post(url, auth=('YOUR JIRA_USER_NAME', 'YOUR_JIRA_PASSWD'),json={"fields": jira_fields},headers=header)
    
    if resp.status_code >= 200 and resp.status_code < 300:
        body = json.loads(response.text)
        if body.get('key') :
            context['key'] = body['key']
        else:
            raise Exception(f'Submit failed {response.text}')
    else:
        raise Exception(f'Create issue failed with code {response.status_code}\n{response.text}')


def attachments(context):
    """
    This function is used to upload attachments to the Issue which has been created
    """
    key = context['key']
    attachments = context['attachments']

    if len(attachments) == 0:
        # No attachments
        return

    url = f'http://www.example.com/jira/rest/api/2/issue/{key}/attachments'
    headers = {
        'X-Atlassian-Token': 'nocheck'
    }
    multiple_files = []

    # transform attachment data to payload submited to API of JIRA
    for attachment in attachments:
        attachment_path = Path(attachment['path'])
        multiple_files.append(
            ('file', (attachment_path.name, open(str(attachment_path), 'rb')))
        )
    response = requests.post(url, files=multiple_files, headers=headers)

    if response.status_code == 200 and response.json()['code'] == 0:
        print('Submit attachments success')
    else:
        raise Exception(f'Submit failed {response.text}')

完整脚本请参考: 示例脚本

脚本高级功能请参考:脚本高级


开发者指南

开发环境

  • macOS OR Linux
  • Python3
  • NodeJS
  • vscode(推荐)
  • Chrome(推荐)

调试代码

Vscode debug配置

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Terminal (integrated)",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        },
        {
            "name": "Server",
            "type": "python",
            "request": "launch",
            "module": "lyrebird",
            "args": [
                "-b",
                "-vvv",
                "--plugin",
                "${workspaceFolder}"
            ],
            "console": "integratedTerminal"
        },
        {
            "name": "Client",
            "type": "chrome",
            "request": "launch",
            "url": "http://localhost:8080/",
            "webRoot": "${workspaceFolder}/frontend/src/",
            "sourceMapPathOverrides": {
                "webpack:///src/*": "${webRoot}/*"
            },
            "timeout": 30000
        }
    ]
}

后端代码

  1. 激活python虚拟环境

    通过 source venv/bin/activate 来激活该环境

  2. 通过Debug功能启动

    按照上面 debug配置中 python:Lyrebrid配置启动即可

前端代码

  1. 启动node server
# 进入前端目录
cd frontend

# 启动前端node serve
npm run serve
  1. 通过Debug功能启动浏览器

    按照上面 debug配置中 vuejs: chrome 配置启动即可

    注意: vscode 需要安装chrome debug插件