mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 08:59:44 +08:00
change: remove har2case, move all features to go version
This commit is contained in:
2
.github/workflows/smoketest.yml
vendored
2
.github/workflows/smoketest.yml
vendored
@@ -36,10 +36,8 @@ jobs:
|
||||
- name: Test commands
|
||||
run: |
|
||||
poetry run hrun -V
|
||||
poetry run har2case -h
|
||||
poetry run httprunner run -h
|
||||
poetry run httprunner startproject -h
|
||||
poetry run httprunner har2case -h
|
||||
- name: Run smoketest - postman echo
|
||||
run: |
|
||||
poetry run hrun examples/postman_echo/request_methods
|
||||
|
||||
1
.github/workflows/unittest.yml
vendored
1
.github/workflows/unittest.yml
vendored
@@ -33,7 +33,6 @@ jobs:
|
||||
poetry run httprunner
|
||||
poetry run hmake
|
||||
poetry run hrun
|
||||
poetry run har2case
|
||||
poetry run coverage run --source=httprunner -m pytest httprunner
|
||||
- name: coverage report
|
||||
run: |
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
|
||||
**python version**
|
||||
|
||||
- change: remove locust, you should run load tests with go version
|
||||
- change: remove har2case, move all features to go version, replace with `hrp run`
|
||||
- change: remove locust, you should run load tests with go version, replace with `hrp boom`
|
||||
- change: remove fastapi and uvicorn dependencies
|
||||
- fix: ignore exceptions when reporting GA events
|
||||
- fix: remove misuse of NoReturn in Python typing
|
||||
|
||||
@@ -140,8 +140,6 @@ func (tc *TCase) ToTestCase() (*TestCase, error) {
|
||||
return testCase, nil
|
||||
}
|
||||
|
||||
var ErrUnsupportedFileExt = fmt.Errorf("unsupported testcase file extension")
|
||||
|
||||
// APIPath implements IAPI interface.
|
||||
type APIPath string
|
||||
|
||||
|
||||
@@ -231,6 +231,7 @@ func Interface2Float64(i interface{}) (float64, error) {
|
||||
|
||||
var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension")
|
||||
|
||||
// LoadFile loads file content with file extension and assigns to structObj
|
||||
func LoadFile(path string, structObj interface{}) (err error) {
|
||||
log.Info().Str("path", path).Msg("load file")
|
||||
file, err := readFile(path)
|
||||
|
||||
@@ -29,17 +29,17 @@ func NewHAR(path string) *har {
|
||||
}
|
||||
|
||||
type har struct {
|
||||
path string
|
||||
filterStr string
|
||||
excludeStr string
|
||||
profileJSON map[string]interface{}
|
||||
outputDir string
|
||||
path string
|
||||
filterStr string
|
||||
excludeStr string
|
||||
profile map[string]interface{}
|
||||
outputDir string
|
||||
}
|
||||
|
||||
func (h *har) SetProfile(path string) {
|
||||
log.Info().Str("path", path).Msg("set profile")
|
||||
h.profileJSON = make(map[string]interface{})
|
||||
err := builtin.LoadFile(path, h.profileJSON)
|
||||
h.profile = make(map[string]interface{})
|
||||
err := builtin.LoadFile(path, h.profile)
|
||||
if err != nil {
|
||||
log.Warn().Str("path", path).
|
||||
Msg("invalid profile format, ignore!")
|
||||
@@ -145,7 +145,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
||||
Request: &hrp.Request{},
|
||||
Validators: make([]interface{}, 0),
|
||||
},
|
||||
profileJSON: h.profileJSON,
|
||||
profile: h.profile,
|
||||
}
|
||||
if err := step.makeRequestMethod(entry); err != nil {
|
||||
return nil, err
|
||||
@@ -173,7 +173,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
||||
|
||||
type tStep struct {
|
||||
hrp.TStep
|
||||
profileJSON map[string]interface{}
|
||||
profile map[string]interface{}
|
||||
}
|
||||
|
||||
func (s *tStep) makeRequestMethod(entry *Entry) error {
|
||||
@@ -201,7 +201,7 @@ func (s *tStep) makeRequestParams(entry *Entry) error {
|
||||
|
||||
func (s *tStep) makeRequestCookies(entry *Entry) error {
|
||||
s.Request.Cookies = make(map[string]string)
|
||||
cookies, ok := s.profileJSON["cookies"]
|
||||
cookies, ok := s.profile["cookies"]
|
||||
if ok {
|
||||
// use cookies from profile
|
||||
cookies, ok := cookies.(map[string]interface{})
|
||||
@@ -224,7 +224,7 @@ func (s *tStep) makeRequestCookies(entry *Entry) error {
|
||||
|
||||
func (s *tStep) makeRequestHeaders(entry *Entry) error {
|
||||
s.Request.Headers = make(map[string]string)
|
||||
headers, ok := s.profileJSON["headers"]
|
||||
headers, ok := s.profile["headers"]
|
||||
if ok {
|
||||
// use headers from profile
|
||||
cookies, ok := headers.(map[string]interface{})
|
||||
|
||||
@@ -58,12 +58,12 @@ func TestLoadHARWithProfile(t *testing.T) {
|
||||
|
||||
if !assert.Equal(t,
|
||||
map[string]interface{}{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
har.profileJSON["headers"]) {
|
||||
har.profile["headers"]) {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
map[string]interface{}{"UserName": "debugtalk"},
|
||||
har.profileJSON["cookies"]) {
|
||||
har.profile["cookies"]) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@ from loguru import logger
|
||||
|
||||
from httprunner import __description__, __version__
|
||||
from httprunner.compat import ensure_cli_args
|
||||
from httprunner.ext.har2case import init_har2case_parser, main_har2case
|
||||
from httprunner.make import init_make_parser, main_make
|
||||
from httprunner.scaffold import init_parser_scaffold, main_scaffold
|
||||
from httprunner.utils import init_sentry_sdk, ga_client
|
||||
from httprunner.utils import ga_client, init_sentry_sdk
|
||||
|
||||
init_sentry_sdk()
|
||||
|
||||
@@ -67,7 +66,6 @@ def main():
|
||||
subparsers = parser.add_subparsers(help="sub-command help")
|
||||
sub_parser_run = init_parser_run(subparsers)
|
||||
sub_parser_scaffold = init_parser_scaffold(subparsers)
|
||||
sub_parser_har2case = init_har2case_parser(subparsers)
|
||||
sub_parser_make = init_make_parser(subparsers)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
@@ -85,9 +83,6 @@ def main():
|
||||
elif sys.argv[1] == "startproject":
|
||||
# httprunner startproject
|
||||
sub_parser_scaffold.print_help()
|
||||
elif sys.argv[1] == "har2case":
|
||||
# httprunner har2case
|
||||
sub_parser_har2case.print_help()
|
||||
elif sys.argv[1] == "run":
|
||||
# httprunner run
|
||||
pytest.main(["-h"])
|
||||
@@ -116,8 +111,6 @@ def main():
|
||||
sys.exit(main_run(extra_args))
|
||||
elif sys.argv[1] == "startproject":
|
||||
main_scaffold(args)
|
||||
elif sys.argv[1] == "har2case":
|
||||
main_har2case(args)
|
||||
elif sys.argv[1] == "make":
|
||||
main_make(args.testcase_path)
|
||||
|
||||
@@ -150,13 +143,5 @@ def main_make_alias():
|
||||
main()
|
||||
|
||||
|
||||
def main_har2case_alias():
|
||||
""" command alias
|
||||
har2case = httprunner har2case
|
||||
"""
|
||||
sys.argv.insert(1, "har2case")
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
""" Convert HAR (HTTP Archive) to YAML/JSON testcase for HttpRunner.
|
||||
|
||||
Usage:
|
||||
# convert to JSON format testcase
|
||||
$ hrun har2case demo.har
|
||||
|
||||
# convert to YAML format testcase
|
||||
$ hrun har2case demo.har -2y
|
||||
|
||||
"""
|
||||
|
||||
from httprunner.ext.har2case.core import HarParser
|
||||
from httprunner.utils import ga_client
|
||||
|
||||
|
||||
def init_har2case_parser(subparsers):
|
||||
""" HAR converter: parse command line options and run commands.
|
||||
"""
|
||||
parser = subparsers.add_parser(
|
||||
"har2case",
|
||||
help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.",
|
||||
)
|
||||
parser.add_argument("har_source_file", nargs="?", help="Specify HAR source file")
|
||||
parser.add_argument(
|
||||
"-2y",
|
||||
"--to-yml",
|
||||
"--to-yaml",
|
||||
dest="to_yaml",
|
||||
action="store_true",
|
||||
help="Convert to YAML format, if not specified, convert to pytest format by default.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-2j",
|
||||
"--to-json",
|
||||
dest="to_json",
|
||||
action="store_true",
|
||||
help="Convert to JSON format, if not specified, convert to pytest format by default.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filter",
|
||||
help="Specify filter keyword, only url include filter string will be converted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exclude",
|
||||
help="Specify exclude keyword, url that includes exclude string will be ignored, "
|
||||
"multiple keywords can be joined with '|'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--profile",
|
||||
dest="profile",
|
||||
help="Specify yaml file to overwrite headers and cookies in HAR.",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main_har2case(args):
|
||||
har_source_file = args.har_source_file
|
||||
|
||||
if args.to_yaml:
|
||||
output_file_type = "YAML"
|
||||
elif args.to_json:
|
||||
output_file_type = "JSON"
|
||||
else:
|
||||
output_file_type = "pytest"
|
||||
|
||||
ga_client.track_event("ConvertTests", f"har2case {output_file_type}")
|
||||
HarParser(har_source_file, args.filter, args.exclude, args.profile).gen_testcase(output_file_type)
|
||||
|
||||
return 0
|
||||
@@ -1,385 +0,0 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.parse as urlparse
|
||||
from typing import Text
|
||||
|
||||
from httprunner.compat import ensure_path_sep
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner.ext.har2case import utils
|
||||
from httprunner.make import make_testcase, format_pytest_with_black
|
||||
from httprunner.loader import load_test_file
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError:
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
|
||||
def ensure_file_path(path: Text) -> Text:
|
||||
|
||||
if not path or not path.endswith(".har"):
|
||||
logger.error("HAR file not specified.")
|
||||
sys.exit(1)
|
||||
|
||||
path = ensure_path_sep(path)
|
||||
if not os.path.isfile(path):
|
||||
logger.error(f"HAR file not exists: {path}")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(os.getcwd(), path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
class HarParser(object):
|
||||
def __init__(self, har_file_path, filter_str=None, exclude_str=None, profile=None):
|
||||
self.har_file_path = ensure_file_path(har_file_path)
|
||||
self.filter_str = filter_str
|
||||
self.exclude_str = exclude_str or ""
|
||||
self.profile = profile and load_test_file(profile)
|
||||
|
||||
def __make_request_url(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request url and queryString, and make teststep url and params
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"url": "https://httprunner.top/home?v=1&w=2",
|
||||
"queryString": [
|
||||
{"name": "v", "value": "1"},
|
||||
{"name": "w", "value": "2"}
|
||||
],
|
||||
},
|
||||
"response": {}
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"name: "/home",
|
||||
"request": {
|
||||
url: "https://httprunner.top/home",
|
||||
params: {"v": "1", "w": "2"}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
request_params = utils.convert_list_to_dict(
|
||||
entry_json["request"].get("queryString", [])
|
||||
)
|
||||
|
||||
url = entry_json["request"].get("url")
|
||||
if not url:
|
||||
logger.exception("url missed in request.")
|
||||
sys.exit(1)
|
||||
|
||||
parsed_object = urlparse.urlparse(url)
|
||||
if request_params:
|
||||
parsed_object = parsed_object._replace(query="")
|
||||
teststep_dict["request"]["url"] = parsed_object.geturl()
|
||||
teststep_dict["request"]["params"] = request_params
|
||||
else:
|
||||
teststep_dict["request"]["url"] = url
|
||||
|
||||
teststep_dict["name"] = parsed_object.path
|
||||
|
||||
def __make_request_method(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request method, and make teststep method.
|
||||
"""
|
||||
method = entry_json["request"].get("method")
|
||||
if not method:
|
||||
logger.exception("method missed in request.")
|
||||
sys.exit(1)
|
||||
|
||||
teststep_dict["request"]["method"] = method
|
||||
|
||||
def __make_request_cookies(self, teststep_dict, entry_json):
|
||||
if self.profile and self.profile.get("cookies"):
|
||||
teststep_dict["request"]["cookies"] = self.profile.get("cookies")
|
||||
else:
|
||||
cookies = {}
|
||||
for cookie in entry_json["request"].get("cookies", []):
|
||||
cookies[cookie["name"]] = cookie["value"]
|
||||
|
||||
if cookies:
|
||||
teststep_dict["request"]["cookies"] = cookies
|
||||
|
||||
def __make_request_headers(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request headers, and make teststep headers.
|
||||
header in IGNORE_REQUEST_HEADERS will be ignored.
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"headers": [
|
||||
{"name": "Host", "value": "httprunner.top"},
|
||||
{"name": "Content-Type", "value": "application/json"},
|
||||
{"name": "User-Agent", "value": "iOS/10.3"}
|
||||
],
|
||||
},
|
||||
"response": {}
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"request": {
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}
|
||||
|
||||
"""
|
||||
if self.profile and self.profile.get("headers"):
|
||||
teststep_dict["request"]["headers"] = self.profile.get("headers")
|
||||
else:
|
||||
teststep_headers = {}
|
||||
for header in entry_json["request"].get("headers", []):
|
||||
if header["name"] == "cookie" or header["name"].startswith(":"):
|
||||
continue
|
||||
|
||||
teststep_headers[header["name"]] = header["value"]
|
||||
|
||||
if teststep_headers:
|
||||
teststep_dict["request"]["headers"] = teststep_headers
|
||||
|
||||
def _make_request_data(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request data, and make teststep request data
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"params": [
|
||||
{"name": "a", "value": 1},
|
||||
{"name": "b", "value": "2"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"response": {...}
|
||||
}
|
||||
|
||||
|
||||
Returns:
|
||||
{
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"data": {"v": "1", "w": "2"}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
method = entry_json["request"].get("method")
|
||||
if method in ["POST", "PUT", "PATCH"]:
|
||||
postData = entry_json["request"].get("postData", {})
|
||||
mimeType = postData.get("mimeType")
|
||||
|
||||
# Note that text and params fields are mutually exclusive.
|
||||
if "text" in postData:
|
||||
post_data = postData.get("text")
|
||||
else:
|
||||
params = postData.get("params", [])
|
||||
post_data = utils.convert_list_to_dict(params)
|
||||
|
||||
request_data_key = "data"
|
||||
if not mimeType:
|
||||
pass
|
||||
elif mimeType.startswith("application/json"):
|
||||
try:
|
||||
post_data = json.loads(post_data)
|
||||
request_data_key = "json"
|
||||
except JSONDecodeError:
|
||||
pass
|
||||
elif mimeType.startswith("application/x-www-form-urlencoded"):
|
||||
post_data = utils.convert_x_www_form_urlencoded_to_dict(post_data)
|
||||
else:
|
||||
# TODO: make compatible with more mimeType
|
||||
pass
|
||||
|
||||
teststep_dict["request"][request_data_key] = post_data
|
||||
|
||||
def _make_validate(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry response and make teststep validate.
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
},
|
||||
],
|
||||
"content": {
|
||||
"size": 71,
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
"text": "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=",
|
||||
"encoding": "base64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"validate": [
|
||||
{"eq": ["status_code", 200]}
|
||||
]
|
||||
}
|
||||
|
||||
"""
|
||||
teststep_dict["validate"].append(
|
||||
{"eq": ["status_code", entry_json["response"].get("status")]}
|
||||
)
|
||||
|
||||
resp_content_dict = entry_json["response"].get("content")
|
||||
|
||||
headers_mapping = utils.convert_list_to_dict(
|
||||
entry_json["response"].get("headers", [])
|
||||
)
|
||||
if "Content-Type" in headers_mapping:
|
||||
teststep_dict["validate"].append(
|
||||
{"eq": ["headers.Content-Type", headers_mapping["Content-Type"]]}
|
||||
)
|
||||
|
||||
text = resp_content_dict.get("text")
|
||||
if not text:
|
||||
return
|
||||
|
||||
mime_type = resp_content_dict.get("mimeType")
|
||||
if mime_type and mime_type.startswith("application/json"):
|
||||
|
||||
encoding = resp_content_dict.get("encoding")
|
||||
if encoding and encoding == "base64":
|
||||
content = base64.b64decode(text)
|
||||
try:
|
||||
content = content.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
logger.warning(f"failed to decode base64 content with utf-8 !")
|
||||
return
|
||||
else:
|
||||
content = text
|
||||
|
||||
try:
|
||||
resp_content_json = json.loads(content)
|
||||
except JSONDecodeError:
|
||||
logger.warning(f"response content can not be loaded as json: {content}")
|
||||
return
|
||||
|
||||
if not isinstance(resp_content_json, dict):
|
||||
# e.g. ['a', 'b']
|
||||
return
|
||||
|
||||
for key, value in resp_content_json.items():
|
||||
if isinstance(value, (dict, list)):
|
||||
continue
|
||||
|
||||
teststep_dict["validate"].append({"eq": ["body.{}".format(key), value]})
|
||||
|
||||
def _prepare_teststep(self, entry_json):
|
||||
""" extract info from entry dict and make teststep
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://httprunner.top/api/v1/Account/Login",
|
||||
"headers": [],
|
||||
"queryString": [],
|
||||
"postData": {},
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": [],
|
||||
"content": {}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
teststep_dict = {"name": "", "request": {}, "validate": []}
|
||||
|
||||
self.__make_request_url(teststep_dict, entry_json)
|
||||
self.__make_request_method(teststep_dict, entry_json)
|
||||
self.__make_request_cookies(teststep_dict, entry_json)
|
||||
self.__make_request_headers(teststep_dict, entry_json)
|
||||
self._make_request_data(teststep_dict, entry_json)
|
||||
self._make_validate(teststep_dict, entry_json)
|
||||
|
||||
return teststep_dict
|
||||
|
||||
def _prepare_config(self):
|
||||
""" prepare config block.
|
||||
"""
|
||||
return {"name": "testcase description", "variables": {}, "verify": False}
|
||||
|
||||
def _prepare_teststeps(self):
|
||||
""" make teststep list.
|
||||
teststeps list are parsed from HAR log entries list.
|
||||
|
||||
"""
|
||||
|
||||
def is_exclude(url, exclude_str):
|
||||
exclude_str_list = exclude_str.split("|")
|
||||
for exclude_str in exclude_str_list:
|
||||
if exclude_str and exclude_str in url:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
teststeps = []
|
||||
log_entries = utils.load_har_log_entries(self.har_file_path)
|
||||
for entry_json in log_entries:
|
||||
url = entry_json["request"].get("url")
|
||||
if self.filter_str and self.filter_str not in url:
|
||||
continue
|
||||
|
||||
if is_exclude(url, self.exclude_str):
|
||||
continue
|
||||
|
||||
teststeps.append(self._prepare_teststep(entry_json))
|
||||
|
||||
return teststeps
|
||||
|
||||
def _make_testcase(self):
|
||||
""" Extract info from HAR file and prepare for testcase
|
||||
"""
|
||||
logger.info("Extract info from HAR file and prepare for testcase.")
|
||||
|
||||
config = self._prepare_config()
|
||||
teststeps = self._prepare_teststeps()
|
||||
|
||||
testcase = {"config": config, "teststeps": teststeps}
|
||||
return testcase
|
||||
|
||||
def gen_testcase(self, file_type="pytest"):
|
||||
logger.info(f"Start to generate testcase from {self.har_file_path}")
|
||||
harfile = os.path.splitext(self.har_file_path)[0]
|
||||
|
||||
try:
|
||||
testcase = self._make_testcase()
|
||||
except Exception as ex:
|
||||
capture_exception(ex)
|
||||
raise
|
||||
|
||||
if file_type == "JSON":
|
||||
output_testcase_file = f"{harfile}.json"
|
||||
utils.dump_json(testcase, output_testcase_file)
|
||||
elif file_type == "YAML":
|
||||
output_testcase_file = f"{harfile}.yml"
|
||||
utils.dump_yaml(testcase, output_testcase_file)
|
||||
else:
|
||||
# default to generate pytest file
|
||||
testcase["config"]["path"] = self.har_file_path
|
||||
output_testcase_file = make_testcase(testcase)
|
||||
format_pytest_with_black(output_testcase_file)
|
||||
|
||||
logger.info(f"generated testcase: {output_testcase_file}")
|
||||
@@ -1,180 +0,0 @@
|
||||
import os
|
||||
|
||||
from httprunner.ext.har2case.core import HarParser
|
||||
from httprunner.ext.har2case.utils import load_har_log_entries
|
||||
from httprunner.ext.har2case.utils_test import TestHar2CaseUtils
|
||||
|
||||
|
||||
class TestHar(TestHar2CaseUtils):
|
||||
def setUp(self):
|
||||
self.data_dir = os.path.join(os.getcwd(), "examples", "data", "har2case")
|
||||
self.har_path = os.path.join(self.data_dir, "demo.har")
|
||||
self.har_parser = HarParser(self.har_path)
|
||||
self.profile_path = os.path.join(self.data_dir, "profile.yml")
|
||||
|
||||
def test_prepare_teststep(self):
|
||||
log_entries = load_har_log_entries(self.har_path)
|
||||
teststep_dict = self.har_parser._prepare_teststep(log_entries[0])
|
||||
self.assertIn("name", teststep_dict)
|
||||
self.assertIn("request", teststep_dict)
|
||||
self.assertIn("validate", teststep_dict)
|
||||
|
||||
validators_mapping = {
|
||||
validator["eq"][0]: validator["eq"][1]
|
||||
for validator in teststep_dict["validate"]
|
||||
}
|
||||
self.assertEqual(validators_mapping["status_code"], 200)
|
||||
self.assertEqual(validators_mapping["body.IsSuccess"], True)
|
||||
self.assertEqual(validators_mapping["body.Code"], 200)
|
||||
self.assertEqual(validators_mapping["body.Message"], None)
|
||||
|
||||
def test_prepare_teststeps(self):
|
||||
teststeps = self.har_parser._prepare_teststeps()
|
||||
self.assertIsInstance(teststeps, list)
|
||||
self.assertIn("name", teststeps[0])
|
||||
self.assertIn("request", teststeps[0])
|
||||
self.assertIn("validate", teststeps[0])
|
||||
|
||||
def test_gen_testcase_yaml(self):
|
||||
yaml_file = os.path.join(self.data_dir, "demo.yml")
|
||||
|
||||
self.har_parser.gen_testcase(file_type="YAML")
|
||||
self.assertTrue(os.path.isfile(yaml_file))
|
||||
os.remove(yaml_file)
|
||||
|
||||
def test_gen_testcase_json(self):
|
||||
json_file = os.path.join(self.data_dir, "demo.json")
|
||||
|
||||
self.har_parser.gen_testcase(file_type="JSON")
|
||||
self.assertTrue(os.path.isfile(json_file))
|
||||
os.remove(json_file)
|
||||
|
||||
def test_profile(self):
|
||||
har_parser = HarParser(self.har_path, profile=self.profile_path)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["headers"],
|
||||
{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["cookies"],
|
||||
{"CASTGC": "TGT"},
|
||||
)
|
||||
|
||||
def test_filter(self):
|
||||
filter_str = "httprunner"
|
||||
har_parser = HarParser(self.har_path, filter_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["url"],
|
||||
"https://httprunner.top/api/v1/Account/Login",
|
||||
)
|
||||
|
||||
filter_str = "debugtalk"
|
||||
har_parser = HarParser(self.har_path, filter_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_exclude(self):
|
||||
exclude_str = "debugtalk"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["url"],
|
||||
"https://httprunner.top/api/v1/Account/Login",
|
||||
)
|
||||
|
||||
exclude_str = "httprunner"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_exclude_multiple(self):
|
||||
exclude_str = "httprunner|v2"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
exclude_str = "http2|v1"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_make_request_data_params(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"params": [{"name": "a", "value": 1}, {"name": "b", "value": "2"}],
|
||||
},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["request"]["data"]["a"], 1)
|
||||
self.assertEqual(testcase_dict["request"]["data"]["b"], "2")
|
||||
|
||||
def test_make_request_data_json(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
"text": '{"a":"1","b":"2"}',
|
||||
},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["request"]["json"], {"a": "1", "b": "2"})
|
||||
|
||||
def test_make_request_data_text_empty(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {"mimeType": "application/json; charset=utf-8", "text": ""},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["request"]["data"], "")
|
||||
|
||||
def test_make_validate(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8",
|
||||
},
|
||||
],
|
||||
"content": {
|
||||
"size": 71,
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
# raw response content text is application/jose type
|
||||
"text": "ZXlKaGJHY2lPaUpTVTBFeFh6VWlMQ0psYm1NaU9pSkJNVEk0UTBKRExV",
|
||||
"encoding": "base64",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.har_parser._make_validate(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["validate"][0], {"eq": ["status_code", 200]})
|
||||
self.assertEqual(
|
||||
testcase_dict["validate"][1],
|
||||
{"eq": ["headers.Content-Type", "application/json; charset=utf-8"]},
|
||||
)
|
||||
|
||||
def test_make_testcase(self):
|
||||
har_path = os.path.join(
|
||||
self.data_dir, "demo-quickstart.har"
|
||||
)
|
||||
har_parser = HarParser(har_path)
|
||||
testcase = har_parser._make_testcase()
|
||||
self.assertIsInstance(testcase, dict)
|
||||
self.assertIn("config", testcase)
|
||||
self.assertIn("teststeps", testcase)
|
||||
self.assertEqual(len(testcase["teststeps"]), 2)
|
||||
@@ -1,130 +0,0 @@
|
||||
import json
|
||||
import sys
|
||||
from json.decoder import JSONDecodeError
|
||||
from urllib.parse import unquote
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def load_har_log_entries(file_path):
|
||||
""" load HAR file and return log entries list
|
||||
|
||||
Args:
|
||||
file_path (str)
|
||||
|
||||
Returns:
|
||||
list: entries
|
||||
[
|
||||
{
|
||||
"request": {},
|
||||
"response": {}
|
||||
},
|
||||
{
|
||||
"request": {},
|
||||
"response": {}
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
with open(file_path, mode="rb") as f:
|
||||
try:
|
||||
content_json = json.load(f)
|
||||
return content_json["log"]["entries"]
|
||||
except (TypeError, JSONDecodeError) as ex:
|
||||
logger.error(f"failed to load HAR file {file_path}: {ex}")
|
||||
sys.exit(1)
|
||||
except KeyError:
|
||||
logger.error(f"log entries not found in HAR file: {content_json}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def x_www_form_urlencoded(post_data):
|
||||
""" convert origin dict to x-www-form-urlencoded
|
||||
|
||||
Args:
|
||||
post_data (dict):
|
||||
{"a": 1, "b":2}
|
||||
|
||||
Returns:
|
||||
str:
|
||||
a=1&b=2
|
||||
|
||||
"""
|
||||
if isinstance(post_data, dict):
|
||||
return "&".join(
|
||||
["{}={}".format(key, value) for key, value in post_data.items()]
|
||||
)
|
||||
else:
|
||||
return post_data
|
||||
|
||||
|
||||
def convert_x_www_form_urlencoded_to_dict(post_data):
|
||||
""" convert x_www_form_urlencoded data to dict
|
||||
|
||||
Args:
|
||||
post_data (str): a=1&b=2
|
||||
|
||||
Returns:
|
||||
dict: {"a":1, "b":2}
|
||||
|
||||
"""
|
||||
if isinstance(post_data, str):
|
||||
converted_dict = {}
|
||||
for k_v in post_data.split("&"):
|
||||
try:
|
||||
key, value = k_v.split("=")
|
||||
except ValueError:
|
||||
raise Exception(
|
||||
"Invalid x_www_form_urlencoded data format: {}".format(post_data)
|
||||
)
|
||||
converted_dict[key] = unquote(value)
|
||||
return converted_dict
|
||||
else:
|
||||
return post_data
|
||||
|
||||
|
||||
def convert_list_to_dict(origin_list):
|
||||
""" convert HAR data list to mapping
|
||||
|
||||
Args:
|
||||
origin_list (list)
|
||||
[
|
||||
{"name": "v", "value": "1"},
|
||||
{"name": "w", "value": "2"}
|
||||
]
|
||||
|
||||
Returns:
|
||||
dict:
|
||||
{"v": "1", "w": "2"}
|
||||
|
||||
"""
|
||||
return {item["name"]: item.get("value") for item in origin_list}
|
||||
|
||||
|
||||
def dump_yaml(testcase, yaml_file):
|
||||
""" dump HAR entries to yaml testcase
|
||||
"""
|
||||
logger.info("dump testcase to YAML format.")
|
||||
|
||||
with open(yaml_file, "w", encoding="utf-8") as outfile:
|
||||
yaml.dump(
|
||||
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
|
||||
)
|
||||
|
||||
logger.info("Generate YAML testcase successfully: {}".format(yaml_file))
|
||||
|
||||
|
||||
def dump_json(testcase, json_file):
|
||||
""" dump HAR entries to json testcase
|
||||
"""
|
||||
logger.info("dump testcase to JSON format.")
|
||||
|
||||
with open(json_file, "w", encoding="utf-8") as outfile:
|
||||
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
|
||||
if isinstance(my_json_str, bytes):
|
||||
my_json_str = my_json_str.decode("utf-8")
|
||||
|
||||
outfile.write(my_json_str)
|
||||
|
||||
logger.info("Generate JSON testcase successfully: {}".format(json_file))
|
||||
@@ -1,59 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from httprunner.ext.har2case import utils
|
||||
|
||||
|
||||
class TestHar2CaseUtils(unittest.TestCase):
|
||||
|
||||
data_dir = os.path.join(os.getcwd(), "examples", "data", "har2case")
|
||||
|
||||
@staticmethod
|
||||
def create_har_file(file_name, content):
|
||||
file_path = os.path.join(
|
||||
TestHar2CaseUtils.data_dir, "{}.har".format(file_name)
|
||||
)
|
||||
with open(file_path, "w") as f:
|
||||
f.write(json.dumps(content))
|
||||
|
||||
return file_path
|
||||
|
||||
def test_load_har_log_entries(self):
|
||||
har_path = os.path.join(TestHar2CaseUtils.data_dir, "demo.har")
|
||||
log_entries = utils.load_har_log_entries(har_path)
|
||||
self.assertIsInstance(log_entries, list)
|
||||
self.assertIn("request", log_entries[0])
|
||||
self.assertIn("response", log_entries[0])
|
||||
|
||||
def test_load_har_log_key_error(self):
|
||||
empty_json_file_path = TestHar2CaseUtils.create_har_file(
|
||||
file_name="empty_json", content={}
|
||||
)
|
||||
with self.assertRaises(SystemExit):
|
||||
utils.load_har_log_entries(empty_json_file_path)
|
||||
os.remove(empty_json_file_path)
|
||||
|
||||
def test_load_har_log_empty_error(self):
|
||||
empty_file_path = TestHar2CaseUtils.create_har_file(
|
||||
file_name="empty", content=""
|
||||
)
|
||||
with self.assertRaises(SystemExit):
|
||||
utils.load_har_log_entries(empty_file_path)
|
||||
os.remove(empty_file_path)
|
||||
|
||||
# def test_x_www_form_urlencoded(self):
|
||||
# origin_dict = {"a":1, "b": "2"}
|
||||
# self.assertIn("a=1", utils.x_www_form_urlencoded(origin_dict))
|
||||
# self.assertIn("b=2", utils.x_www_form_urlencoded(origin_dict))
|
||||
|
||||
def test_convert_list_to_dict(self):
|
||||
origin_list = [{"name": "v", "value": "1"}, {"name": "w", "value": "2"}]
|
||||
self.assertEqual(utils.convert_list_to_dict(origin_list), {"v": "1", "w": "2"})
|
||||
|
||||
def test_convert_x_www_form_urlencoded_to_dict(self):
|
||||
origin_str = "a=1&b=2"
|
||||
converted_dict = utils.convert_x_www_form_urlencoded_to_dict(origin_str)
|
||||
self.assertIsInstance(converted_dict, dict)
|
||||
self.assertEqual(converted_dict["a"], "1")
|
||||
self.assertEqual(converted_dict["b"], "2")
|
||||
@@ -138,7 +138,7 @@ class HttpRunner(object):
|
||||
|
||||
# parse
|
||||
functions = self.__project_meta.functions
|
||||
# prepare_upload_step(step, functions)
|
||||
prepare_upload_step(step, functions)
|
||||
request_dict = step.request.dict()
|
||||
request_dict.pop("upload", None)
|
||||
parsed_request_dict = parse_data(
|
||||
|
||||
6
poetry.lock
generated
6
poetry.lock
generated
@@ -252,7 +252,7 @@ reference = "tsinghua"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -835,8 +835,8 @@ iniconfig = [
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.1.0-py3-none-any.whl", hash = "sha256:da424924c069a4013730d8dd010cbecac7e7bb752be388db3741688bffb48dc6"},
|
||||
{file = "Jinja2-3.1.0.tar.gz", hash = "sha256:a2f09a92f358b96b5f6ca6ecb4502669c4acb55d8733bbb2b2c9c4af5564c605"},
|
||||
{file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"},
|
||||
{file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"},
|
||||
]
|
||||
jmespath = [
|
||||
{file = "jmespath-0.9.5-py2.py3-none-any.whl", hash = "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec"},
|
||||
|
||||
@@ -10,7 +10,7 @@ homepage = "https://github.com/httprunner/httprunner"
|
||||
repository = "https://github.com/httprunner/httprunner"
|
||||
documentation = "https://httprunner.com/docs"
|
||||
|
||||
keywords = ["HTTP", "apitest", "perftest", "DEM", "requests", "locustio"]
|
||||
keywords = ["HTTP", "apitest", "perftest", "requests"]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
@@ -56,7 +56,6 @@ coverage = "^4.5.4"
|
||||
httprunner = "httprunner.cli:main"
|
||||
hrun = "httprunner.cli:main_hrun_alias"
|
||||
hmake = "httprunner.cli:main_make_alias"
|
||||
har2case = "httprunner.cli:main_har2case_alias"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=1.0.0"]
|
||||
|
||||
Reference in New Issue
Block a user