mirror of
https://github.com/httprunner/httprunner.py.git
synced 2026-05-07 04:52:50 +08:00
249 lines
8.1 KiB
Python
249 lines
8.1 KiB
Python
import os
|
|
import time
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Dict, List, Text
|
|
|
|
try:
|
|
import allure
|
|
|
|
ALLURE = allure
|
|
except ModuleNotFoundError:
|
|
ALLURE = None
|
|
|
|
from loguru import logger
|
|
|
|
from httprunner.client import HttpSession
|
|
from httprunner.config import Config
|
|
from httprunner.exceptions import ParamsError, ValidationFailure
|
|
from httprunner.loader import load_project_meta
|
|
from httprunner.models import (
|
|
ProjectMeta,
|
|
StepResult,
|
|
TConfig,
|
|
TestCaseInOut,
|
|
TestCaseSummary,
|
|
TestCaseTime,
|
|
VariablesMapping,
|
|
)
|
|
from httprunner.parser import Parser
|
|
from httprunner.utils import LOGGER_FORMAT, merge_variables, ga4_client
|
|
|
|
|
|
class SessionRunner(object):
|
|
config: Config
|
|
teststeps: List[object] # list of Step
|
|
|
|
parser: Parser = None
|
|
session: HttpSession = None
|
|
case_id: Text = ""
|
|
root_dir: Text = ""
|
|
thrift_client = None
|
|
db_engine = None
|
|
|
|
__config: TConfig
|
|
__project_meta: ProjectMeta = None
|
|
__export: List[Text] = []
|
|
__step_results: List[StepResult] = []
|
|
__session_variables: VariablesMapping = {}
|
|
__is_referenced: bool = False
|
|
# time
|
|
__start_at: float = 0
|
|
__duration: float = 0
|
|
# log
|
|
__log_path: Text = ""
|
|
|
|
def __init(self):
|
|
self.__config = self.config.struct()
|
|
self.__session_variables = self.__session_variables or {}
|
|
self.__start_at = 0
|
|
self.__duration = 0
|
|
self.__is_referenced = self.__is_referenced or False
|
|
|
|
self.__project_meta = self.__project_meta or load_project_meta(
|
|
self.__config.path
|
|
)
|
|
self.case_id = self.case_id or str(uuid.uuid4())
|
|
self.root_dir = self.root_dir or self.__project_meta.RootDir
|
|
self.__log_path = os.path.join(self.root_dir, "logs", f"{self.case_id}.run.log")
|
|
|
|
self.__step_results = self.__step_results or []
|
|
self.session = self.session or HttpSession()
|
|
self.parser = self.parser or Parser(self.__project_meta.functions)
|
|
|
|
def with_session(self, session: HttpSession) -> "SessionRunner":
|
|
self.session = session
|
|
return self
|
|
|
|
def get_config(self) -> TConfig:
|
|
return self.__config
|
|
|
|
def set_referenced(self) -> "SessionRunner":
|
|
self.__is_referenced = True
|
|
return self
|
|
|
|
def with_case_id(self, case_id: Text) -> "SessionRunner":
|
|
self.case_id = case_id
|
|
return self
|
|
|
|
def with_variables(self, variables: VariablesMapping) -> "SessionRunner":
|
|
self.__session_variables = variables
|
|
return self
|
|
|
|
def with_export(self, export: List[Text]) -> "SessionRunner":
|
|
self.__export = export
|
|
return self
|
|
|
|
def with_thrift_client(self, thrift_client) -> "SessionRunner":
|
|
self.thrift_client = thrift_client
|
|
return self
|
|
|
|
def with_db_engine(self, db_engine) -> "SessionRunner":
|
|
self.db_engine = db_engine
|
|
return self
|
|
|
|
def __parse_config(self, param: Dict = None) -> None:
|
|
# parse config variables
|
|
self.__config.variables.update(self.__session_variables)
|
|
if param:
|
|
self.__config.variables.update(param)
|
|
self.__config.variables = self.parser.parse_variables(self.__config.variables)
|
|
|
|
# parse config name
|
|
self.__config.name = self.parser.parse_data(
|
|
self.__config.name, self.__config.variables
|
|
)
|
|
|
|
# parse config base url
|
|
self.__config.base_url = self.parser.parse_data(
|
|
self.__config.base_url, self.__config.variables
|
|
)
|
|
|
|
def get_export_variables(self) -> Dict:
|
|
# override testcase export vars with step export
|
|
export_var_names = self.__export or self.__config.export
|
|
export_vars_mapping = {}
|
|
for var_name in export_var_names:
|
|
if var_name not in self.__session_variables:
|
|
raise ParamsError(
|
|
f"failed to export variable {var_name} from session variables {self.__session_variables}"
|
|
)
|
|
|
|
export_vars_mapping[var_name] = self.__session_variables[var_name]
|
|
|
|
return export_vars_mapping
|
|
|
|
def get_summary(self) -> TestCaseSummary:
|
|
"""get testcase result summary"""
|
|
start_at_timestamp = self.__start_at
|
|
start_at_iso_format = datetime.utcfromtimestamp(start_at_timestamp).isoformat()
|
|
|
|
summary_success = True
|
|
for step_result in self.__step_results:
|
|
if not step_result.success:
|
|
summary_success = False
|
|
break
|
|
|
|
return TestCaseSummary(
|
|
name=self.__config.name,
|
|
success=summary_success,
|
|
case_id=self.case_id,
|
|
time=TestCaseTime(
|
|
start_at=self.__start_at,
|
|
start_at_iso_format=start_at_iso_format,
|
|
duration=self.__duration,
|
|
),
|
|
in_out=TestCaseInOut(
|
|
config_vars=self.__config.variables,
|
|
export_vars=self.get_export_variables(),
|
|
),
|
|
log=self.__log_path,
|
|
step_results=self.__step_results,
|
|
)
|
|
|
|
def merge_step_variables(self, variables: VariablesMapping) -> VariablesMapping:
|
|
# override variables
|
|
# step variables > extracted variables from previous steps
|
|
variables = merge_variables(variables, self.__session_variables)
|
|
# step variables > testcase config variables
|
|
variables = merge_variables(variables, self.__config.variables)
|
|
|
|
# parse variables
|
|
return self.parser.parse_variables(variables)
|
|
|
|
def __run_step(self, step):
|
|
"""run teststep, step maybe any kind that implements IStep interface
|
|
|
|
Args:
|
|
step (Step): teststep
|
|
|
|
"""
|
|
logger.info(f"run step begin: {step.name()} >>>>>>")
|
|
|
|
# run step
|
|
for i in range(step.retry_times + 1):
|
|
try:
|
|
if ALLURE is not None:
|
|
with ALLURE.step(f"step: {step.name()}"):
|
|
step_result: StepResult = step.run(self)
|
|
else:
|
|
step_result: StepResult = step.run(self)
|
|
break
|
|
except ValidationFailure:
|
|
if i == step.retry_times:
|
|
raise
|
|
else:
|
|
logger.warning(
|
|
f"run step {step.name()} validation failed,wait {step.retry_interval} sec and try again"
|
|
)
|
|
time.sleep(step.retry_interval)
|
|
logger.info(
|
|
f"run step retry ({i + 1}/{step.retry_times} time): {step.name()} >>>>>>"
|
|
)
|
|
|
|
# save extracted variables to session variables
|
|
self.__session_variables.update(step_result.export_vars)
|
|
# update testcase summary
|
|
self.__step_results.append(step_result)
|
|
|
|
logger.info(f"run step end: {step.name()} <<<<<<\n")
|
|
|
|
def test_start(self, param: Dict = None) -> "SessionRunner":
|
|
"""main entrance, discovered by pytest"""
|
|
ga4_client.send_event("test_start")
|
|
print("\n")
|
|
self.__init()
|
|
self.__parse_config(param)
|
|
|
|
if ALLURE is not None and not self.__is_referenced:
|
|
# update allure report meta
|
|
ALLURE.dynamic.title(self.__config.name)
|
|
ALLURE.dynamic.description(f"TestCase ID: {self.case_id}")
|
|
|
|
logger.info(
|
|
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.case_id}"
|
|
)
|
|
|
|
logger.add(self.__log_path, format=LOGGER_FORMAT, level="DEBUG")
|
|
self.__start_at = time.time()
|
|
try:
|
|
# run step in sequential order
|
|
for step in self.teststeps:
|
|
self.__run_step(step)
|
|
finally:
|
|
logger.info(f"generate testcase log: {self.__log_path}")
|
|
if ALLURE is not None:
|
|
ALLURE.attach.file(
|
|
self.__log_path,
|
|
name="all log",
|
|
attachment_type=ALLURE.attachment_type.TEXT,
|
|
)
|
|
|
|
self.__duration = time.time() - self.__start_at
|
|
return self
|
|
|
|
|
|
class HttpRunner(SessionRunner):
|
|
# split SessionRunner to keep consistent with golang version
|
|
pass
|