diff --git a/README.md b/README.md index 36ce82c..5085bd0 100644 --- a/README.md +++ b/README.md @@ -66,13 +66,13 @@ * **log:** shows test execution on .log file, with the same name as the project_name, errors are shown in stderr * **portalio:** ReportPortalIO Web REST DB and reporter: * **slack:** tests results are sent to Slack channel - * **web:** tests results can be seen in realtime on a browser + * **web:** tests results can be seen in realtime on a browser. use **-r-web-port PORT** to specify other than 9204 * **xml:** test results will be saved on report.xml file # 2.3. Run: - ### options: - * **-rid** RunID (ex: -rid 17, if not passed than current hour and minute will be used ex: 2359) + * **-rid** RunID (ex: -rid 17, if not passed then current hour and minute will be used ex: 2359) * **-pn** ProjectName (ex: -pn jules) * **-env** EnvironmentName to test (ex: -env dev) * **-rf** ResultsFolder (ex: -rf "/qa/test_results/"), where the tests results will be stored diff --git a/testipy/__init__.py b/testipy/__init__.py index 10cf222..54084a3 100644 --- a/testipy/__init__.py +++ b/testipy/__init__.py @@ -1,9 +1,7 @@ -#!/usr/bin/env python3 - import logging -__version__ = "0.10.1" +__version__ = "0.10.2" __app__ = "TestiPy" __app_full__ = "Python Test Tool" __author__ = "Pedro Nunes" diff --git a/testipy/configs/default_config.py b/testipy/configs/default_config.py index a585650..95ba854 100644 --- a/testipy/configs/default_config.py +++ b/testipy/configs/default_config.py @@ -1,6 +1,6 @@ import os -from testipy.configs import enums_data +from testipy.configs.enums_data import STATE_PASSED, STATE_FAILED # run.py default_project_name = "local" @@ -24,8 +24,8 @@ separator_and_join_tags = "&" # execute_tests.py -if_no_test_started_mark_as = enums_data.STATE_PASSED -count_as_failed_states = [enums_data.STATE_FAILED] +if_no_test_started_mark_as = STATE_PASSED +count_as_failed_states = [STATE_FAILED] suite_threads = 1 # report_base.py diff --git a/testipy/engine/execute_tests.py b/testipy/engine/execute_tests.py index f42d281..60f49a1 100644 --- a/testipy/engine/execute_tests.py +++ b/testipy/engine/execute_tests.py @@ -1,12 +1,12 @@ +from __future__ import annotations import os import traceback import concurrent.futures -from typing import List +from typing import List, TYPE_CHECKING from testipy import get_exec_logger from testipy.configs import enums_data, default_config -from testipy.engine.models import PackageAttr, SuiteAttr, TestMethodAttr from testipy.lib_modules import common_methods as cm from testipy.lib_modules.common_methods import synchronized from testipy.lib_modules.state_counter import StateCounter @@ -14,8 +14,11 @@ from testipy.lib_modules.start_arguments import StartArguments from testipy.helpers.prettify import format_duration from testipy.helpers.handle_assertions import ExpectedError -from testipy.reporter.report_manager import ReportManager -from testipy.reporter import SuiteDetails, TestDetails + +if TYPE_CHECKING: + from testipy.models import PackageAttr, SuiteAttr, TestMethodAttr, SuiteDetails, TestDetails + from testipy.reporter.report_manager import ReportManager + _exec_logger = get_exec_logger() diff --git a/testipy/engine/read_tests.py b/testipy/engine/read_tests.py index a7d1551..66ba726 100644 --- a/testipy/engine/read_tests.py +++ b/testipy/engine/read_tests.py @@ -9,8 +9,10 @@ from testipy.lib_modules import py_inspector, common_methods as cm from testipy.lib_modules.args_parser import ArgsParser from testipy.helpers import load_config -from testipy.engine.models import ( - PackageAttr, SuiteAttr, TestMethodAttr, +from testipy.models.attr import ( + PackageAttr, + SuiteAttr, + TestMethodAttr, get_package_by_name, mark_packages_suites_methods_ids, show_test_structure, diff --git a/testipy/helpers/data_driven_testing.py b/testipy/helpers/data_driven_testing.py index c385381..cbbfe98 100644 --- a/testipy/helpers/data_driven_testing.py +++ b/testipy/helpers/data_driven_testing.py @@ -1,16 +1,17 @@ from __future__ import annotations - from abc import abstractmethod, ABC from collections import OrderedDict from enum import Enum -from typing import Union, Dict, List, Tuple +from typing import Union, Dict, List, Tuple, TYPE_CHECKING from testipy.configs import enums_data -from testipy.reporter.package_manager import SuiteDetails, TestDetails -from testipy.reporter.report_manager import ReportManager from testipy.helpers import get_traceback_tabulate, load_config, left_update_dict, prettify from testipy.helpers.handle_assertions import ExpectedError, SkipTestError +if TYPE_CHECKING: + from testipy.models import SuiteDetails, TestDetails + from testipy.reporter import ReportManager + class RunMode(Enum): SCENARIOS_AS_TESTS__USECASES_AS_TESTSTEPS = 1 diff --git a/testipy/helpers/errors.py b/testipy/helpers/errors.py index defd655..511ff41 100644 --- a/testipy/helpers/errors.py +++ b/testipy/helpers/errors.py @@ -42,12 +42,15 @@ def get_traceback_list(exc_value: BaseException) -> List[Dict]: tbe = traceback.TracebackException.from_exception(exc_value) # get stacktrace (cascade methods calls) - error_lines = [{ + error_lines = [ + { "filename": frame_summary.filename, "method" : frame_summary.name, "lineno" : frame_summary.lineno, "code" : frame_summary.line - } for frame_summary in tbe.stack] + } + for frame_summary in tbe.stack + ] # append error, by order of execution result.append({ diff --git a/testipy/helpers/handle_assertions.py b/testipy/helpers/handle_assertions.py index d9cea29..937e4cf 100644 --- a/testipy/helpers/handle_assertions.py +++ b/testipy/helpers/handle_assertions.py @@ -87,8 +87,8 @@ def assert_equal_complex_object( def assert_equal_dicts( - expected: Any, - received: Any, + expected: Dict, + received: Dict, expected_name: str = "expected", received_name: str = "received", ) -> None: @@ -103,8 +103,8 @@ def assert_equal_dicts( def assert_equal_lists( - expected: Any, - received: Any, + expected: List, + received: List, expected_name: str = "expected", received_name: str = "received", same_len: bool = True, diff --git a/testipy/lib_modules/args_parser.py b/testipy/lib_modules/args_parser.py index 3358b1f..4127719 100644 --- a/testipy/lib_modules/args_parser.py +++ b/testipy/lib_modules/args_parser.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import sys diff --git a/testipy/lib_modules/browser_manager/playwright.py b/testipy/lib_modules/browser_manager/playwright.py index c8dd75b..c55fb2d 100644 --- a/testipy/lib_modules/browser_manager/playwright.py +++ b/testipy/lib_modules/browser_manager/playwright.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 import base64 from time import sleep diff --git a/testipy/lib_modules/common_methods.py b/testipy/lib_modules/common_methods.py index b01aefc..c10c1cc 100644 --- a/testipy/lib_modules/common_methods.py +++ b/testipy/lib_modules/common_methods.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 from __future__ import annotations import os diff --git a/testipy/lib_modules/py_inspector.py b/testipy/lib_modules/py_inspector.py index 8f3c659..244233d 100644 --- a/testipy/lib_modules/py_inspector.py +++ b/testipy/lib_modules/py_inspector.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import os import importlib.util import inspect diff --git a/testipy/lib_modules/webhook_http_listener.py b/testipy/lib_modules/webhook_http_listener.py index d6e3808..5ce4a19 100644 --- a/testipy/lib_modules/webhook_http_listener.py +++ b/testipy/lib_modules/webhook_http_listener.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import sys import threading import json diff --git a/testipy/models/__init__.py b/testipy/models/__init__.py new file mode 100644 index 0000000..4290945 --- /dev/null +++ b/testipy/models/__init__.py @@ -0,0 +1,2 @@ +from testipy.models.attr import PackageAttr, SuiteAttr, TestMethodAttr +from testipy.models.package_manager import PackageManager, PackageDetails, SuiteDetails, TestDetails diff --git a/testipy/engine/models.py b/testipy/models/attr.py similarity index 100% rename from testipy/engine/models.py rename to testipy/models/attr.py diff --git a/testipy/reporter/package_manager.py b/testipy/models/package_manager.py similarity index 92% rename from testipy/reporter/package_manager.py rename to testipy/models/package_manager.py index 37d3d66..c441c7c 100644 --- a/testipy/reporter/package_manager.py +++ b/testipy/models/package_manager.py @@ -1,13 +1,15 @@ from __future__ import annotations - -from typing import Dict, List, Set, NamedTuple, Any, Union +from typing import Dict, List, Set, NamedTuple, Any, Union, TYPE_CHECKING from tabulate import tabulate from testipy.configs import enums_data, default_config -from testipy.engine.models import PackageAttr, SuiteAttr, TestMethodAttr from testipy.lib_modules.common_methods import get_datetime_now from testipy.lib_modules.state_counter import StateCounter +if TYPE_CHECKING: + from testipy.models import PackageAttr, SuiteAttr, TestMethodAttr + from testipy.reporter.report_manager import ReportManager + class TestInfo(NamedTuple): timestamp: int @@ -50,7 +52,7 @@ class PackageDetails(CommonDetails): def __init__(self, parent: PackageManager, package_attr: PackageAttr, package_name: str = ""): super(PackageDetails, self).__init__(package_name or package_attr.package_name) - self.parent = parent + self.parent: PackageManager = parent self.package_attr: PackageAttr = package_attr self.suite_manager: SuiteManager = SuiteManager(self) @@ -69,12 +71,12 @@ class SuiteDetails(CommonDetails): def __init__(self, parent: PackageDetails, suite_attr: SuiteAttr, suite_name: str = ""): super(SuiteDetails, self).__init__(suite_name or suite_attr.name) - self.package = parent + self.package: PackageDetails = parent self.suite_attr: SuiteAttr = suite_attr self.current_test_method_attr: TestMethodAttr = None self.test_state_by_prio: Dict[int, Set] = dict() # {2: {"PASS", "SKIP"}, 10: ... self.rb_test_result_rows: List = [] - self.test_manager = TestManager(self) + self.test_manager: TestManager = TestManager(self) def set_current_test_method_attr(self, test_method_attr: Union[TestMethodAttr, None]) -> SuiteDetails: self.current_test_method_attr = test_method_attr @@ -119,19 +121,27 @@ class TestDetails(CommonDetails): def __init__(self, parent: SuiteDetails, test_method_attr: TestMethodAttr, test_name: str = ""): super(TestDetails, self).__init__(test_name or test_method_attr.name) - self.suite = parent + self.suite: SuiteDetails = parent self.test_method_attr: TestMethodAttr = test_method_attr + self.rm: ReportManager = None self.test_id: int = 0 self.test_usecase: str = "" self.test_comment: str = test_method_attr.comment + self.test_state: str = "" + self.test_reason_of_state: str = "" - self._info = list() - self._test_step = StateCounter() + self._info: List[TestInfo] = list() + self._test_step: StateCounter = StateCounter() + + def get_rm(self) -> ReportManager: + return self.rm def endTest(self, state: str, reason_of_state: str, exc_value: BaseException = None): self.end_time = get_datetime_now() self.state_counter.inc_state(state, reason_of_state=reason_of_state, description="end test", exc_value=exc_value) + self.test_state = state + self.test_reason_of_state = reason_of_state self.suite.update_package_suite_counters(self.get_prio(), state, reason_of_state) self.suite.test_manager.remove_test_running(self) @@ -216,7 +226,7 @@ def set_next_step_starting_from_now(self): return self def get_state(self): - return self.state_counter.get_last_state() or self._test_step.get_last_state() + return self.test_state or self._test_step.get_last_state() def is_passed(self): return self.get_state() == enums_data.STATE_PASSED @@ -227,6 +237,9 @@ def is_failed(self): def is_skipped(self): return self.get_state() == enums_data.STATE_SKIPPED + def get_reason_of_state(self) -> str: + return self.test_reason_of_state or self._test_step.get_last_reason_of_state() + def __str__(self): res = f"meid={self.get_method_id()} | teid={self.get_test_id()} | prio={self.get_prio()} | {self.get_name()}" if usecase := self.get_usecase(): diff --git a/testipy/reporter/__init__.py b/testipy/reporter/__init__.py index b424f57..182c76d 100644 --- a/testipy/reporter/__init__.py +++ b/testipy/reporter/__init__.py @@ -1,3 +1,2 @@ from testipy.reporter.report_interfaces import ReportInterface from testipy.reporter.report_manager import ReportManager -from testipy.reporter.package_manager import PackageManager, PackageDetails, SuiteDetails, TestDetails \ No newline at end of file diff --git a/testipy/reporter/report_base.py b/testipy/reporter/report_base.py index eb47386..1270f67 100644 --- a/testipy/reporter/report_base.py +++ b/testipy/reporter/report_base.py @@ -6,10 +6,9 @@ from mimetypes import guess_type from testipy.configs import enums_data, default_config -from testipy.engine.models import PackageAttr, SuiteAttr, TestMethodAttr +from testipy.models import PackageAttr, SuiteAttr, TestMethodAttr, PackageManager, PackageDetails, SuiteDetails, TestDetails from testipy.reporter.report_interfaces import ReportInterface from testipy.lib_modules.common_methods import get_current_date_time_ns, get_timestamp -from testipy.reporter.package_manager import PackageManager, PackageDetails, SuiteDetails, TestDetails class ReportBase(ReportInterface): @@ -95,11 +94,18 @@ def end_suite(self, sd: SuiteDetails): # update DataFrame with all ended tests for this suite df_size = self._df.shape[0] - new_rows = pd.DataFrame(sd.rb_test_result_rows, - columns=self._df_columns, - index=range(df_size, df_size + len(sd.rb_test_result_rows))) - self._df = new_rows if df_size == 0 else pd.concat( - [self._df, new_rows], axis=0, join="outer", ignore_index=True, verify_integrity=False, copy=False) + new_rows = pd.DataFrame( + sd.rb_test_result_rows, + columns=self._df_columns, + index=range(df_size, df_size + len(sd.rb_test_result_rows)) + ) + + if df_size == 0: + self._df = new_rows + else: + df1 = self._df.dropna(axis=1, how="all") + df2 = new_rows.dropna(axis=1, how="all") + self._df = pd.concat([df1, df2], axis=0, join="outer", ignore_index=True, copy=False) # clear list since they were added to DataFrame sd.rb_test_result_rows.clear() diff --git a/testipy/reporter/report_interfaces.py b/testipy/reporter/report_interfaces.py index d61fd3d..c904a49 100644 --- a/testipy/reporter/report_interfaces.py +++ b/testipy/reporter/report_interfaces.py @@ -1,17 +1,19 @@ from __future__ import annotations +from typing import Union, List, Dict, TYPE_CHECKING import os -from typing import Union, List, Dict from abc import abstractmethod, ABC from testipy import get_exec_logger from testipy.configs import default_config -from testipy.engine.models import PackageAttr from testipy.lib_modules.args_parser import ArgsParser from testipy.lib_modules.start_arguments import StartArguments from testipy.lib_modules.browser_manager import BrowserManager from testipy.lib_modules import webhook_http_listener as HL -from testipy.reporter.package_manager import PackageDetails, SuiteDetails, TestDetails +from testipy.models import PackageDetails, SuiteDetails, TestDetails + +if TYPE_CHECKING: + from testipy.models import PackageAttr _exec_logger = get_exec_logger() diff --git a/testipy/reporter/report_manager.py b/testipy/reporter/report_manager.py index 5670507..afba545 100644 --- a/testipy/reporter/report_manager.py +++ b/testipy/reporter/report_manager.py @@ -1,20 +1,21 @@ from __future__ import annotations import os -from typing import Dict, List, Tuple, Any +from typing import Dict, List, Tuple, Any, TYPE_CHECKING from testipy import get_exec_logger from testipy.configs import enums_data -from testipy.engine.models import PackageAttr, SuiteAttr, TestMethodAttr -from testipy.helpers import format_duration +from testipy.helpers import format_duration, get_traceback_tabulate from testipy.lib_modules.common_methods import synchronized from testipy.lib_modules.textdecor import color_state from testipy.lib_modules.args_parser import ArgsParser from testipy.lib_modules.start_arguments import StartArguments from testipy.lib_modules.py_inspector import get_class_from_file_with_prefix -from testipy.reporter.report_base import ReportBase, PackageDetails, SuiteDetails, TestDetails +from testipy.reporter.report_base import ReportBase from testipy.reporter.report_interfaces import ReportManagerAddons +if TYPE_CHECKING: + from testipy.models import PackageAttr, SuiteAttr, TestMethodAttr, PackageDetails, SuiteDetails, TestDetails _exec_logger = get_exec_logger() @@ -135,7 +136,8 @@ def start_suite(self, sd: SuiteDetails): @synchronized def startTest(self, sd: SuiteDetails, test_method_attr: TestMethodAttr = None, test_name: str = "", usecase: str = "", description: str = "") -> TestDetails: - td = super().startTest(sd, test_method_attr, test_name, usecase, description) + td: TestDetails = super().startTest(sd, test_method_attr, test_name, usecase, description) + td.rm = self self.start_test(td) return td @@ -266,6 +268,34 @@ def input_prompt_message(self, message: str, default_value: str = "") -> str: # +class TestStep: + def __init__(self, td: TestDetails, description: str, reason_of_state: str = "ok", take_screenshot: bool = False): + self.td: TestDetails = td + self.description: str = description + self.reason_of_state: str = reason_of_state + self.take_screenshot = take_screenshot + + def __enter__(self): + self.td.get_rm().show_status(f"Executing step {self.description}") + + def __exit__(self, exc_type, exc_val, exc_tb): + self.td.get_rm().test_step( + current_test=self.td, + state=enums_data.STATE_FAILED if exc_val else enums_data.STATE_PASSED, + reason_of_state=str(exc_val) if exc_val else self.reason_of_state, + description=self.description, + take_screenshot=self.take_screenshot, + exc_value=exc_val + ) + if exc_val: + self.td.get_rm().test_info( + current_test=self.td, + info=get_traceback_tabulate(exc_val), + level="ERROR", + ) + raise exc_val + + def build_report_manager_with_reporters(ap: ArgsParser, sa: StartArguments): # create report manager rm = ReportManager(ap, sa) diff --git a/testipy/reporter/reporters/reporter_echo.py b/testipy/reporter/reporters/reporter_echo.py index 8632813..3a382e1 100644 --- a/testipy/reporter/reporters/reporter_echo.py +++ b/testipy/reporter/reporters/reporter_echo.py @@ -1,13 +1,18 @@ -from typing import Dict, List +from __future__ import annotations +from typing import Dict, List, TYPE_CHECKING from tabulate import tabulate from testipy.configs import enums_data -from testipy.engine.models import PackageAttr from testipy.helpers import format_duration, prettify from testipy.lib_modules import textdecor from testipy.lib_modules.start_arguments import StartArguments +from testipy.reporter import ReportInterface from testipy.reporter.reporters import df_manager as dfm -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager + _line_size = 160 diff --git a/testipy/reporter/reporters/reporter_excel.py b/testipy/reporter/reporters/reporter_excel.py index 9c3521f..3c5e8a9 100644 --- a/testipy/reporter/reporters/reporter_excel.py +++ b/testipy/reporter/reporters/reporter_excel.py @@ -1,13 +1,16 @@ +from __future__ import annotations +from typing import List, TYPE_CHECKING import os import pandas as pd -from typing import List - from testipy import get_exec_logger -from testipy.engine.models import PackageAttr from testipy.lib_modules.start_arguments import StartArguments +from testipy.reporter import ReportInterface from testipy.reporter.reporters import df_manager as dfm -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager _exec_logger = get_exec_logger() diff --git a/testipy/reporter/reporters/reporter_junit.py b/testipy/reporter/reporters/reporter_junit.py index 758ee3b..464df4d 100644 --- a/testipy/reporter/reporters/reporter_junit.py +++ b/testipy/reporter/reporters/reporter_junit.py @@ -1,14 +1,17 @@ +from __future__ import annotations +from typing import List, TYPE_CHECKING import os -from typing import List - from testipy import get_exec_logger from testipy.configs import enums_data -from testipy.engine.models import PackageAttr from testipy.helpers import get_traceback_list from testipy.lib_modules.state_counter import StateCounter from testipy.lib_modules.start_arguments import StartArguments -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails +from testipy.reporter import ReportInterface + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager HEADER = '\n' diff --git a/testipy/reporter/reporters/reporter_log.py b/testipy/reporter/reporters/reporter_log.py index cf749c7..3e68f9d 100644 --- a/testipy/reporter/reporters/reporter_log.py +++ b/testipy/reporter/reporters/reporter_log.py @@ -1,17 +1,21 @@ +from __future__ import annotations +from typing import List, TYPE_CHECKING import os import logging -from typing import List from tabulate import tabulate from mss import mss from testipy import get_exec_logger from testipy.configs import enums_data -from testipy.engine.models import PackageAttr from testipy.helpers import get_traceback_list, prettify, format_duration from testipy.lib_modules.common_methods import get_app_version from testipy.lib_modules.start_arguments import StartArguments -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails +from testipy.reporter import ReportInterface + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager log_format = "%(asctime)s %(levelname)s - %(message)s" diff --git a/testipy/reporter/reporters/reporter_portalio.py b/testipy/reporter/reporters/reporter_portalio.py index db68f41..3597589 100644 --- a/testipy/reporter/reporters/reporter_portalio.py +++ b/testipy/reporter/reporters/reporter_portalio.py @@ -1,17 +1,21 @@ +from __future__ import annotations +from typing import List, TYPE_CHECKING import os import json from time import time -from typing import List from reportportal_client import create_client, ClientType from testipy import get_exec_logger from testipy.configs import enums_data -from testipy.engine.models import PackageAttr from testipy.lib_modules.common_methods import dict_without_keys from testipy.lib_modules.start_arguments import StartArguments from testipy.helpers import load_config -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails +from testipy.reporter import ReportInterface + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager # to show or not stacktrace diff --git a/testipy/reporter/reporters/reporter_slack.py b/testipy/reporter/reporters/reporter_slack.py index 0eae36a..9d77f83 100644 --- a/testipy/reporter/reporters/reporter_slack.py +++ b/testipy/reporter/reporters/reporter_slack.py @@ -1,16 +1,22 @@ +from __future__ import annotations +from typing import List, TYPE_CHECKING import os -from typing import List from slack_sdk import WebClient from slack_sdk.errors import SlackApiError from testipy import get_exec_logger from testipy.configs import enums_data -from testipy.engine.models import PackageAttr +from testipy.models import PackageAttr from testipy.helpers import Timer from testipy.lib_modules.common_methods import get_app_version from testipy.lib_modules.start_arguments import StartArguments -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails +from testipy.reporter import ReportInterface + +if TYPE_CHECKING: + from testipy.models import PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager + SLACK_API_TOKEN = os.getenv("TESTIPY_SLACK_API_TOKEN") DEFAULT_CHANNEL = "C06BQ1UP6KG" diff --git a/testipy/reporter/reporters/reporter_template.py b/testipy/reporter/reporters/reporter_template.py index 06456da..b3b1729 100644 --- a/testipy/reporter/reporters/reporter_template.py +++ b/testipy/reporter/reporters/reporter_template.py @@ -1,8 +1,12 @@ -from typing import List +from __future__ import annotations +from typing import List, TYPE_CHECKING -from testipy.engine.models import PackageAttr from testipy.lib_modules.start_arguments import StartArguments -from testipy.reporter import ReportManager, ReportInterface, PackageDetails, SuiteDetails, TestDetails +from testipy.reporter import ReportInterface + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager class ReporterTemplate(ReportInterface): diff --git a/testipy/reporter/reporters/reporter_web.py b/testipy/reporter/reporters/reporter_web.py index b6b8ca6..d652131 100644 --- a/testipy/reporter/reporters/reporter_web.py +++ b/testipy/reporter/reporters/reporter_web.py @@ -1,9 +1,11 @@ +from __future__ import annotations +from typing import Dict, List, TYPE_CHECKING + import webbrowser import html import base64 import os -from typing import List, Dict from threading import Thread from flask import Flask, render_template, copy_current_request_context, request from flask_socketio import SocketIO, emit, disconnect @@ -11,11 +13,13 @@ from testipy import get_exec_logger from testipy.configs import enums_data -from testipy.engine.models import PackageAttr from testipy.helpers import Timer, prettify, format_duration from testipy.lib_modules.start_arguments import StartArguments -from testipy.reporter import ReportManager, ReportInterface -from testipy.reporter import PackageDetails, SuiteDetails, TestDetails +from testipy.reporter import ReportInterface + +if TYPE_CHECKING: + from testipy.models import PackageAttr, PackageDetails, SuiteDetails, TestDetails + from testipy.reporter import ReportManager from engineio.payload import Payload Payload.max_decode_packets = 100 @@ -55,8 +59,8 @@ def get_image_from_attachment(attachment: Dict) -> str: return "" -def _run_flask_in_thread(): - socket_io.run(app, host="0.0.0.0", port=PORT, debug=False, log_output=DEBUG_MODE_SOCKET_IO, allow_unsafe_werkzeug=True) +def _run_flask_in_thread(port: int = PORT): + socket_io.run(app, host="0.0.0.0", port=port, debug=False, log_output=DEBUG_MODE_SOCKET_IO, allow_unsafe_werkzeug=True) def _send_cached_content(): @@ -119,6 +123,7 @@ class ReporterWeb(ReportInterface): def __init__(self, rm: ReportManager, sa: StartArguments): super().__init__(self.__class__.__name__) + self.port = int(rm.get_ap().get_option("-r-web-port", PORT)) # save ReportManager self.rm = rm @@ -131,7 +136,7 @@ def __init__(self, rm: ReportManager, sa: StartArguments): self.namespace = sa.namespace ServerSocketIO(app, socket_io, self.namespace) - Thread(target=_run_flask_in_thread).start() + Thread(target=_run_flask_in_thread, args=[self.port]).start() def save_file(self, current_test: TestDetails, data, filename: str): pass @@ -140,7 +145,7 @@ def copy_file(self, current_test: TestDetails, orig_filename: str, dest_filename pass def _startup_(self, selected_tests: List[PackageAttr]): - url = f"http://127.0.0.1:{PORT}/?namespace={self.namespace}" + url = f"http://127.0.0.1:{self.port}/?namespace={self.namespace}" _selected_tests = self.rm.get_selected_tests_as_dict() try: @@ -148,6 +153,7 @@ def _startup_(self, selected_tests: List[PackageAttr]): raise PermissionError("Cannot open browser!") except: print(f"Open your browser to view the results at: {url}") + _push_to_cache("rm_selected_tests", {"data": _selected_tests["data"]}) def _teardown_(self, end_state: str): @@ -188,12 +194,14 @@ def end_suite(self, sd: SuiteDetails): def start_test(self, current_test: TestDetails): _delete_from_cache("start_test") - test_details = {"name": current_test.get_name(), - "ncycle": current_test.get_cycle(), - "usecase": current_test.get_usecase(), - "method_id": current_test.get_method_id(), - "test_id": current_test.get_test_id(), - "comment": current_test.get_comment()} + test_details = { + "name": current_test.get_name(), + "ncycle": current_test.get_cycle(), + "usecase": current_test.get_usecase(), + "method_id": current_test.get_method_id(), + "test_id": current_test.get_test_id(), + "comment": current_test.get_comment() + } self._notify_clients("start_test", test_details) self.test_info(current_test, f"Test details:\n{prettify(current_test.get_attr())}", "DEBUG") @@ -222,7 +230,7 @@ def end_test(self, current_test: TestDetails, ending_state: str, end_reason: str test_usecase = current_test.get_usecase() test_duration = current_test.get_duration() - info = self._format_info(current_test) + info = self._format_info(current_test, ending_state, end_reason) data = {"status": ending_state, "method_id": test_method_id, @@ -234,7 +242,8 @@ def end_test(self, current_test: TestDetails, ending_state: str, end_reason: str self._notify_clients('end_test', data) def show_status(self, message: str): - self._notify_clients('show_status', message, save_to_cache=False) + _delete_from_cache("show_status") + self._notify_clients('show_status', message, save_to_cache=True) def show_alert_message(self, message: str): self._notify_clients('show_alert_message', message, save_to_cache=False) @@ -249,11 +258,23 @@ def input_prompt_message(self, message: str, default_value: str = ""): return response - def _format_info(self, current_test: TestDetails): + def _format_info(self, current_test: TestDetails, ending_state: str, end_reason: str): + test_attr = { + "package": current_test.suite.package.get_name(), + "suite": current_test.suite.get_name(), + "suite filename": current_test.suite.suite_attr.filename, + "test_id": current_test.get_test_id(), + } + test_attr.update(current_test.get_attr()) + str_res = "

Test Attributes:

" - str_res += f"

{escaped_text(prettify(current_test.get_attr(), as_yaml=True))}


" - str_res += f"meid({current_test.get_method_id()}) - test_id({current_test.get_test_id()}) - {current_test.get_full_name(with_cycle_number=True)} {current_test.get_counters().get_last_state()} - {current_test.get_counters().get_last_reason_of_state()}
" - str_res += f"{current_test.get_counters().get_begin_time()} - {current_test.get_counters().get_end_time()} took {format_duration(current_test.get_duration())}" + str_res += f"

{escaped_text(prettify(test_attr, as_yaml=True))}


" + str_res += f"{escaped_text(current_test.get_full_name(with_cycle_number=True))}
" + str_res += escaped_text(" Status: ") + f"{ending_state}
" + str_res += escaped_text("End reason: ") + f"{end_reason}
" + str_res += escaped_text(" Started: ") + f"{current_test.get_counters().get_begin_time()}
" + str_res += escaped_text(" Ended: ") + f"{current_test.get_counters().get_end_time()}
" + str_res += escaped_text(" Took: ") + f"{format_duration(current_test.get_duration())}" # add test info log for ts, current_time, level, info, attachment in current_test.get_info(): @@ -264,7 +285,6 @@ def _format_info(self, current_test: TestDetails): tc = current_test.get_test_step_counters() if len(tc.get_timed_laps()) > 0: str_res += "
" + escaped_text(current_test.get_test_step_counters_tabulate()).replace("\n", "
") - str_res += "
Steps Summary: " + escaped_text(str(tc.summary())) else: str_res += "
Test Summary: " + escaped_text(str(current_test.get_counters().summary(verbose=False))) diff --git a/testipy/reporter/reporters/templates/webreport.js b/testipy/reporter/reporters/templates/webreport.js index 7d6309f..5f022b6 100644 --- a/testipy/reporter/reporters/templates/webreport.js +++ b/testipy/reporter/reporters/templates/webreport.js @@ -75,8 +75,8 @@ $(document).ready(function() { show_status(message); }); socket.on('show_alert_message', function (message) { - window.alert(message); show_status(message); + window.alert(message); }); socket.on('input_prompt_message', function (msg, cb) { let response = window.prompt(msg.message, msg.default_value); @@ -176,6 +176,7 @@ function show_log(tid) { function show_status(message) { $('#status_message').text(message); + $('#test_output').append(message).append("
"); } diff --git a/testipy/run.py b/testipy/run.py index a3d613b..f4d9cfe 100644 --- a/testipy/run.py +++ b/testipy/run.py @@ -16,7 +16,7 @@ from testipy import get_exec_logger from testipy.configs import default_config, enums_data from testipy.engine import read_files_to_get_selected_tests, run_selected_tests -from testipy.engine.models import PackageAttr +from testipy.models import PackageAttr from testipy.reporter.report_manager import build_report_manager_with_reporters from testipy.helpers import get_traceback_list, format_duration, prettify from testipy.lib_modules.args_parser import ArgsParser