Blazemeter / taurus

@@ -29,7 +29,7 @@
Loading
29 29
from bzt.requests_model import HTTPRequest, HierarchicRequestParser, TransactionBlock, \
30 30
    SetVariables, IncludeScenarioBlock
31 31
from bzt.utils import iteritems, dehumanize_time, ensure_is_dict, is_selenium_4
32 -
from .ast_helpers import ast_attr, ast_call, gen_empty_line_stmt, gen_store, gen_subscript
32 +
from .ast_helpers import ast_attr, ast_call, gen_empty_line_stmt, gen_store, gen_subscript, gen_try_except, gen_raise
33 33
from .jmeter_functions import JMeterExprCompiler
34 34
35 35
@@ -118,7 +118,10 @@
Loading
118 118
119 119
    BY_TAGS = ("byName", "byID", "byCSS", "byXPath", "byLinkText", "byElement", "byShadow")
120 120
    COMMON_TAGS = ("Cookies", "Title", "Window", "Eval", "ByIdx", "String")
121 -
    EXTENDED_LOG_TAG = ("logStart", "logEnd", "log")
121 +
    EXTERNAL_HANDLER_START = 'action_start'
122 +
    EXTERNAL_HANDLER_END = 'action_end'
123 +
    EXTERNAL_HANDLER_TAGS = (EXTERNAL_HANDLER_START, EXTERNAL_HANDLER_END)
124 +
    DEPRECATED_LOG_TAG = 'log'
122 125
123 126
    ACCESS_TARGET = 'target'
124 127
    ACCESS_PLAIN = 'plain'
@@ -128,7 +131,7 @@
Loading
128 131
129 132
    def __init__(self, scenario, label, wdlog=None, executor=None,
130 133
                 ignore_unknown_actions=False, generate_markers=None,
131 -
                 capabilities=None, wd_addr=None, test_mode="selenium", generate_external_logging=None):
134 +
                 capabilities=None, wd_addr=None, test_mode="selenium", generate_external_handler=False):
132 135
        self.scenario = scenario
133 136
        self.selenium_extras = set()
134 137
        self.data_sources = list(scenario.get_data_sources())
@@ -148,7 +151,7 @@
Loading
148 151
        self.appium = False
149 152
        self.ignore_unknown_actions = ignore_unknown_actions
150 153
        self.generate_markers = generate_markers
151 -
        self.generate_external_logging = generate_external_logging
154 +
        self.generate_external_handler = generate_external_handler
152 155
        self.test_mode = test_mode
153 156
154 157
    def _parse_action_params(self, expr, name):
@@ -179,7 +182,7 @@
Loading
179 182
180 183
    def _parse_string_action(self, name, param):
181 184
        tags = "|".join(self.BY_TAGS + self.COMMON_TAGS)
182 -
        all_actions = self.ACTIONS + "|" + "|".join(self.EXTENDED_LOG_TAG) + "|" + self.EXECUTION_BLOCKS
185 +
        all_actions = self.ACTIONS + "|" + "|".join((self.DEPRECATED_LOG_TAG, self.EXECUTION_BLOCKS))
183 186
        expr = re.compile(r"^(%s)(%s)?(\(([\S\s]*)\))?$" % (all_actions, tags), re.IGNORECASE)
184 187
        atype, tag, selector = self._parse_action_params(expr, name)
185 188
        value = None
@@ -231,7 +234,7 @@
Loading
231 234
        if action_config.get("element"):
232 235
            selectors.extend(self._gen_selector_byelement(action_config))
233 236
        if action_config.get("shadow"):
234 -
            selectors = [{"shadow" : action_config.get("shadow")}]
237 +
            selectors = [{"shadow": action_config.get("shadow")}]
235 238
        if action_config.get("source") and action_config.get("target"):
236 239
            source = action_config.get("source")
237 240
            target = action_config.get("target")
@@ -243,7 +246,8 @@
Loading
243 246
        param = action_config["param"]
244 247
        value = action_config["value"]
245 248
        tags = "|".join(self.COMMON_TAGS) + "|ByName"  # ByName is needed in switchFrameByName
246 -
        expr = re.compile("^(%s)(%s)?$" % (self.ACTIONS, tags), re.IGNORECASE)
249 +
        all_actions = self.ACTIONS + "|" + "|".join(self.EXTERNAL_HANDLER_TAGS)
250 +
        expr = re.compile("^(%s)(%s)?$" % (all_actions, tags), re.IGNORECASE)
247 251
        action_params = self._parse_action_params(expr, name)
248 252
249 253
        return action_params[0], action_params[1], param, value, selectors
@@ -399,9 +403,9 @@
Loading
399 403
                args=[ast.Str(selector, kind="")]))
400 404
        else:
401 405
            if not tag:
402 -
                tag = "name"    # if tag is not present default it to name
406 +
                tag = "name"  # if tag is not present default it to name
403 407
            elif tag.startswith('by'):
404 -
                tag = tag[2:]   # remove the 'by' prefix
408 +
                tag = tag[2:]  # remove the 'by' prefix
405 409
            elements.append(ast_call(
406 410
                func=ast_attr(method),
407 411
                args=[self._gen_locator(tag, selector)]))
@@ -667,9 +671,14 @@
Loading
667 671
668 672
        action_elements = []
669 673
670 -
        if atype == "log":
671 -
            action_elements.append(
672 -
                ast_call(func=ast_attr("apiritif.external_log"), args=[self._gen_expr(param.strip())]))
674 +
        if atype in self.EXTERNAL_HANDLER_TAGS:
675 +
            action_elements.append(ast_call(
676 +
                func=ast_attr(atype),
677 +
                args=[self._gen_expr(self._gen_expr(param))]
678 +
            ))
679 +
        elif atype == self.DEPRECATED_LOG_TAG:
680 +
            self.log.warning("'log' is deprecated. It will be removed in the next release.")
681 +
            return []
673 682
        elif tag == "window":
674 683
            action_elements.extend(self._gen_window_mngr(atype, param))
675 684
        elif atype == "switchframe":
@@ -921,7 +930,7 @@
Loading
921 930
            browser = "remote"  # Force to use remote web driver
922 931
        elif not browser:
923 932
            browser = "firefox"
924 -
        elif browser not in local_browsers:   # browser isn't supported
933 +
        elif browser not in local_browsers:  # browser isn't supported
925 934
            raise TaurusConfigError("Unsupported browser name: %s" % browser)
926 935
        return browser
927 936
@@ -931,10 +940,8 @@
Loading
931 940
    def _gen_webdriver(self):
932 941
        self.log.debug("Generating setUp test method")
933 942
934 -
        body = [ast.Assign(targets=[ast_attr("self.driver")], value=ast_attr("None"))]
935 -
936 943
        browser = self._check_platform()
937 -
        body.extend(self._get_options(browser))
944 +
        body = [self._get_options(browser)]
938 945
939 946
        if browser == 'firefox':
940 947
            body.extend(self._get_firefox_profile() + [self._get_firefox_webdriver()])
@@ -956,6 +963,30 @@
Loading
956 963
957 964
        return body
958 965
966 +
    def _wrap_with_try_except(self, web_driver_cmds):
967 +
        body = [ast.Assign(targets=[ast_attr("self.driver")], value=ast_attr("None")),
968 +
            self._gen_new_session_start()]
969 +
970 +
        exception_variables = [ast.Name(id='ex_type'), ast.Name(id='ex'), ast.Name(id='tb')]
971 +
        exception_handler = [
972 +
            ast.Assign(targets=[ast.Tuple(elts=exception_variables)],
973 +
                       value=ast_call(func=ast_attr('sys.exc_info'), args=[]))]
974 +
975 +
        exception_handler.extend(self._gen_new_session_end(True))
976 +
977 +
        exception_handler.append(
978 +
            ast.Expr(value=ast_call(
979 +
                func=ast_attr('apiritif.log.error'),
980 +
                args=[
981 +
                    self._gen_expr(ast_attr("str(traceback.format_exception(ex_type, ex, tb))"))
982 +
                ])))
983 +
        exception_handler.append(gen_raise())
984 +
985 +
        body.append(gen_try_except(web_driver_cmds, exception_handler))
986 +
        body.append(self._gen_new_session_end())
987 +
988 +
        return body
989 +
959 990
    def _get_timeout(self):
960 991
        return ast.Expr(
961 992
            ast_call(
@@ -1220,6 +1251,10 @@
Loading
1220 1251
            ast.Import(names=[ast.alias(name='apiritif', asname=None)]),  # or "from apiritif import http, utils"?
1221 1252
            gen_empty_line_stmt()]
1222 1253
1254 +
        if self.generate_external_handler:
1255 +
            imports.append(ast.Import(names=[ast.alias(name='traceback', asname=None)]))
1256 +
            self.selenium_extras.update(self.EXTERNAL_HANDLER_TAGS)
1257 +
1223 1258
        if self.test_mode == "selenium":
1224 1259
            if self.appium:
1225 1260
                source = "appium"
@@ -1328,11 +1363,6 @@
Loading
1328 1363
            self.selenium_extras.add(func_name)
1329 1364
            handlers.append(ast.Expr(ast_call(func=func_name)))
1330 1365
1331 -
        if self.generate_external_logging:
1332 -
            self.selenium_extras.add("add_logging_handlers")
1333 -
1334 -
            handlers.append(ast.Expr(ast_call(func="add_logging_handlers")))
1335 -
1336 1366
        stored_vars = {
1337 1367
            "timeout": "timeout",
1338 1368
            "func_mode": str(self.executor.engine.is_functional_mode())}
@@ -1357,11 +1387,13 @@
Loading
1357 1387
        timeout_setup = [ast.Expr(ast.Assign(
1358 1388
            targets=[ast_attr("timeout")],
1359 1389
            value=ast.Num(self._get_scenario_timeout(), kind="")))]
1360 -
1390 +
        body = data_sources + timeout_setup + target_init + handlers + store_block
1391 +
        if self.generate_external_handler:
1392 +
            body = self._wrap_with_try_except(body)
1361 1393
        setup = ast.FunctionDef(
1362 1394
            name="setUp",
1363 1395
            args=[ast_attr("self")],
1364 -
            body=data_sources + timeout_setup + target_init + handlers + store_block,
1396 +
            body=body,
1365 1397
            decorator_list=[])
1366 1398
        return [setup, gen_empty_line_stmt()]
1367 1399
@@ -1456,7 +1488,7 @@
Loading
1456 1488
        value = value.replace("{", "{{").replace("}", "}}")
1457 1489
        for block in re.finditer(r"\${{[\w\d]*}}", value):
1458 1490
            start, end = block.start(), block.end()
1459 -
            line = "$" + value[start+2:end-1]
1491 +
            line = "$" + value[start + 2:end - 1]
1460 1492
            value = value[:start] + line + value[end:]
1461 1493
        return value
1462 1494
@@ -1645,8 +1677,8 @@
Loading
1645 1677
        if self.test_mode == "selenium":
1646 1678
            for action in req.config.get("actions"):
1647 1679
                action_lines = self._gen_action(action)
1648 -
                if self.generate_external_logging:
1649 -
                    action_lines = self._gen_log_start(action) + action_lines + self._gen_log_end(action)
1680 +
                if self.generate_external_handler:
1681 +
                    action_lines = self._gen_action_start(action) + action_lines + self._gen_action_end(action)
1650 1682
1651 1683
                lines.extend(action_lines)
1652 1684
@@ -1672,11 +1704,59 @@
Loading
1672 1704
1673 1705
        return lines
1674 1706
1675 -
    def _gen_log_start(self, action):
1676 -
        return self._gen_action("log('start: %s')" % action)
1677 -
1678 -
    def _gen_log_end(self, action):
1679 -
        return self._gen_action("log('end: %s')" % action)
1707 +
    def _gen_new_session_start(self):
1708 +
        return self._gen_action({
1709 +
            'type': self.EXTERNAL_HANDLER_START,
1710 +
            'value': None,
1711 +
            'param': {
1712 +
                'type': 'new_session',
1713 +
                'value': None,
1714 +
                'param': self.capabilities,
1715 +
            }
1716 +
        })
1717 +
1718 +
    def _gen_new_session_end(self, msg=False):
1719 +
        val = {
1720 +
            'type': 'new_session',
1721 +
            'param': self.capabilities,
1722 +
        }
1723 +
1724 +
        if msg:
1725 +
            val['message'] = self._gen_expr(ast_attr("str(traceback.format_exception(ex_type, ex, tb))"))
1726 +
1727 +
        return self._gen_action({
1728 +
            'type': self.EXTERNAL_HANDLER_END,
1729 +
            'value': None,
1730 +
            'param': val,
1731 +
        })
1732 +
1733 +
    def _gen_action_start(self, action):
1734 +
        atype, tag, param, value, selectors = self._parse_action(action)
1735 +
        return self._gen_action({
1736 +
            'type': self.EXTERNAL_HANDLER_START,
1737 +
            'value': None,
1738 +
            'param': {
1739 +
                'type': atype,
1740 +
                'tag': tag,
1741 +
                'param': param,
1742 +
                'value': value,
1743 +
                'selectors': selectors,
1744 +
            },
1745 +
        })
1746 +
1747 +
    def _gen_action_end(self, action):
1748 +
        atype, tag, param, value, selectors = self._parse_action(action)
1749 +
        return self._gen_action({
1750 +
            'type': self.EXTERNAL_HANDLER_END,
1751 +
            'value': None,
1752 +
            'param': {
1753 +
                'type': atype,
1754 +
                'tag': tag,
1755 +
                'param': param,
1756 +
                'value': value,
1757 +
                'selectors': selectors,
1758 +
            },
1759 +
        })
1680 1760
1681 1761
    def _gen_sel_assertion(self, assertion_config):
1682 1762
        self.log.debug("Generating assertion, config: %s", assertion_config)

@@ -68,6 +68,11 @@
Loading
68 68
        # path to taurus dir. It's necessary for bzt usage inside tools/helpers
69 69
        self.env.add_path({"PYTHONPATH": get_full_path(BZT_DIR, step_up=1)})
70 70
71 +
        if self.settings.get("plugins-path"):
72 +
            # add path to plugins directory to Apiritif env vars
73 +
            self.log.debug(f'Found Apiritif plugins path: {self.settings.get("plugins-path")}')
74 +
            self.env.add_path({"PLUGINS_PATH": self.settings.get('plugins-path')})
75 +
71 76
        self.reporting_setup()  # no prefix/suffix because we don't fully control report file names
72 77
73 78
    def __tests_from_requests(self):
@@ -91,6 +96,9 @@
Loading
91 96
                self.log.warning("Obsolete format of capabilities found (list), should be dict")
92 97
                scenario["capabilities"] = {item.keys()[0]: item.values()[0] for item in scenario_caps}
93 98
99 +
            if scenario.get("external-logging", False):
100 +
                self.log.warning("'external-logging' is deprecated and unsupported now. Use 'plugins-path' instead.")
101 +
94 102
            configs = (self.settings, scenario, self.execution)
95 103
96 104
            capabilities = get_assembled_value(configs, "capabilities")
@@ -102,7 +110,7 @@
Loading
102 110
                generate_markers=generate_markers,
103 111
                capabilities=capabilities,
104 112
                wd_addr=remote, test_mode=test_mode,
105 -
                generate_external_logging=scenario.get("external-logging", False))
113 +
                generate_external_handler=True if self.settings.get('plugins-path', False) else False)
106 114
107 115
        builder.build_source_code()
108 116
        builder.save(filename)

@@ -27,7 +27,27 @@
Loading
27 27
28 28
29 29
def gen_empty_line_stmt():
30 -
    return ast.Expr(value=ast.Name(id="")) # hacky, but works
30 +
    return ast.Expr(value=ast.Name(id=""))  # hacky, but works
31 +
32 +
33 +
def gen_try_except(try_body, exception_body):
34 +
    return ast.Try(
35 +
        body=try_body,
36 +
        handlers=[
37 +
            ast.ExceptHandler(
38 +
                type=ast.Name(id='Exception', ctx=ast.Load()),
39 +
                name=None,
40 +
                body=exception_body,
41 +
            )
42 +
        ],
43 +
        type_ignores=[],
44 +
        orelse=None,
45 +
        finalbody=[],
46 +
    )
47 +
48 +
49 +
def gen_raise():
50 +
    return ast.Raise(exc=None, cause=None)
31 51
32 52
33 53
def gen_subscript(var_name, index):
Files Coverage
bzt 90.21%
Project Totals (70 files) 90.21%
9274.2
TRAVIS_PYTHON_VERSION=3.8
TRAVIS_OS_NAME=linux
1
codecov:
2
  notify:
3
    require_ci_to_pass: yes
4

5
coverage:
6
  round: up
7

8
ignore:
9
  - bzt/resources
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading