测试平台系列(135) har文件转为RequestInfo.md

2022/6/13 测试平台接口测试FastApiPythonReact

大家好~我是米洛
我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持。
欢迎关注我的公众号米洛的测开日记,获取最新文章教程!

# 回顾

上一节我们小小地改造了一下断言模块,使得JSONPath能够更深入pity心。这一节我们就来聊聊har文件转pity用例的上半部分:har转RequestInfo

# Har文件分析

har文件,相信经常用Chrome浏览器的都知道,浏览器录制的数据可以转换为har文件,来看下面一张图:

上面是我录制了掘金平台的相关接口,我们在任意处鼠标右键,或者点击这个下载按钮,都能够把这些请求保存为har文件。

我们知道写用例是一件很麻烦的事情,假设一个链路有5个接口,假设你在测试一个用户领券买东西的流程,你可能会经历以下步骤:

  1. 用户登录
  2. 用户去领券中心领取100-2的优惠券
  3. 用户搜索符合优惠券的商品,并将它加入购物车
  4. 用户去购物车结算,生成订单(并使用此优惠券)
  5. 用户支付

可以看到,这个链路其实就不太短,如果我们在不熟悉这几个接口参数的情况下,我们要对照接口文档,去造对应的参数,甚至可能造了一会发现case还是没跑通。

那如果以上场景,你在浏览器操作的时候,就能帮你汇总这些接口,并抓取下来,你写用例的时候只需要把固定的参数比如账号,商品id等参数化,那会不会非常友好,高效,而且链路肯定是通的,因为你在浏览器已经完成过这样的流程了。

所以这就是为什么,我们要支持用例录制的原因,因为雀氏方便,雀氏蟀啊!

好像有点扯远了,我们继续聊这个har的事情。总之har就是chrome tools帮我们录制好我们发出的请求,剩下的筛选,生成用例就得靠我们自己了。

来看看har文件里面的内容吧:

可以看到,他其实是一个JSON数据,所以我们用JSON库解析它就好了。

# XHR和Fetch

虽然js/css/img等等请求也算http请求,但一般来说与服务端交互的还得是xhr和fetch。fetch可以理解为二代xhr(ajax)。

所以我们只需要获取这2种类型的数据即可。

而且它把request和response的数据都分开了,我们可以很方便地读取

# 编写Convetor

  • app/core/request/convertor.py
__author__ = "woody"

from typing import List

from app.core.request.request import RequestInfo

"""
request转换器,支持har到
"""


class Convertor(object):

    @staticmethod
    def convert(file, regex: str = None) -> List[RequestInfo]:
        raise NotImplementedError

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

内容很简单,并且支持正则,目的是为了过滤不要的url,或者说是为了筛选出想要的接口。

当然这都是抽象接口,接着我们来写具体的实现。

# 编写HarConvetor

  • app/core/request/har_convertor.py
import json
import re
from typing import List

from app.core.request.convertor import Convertor
from app.core.request.request import RequestInfo
from app.excpetions.convert.ConvertException import HarConvertException


class HarConvertor(Convertor):
    @staticmethod
    def convert(file, regex: str = None) -> List[RequestInfo]:
        try:
            with open(file, "r", encoding="utf-8") as f:
                ans = []
                flag = None
                if regex is not None:
                    flag = re.compile(regex)
                # 加载har请求数据
                data = json.load(f)
                entries = data.get("log", {}).get("entries")
                if not entries:
                    raise HarConvertException("entries数据为空")
                for entry in entries:
                    # 如果是fetch或xhr接口,说明是http请求(暂不支持js)
                    if entry.get("_resourceType").lower() in ("fetch", "xhr"):
                        info = RequestInfo()
                        request_data = entry.get("request")
                        info.url = request_data.get("url")
                        if flag is not None and not re.findall(flag, info.url):
                            # 由于不符合预期的url,所以过滤掉
                            continue
                        response_data = entry.get("response")
                        info.body = HarConvertor.get_body(request_data)
                        info.status_code = response_data.get("status")
                        info.request_method = request_data.get("method")
                        info.request_headers = HarConvertor.get_kv(request_data)
                        info.response_headers = HarConvertor.get_kv(response_data)
                        info.cookies = HarConvertor.get_kv(response_data, "cookies")
                        info.request_cookies = HarConvertor.get_kv(request_data, "cookies")
                        info.response_content = response_data.get("content", {}).get("text")
                        ans.append(info)
                return ans
        except HarConvertException as e:
            raise HarConvertException(f"har文件转换异常: {e}")
        except Exception as e:
            raise HarConvertException(f"har文件转换失败: {e}")

    @staticmethod
    def get_kv(request_data: dict, key: str = "headers") -> dict:
        """
        通过response/request获取header信息
        :param key:
        :param request_data:
        :return:
        """
        headers = request_data.get(key)
        result = dict()
        for h in headers:
            result[h.get("name")] = h.get("value")
        return result

    @staticmethod
    def get_body(request_data: dict):
        data = request_data.get("postData", {})
        return data.get("text")


if __name__ == "__main__":
    requests = HarConvertor.convert("./pity.fun.har", "juejin.cn")
    print(requests)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

这里主要做了2个事情,读取+转换,首先我们将读取的har利用json.load转为Python数据。

接着去找到对应的请求数组: entries, 遍历之。

接着就是拼接为我们自己的RequestInfo了,到这里我们的转换工作就完成了,我们还自定义了HarConvertException, 是为了更好地区别异常类型

这样设计的好处是,以后如果要支持hab, hba, hxr各种格式的时候,我们只需要实现convert方法即可。

接着就是我们最重要的转换环节了,可能需要博主费点脑子了。大家做好心理准备~