通用技术 通过 Har 生成测试脚本 (LR 为例)

among · 2016年06月27日 · 最后由 Lancats 回复于 2016年09月05日 · 3004 次阅读
本帖已被设为精华帖!

前言

HTTP Archive (HAR) format 是 http 协议交互的归档格式。
这个格式在本质上就是 utf8 格式的 json 字符串,存储着所有的 http 请求和 http 响应的数据,包括所有的 head 和 body。

如何获取 HAR 格式

一般,很多 proxy 类的工具,如 fiddler,charles,原来一直以为 charles 不支持保存为 har 格式,后来才知道是在 export 菜单里面:

通过代理和反向代理获取 http 报文

在 charles 中,支持代理,反向代理,端口转发 这三种主要的方法获取交互的报文。

  1. 代理模式:这个就是普通的代理,proxy 模式,浏览器都支持。
  2. 反向代理:简单说就是代理服务器,对于不支持设置代理的应用,如接口类,可以通过这个来获取报文。
  3. 端口转发:这个功能更强大,基于 tcp,udp 层的,对于 Socket 类的都能录到报文。一般如果不知道是什么协议的, 可以用这个,如果判断是 http 协议的, 最好用反向代理模式的,这样可以更直观的看到解析后的报文。

解析 har

通过代理和反向代理的方式,可以获取到 http 报文,导出为 har 格式后,进行解析,可以直接生成测试脚本,以生成 loadrunner 脚本为例。
基于 python 的脚本如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from optparse import OptionParser
import os
import sys
import json

# check env
if sys.version_info < (3, 4):
    raise RuntimeError('At least Python 3.4 is required.')

restype = ('js', 'css', 'jpg', 'gif', 'ico', 'png')

def list2dic(headers):
    header_dic = dict()
    for head in headers:
        if head['name'] in header_dic:
            header_dic[head['name']] = header_dic[head['name']] + ',' + head['value']
        else:
            header_dic[head['name']] = head['value']
    return header_dic

def dictoand(dct):
    res_list = list()
    for tp in dct:
        res_list.append('%s=%s' % (tp['name'], tp['value']))
    return '&'.join(res_list)

def dict2lr(lrsc):
    tmpl = '''
    web_custom_request("%(name)s",
        "URL=%(url)s",
        "Method=%(method)s",
        "Resource=%(res)s",
        "Referer=%(referer)s",
        "EncType=%(enctype)s",
        "Body=%(body)s",
        LAST);'''
    # url
    url = lrsc['url']
    method = lrsc['method']
    name = url.split('/')[-1]
    name = name.split('?')[0]
    suff = url.split('.')[-1]
    # Resource type
    global restype
    res = '0'
    if suff in restype:
        res = '1'

    # Content-Type
    enctype = ''
    if 'Content-Type' in lrsc:
        enctype = lrsc['Content-Type']
    # Referer
    referer = ''
    if 'Referer' in lrsc:
        referer = lrsc['Referer']

    # Body
    body = ''
    if 'posttext' in lrsc:
        body = lrsc['posttext']
    elif 'postparams' in lrsc:
        body = dictoand(lrsc['postparams'])
    body = body.replace('"', '\\"')
    res = tmpl % {'name': name, 'url': url, 'method': method, 'enctype': enctype, 'referer': referer, 'res': res,
                  'body': body}
    # Head
    if 'SOAPAction' in lrsc:
        res = ("\n" + ' web_add_header("SOAPAction", "%s")' + ";\n" + res) % lrsc['SOAPAction']
    return res

def parhar(harfile):
    res = list()
    try:
        FH = open(harfile, mode='r', encoding='utf-8', closefd=True)
        all = json.load(FH)
        FH.close()
    except Exception as ex:
        print('Open har file errr: %s' % ex)
        quit()

    har_ver = all['log']['version']
    creater = all['log']['creator']['name']
    entries = all['log']['entries']
    ct = len(entries)
    for et in entries:
        stm = et['startedDateTime']
        req = et['request']
        rsp = et['response']
        lrsc = dict()
        if '_charlesStatus' in rsp and rsp['_charlesStatus'] != 'Complete':
            continue
        lrsc['method'] = req['method']
        lrsc['url'] = req['url']
        headers = req['headers']
        # http head
        header_dic = list2dic(headers)
        if 'SOAPAction' in header_dic:
            lrsc['SOAPAction'] = header_dic['SOAPAction'].replace('"', '\\"')
        if 'Referer' in header_dic:
            lrsc['Referer'] = header_dic['Referer']
        if 'Content-Type' in header_dic:
            lrsc['Content-Type'] = header_dic['Content-Type']
        if lrsc['method'] == 'GET':
            pass
        elif lrsc['method'] == 'POST':
            if 'postData' in req:
                if 'text' in req['postData']:
                    lrsc['posttext'] = req['postData']['text']
                if 'params' in req['postData']:
                    lrsc['postparams'] = req['postData']['params']
                if 'mimeType' in req['postData']:
                    lrsc['postmime'] = req['postData']['mimeType']
        else:
            continue
        res.append(dict2lr(lrsc))
    return res

if __name__ == '__main__':
    parse = OptionParser()
    parse.add_option("-f", action="store", dest="harfile", help='harfile path')
    parse.add_option("-o", action="store", dest="lrfile", help='action.c path')
    (options, args) = parse.parse_args()

    if options.harfile is None or options.lrfile is None:
        parse.print_help()
        quit()
    if not os.path.exists(options.harfile):
        print('Har file %s not exist' % options.harfile)
        quit()
    res = parhar(options.harfile)
    file = open(options.lrfile, mode='w', encoding='utf-8')
    for sc in res:
        file.write(sc)
        file.write("\n")
    file.close()
    print('Output to %s' % options.lrfile)

生成的脚本,可以直接 copy 到 LR 中使用,根据需要做参数化和关联,对于不支持通过 lr 录制的,如接口类,app 类的,通过这种方式可以更快的生成脚本。
可能还存在部分未考虑到的问题,如请求并发,编码等问题。需要根据实际情况调整。

其他

对于其他工具,也可以自己解析 har 生成对应的测试案例、脚本等。
另外,在 charles 中,还支持 web interface,开启后,可以远程管理 charles,打开或关闭选项,下载 har 文件,有需要的可以自己写请求实时远程管理,做到进一步的自动化。
有需要就自己折腾吧。

共收到 6 条回复 时间 点赞

单纯为了方便录制 app 的 http 请求,可以直接通过代理 LR 录制;楼主方法使用很多场景,赞.
Charles 还可以这么玩,受教了.

赞!
之前针对 LR 有时候无法录制的情况,也做了一个类似的,可以将 Fiddler2 录制生成的文件转换为 LR 脚本。
https://github.com/debugtalk/Fiddler2LRScript

#3 楼 @debugtalk 用熟了 charles,很早就想直接解析 charles 保存的 chls 格式的文件,发现格式太复杂,也是后来才知道在 export 里面可以直接导出为 har 格式。

配合 charles 的 web interface 接口,可折腾空间很大。

赞一个,不错,可以尝试下。

不错,不过 LR 已经支持直接打开 HAR,自动转换成 LR 的脚本的。

赞一个 不错

恒温 将本帖设为了精华贴 06月28日 03:15
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册