Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 142 additions & 9 deletions bzt/modules/_apiritif/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ def create_method_name(label):


class ApiritifScriptGenerator(object):
def _gen_class_actionid_tracking(self):
# Add _current_actionId to the class and update it on each action_start
return [
ast.Assign(
targets=[ast_attr("self._current_actionId")],
value=ast.Constant(value=None, kind="")
)
]

def _gen_action_start_with_tracking(self, actionId):
# Assign actionId (or None) to self._current_actionId before each action
return [
ast.Assign(
targets=[ast_attr("self._current_actionId")],
value=ast.Constant(value=actionId, kind="")
)
]

BYS = {
'xpath': "XPATH",
'css': "CSS_SELECTOR",
Expand Down Expand Up @@ -163,6 +181,7 @@ def __init__(self, scenario, label, wdlog=None, executor=None, ignore_unknown_ac
self.replace_dialogs = True
self.bzm_tdo_settings = bzm_tdo_settings
self.do_testdata_orchestration = False
self.has_action_ids = False

def _parse_action_params(self, expr, name):
res = expr.match(name)
Expand Down Expand Up @@ -243,7 +262,11 @@ def _parse_dict_action(self, action_config):
# actionId is an optional unique identifier for this action, used to track or reference
# the action in the generated code and logs. It is passed through multiple layers of the
# code generation process to maintain traceability of actions.
actionId = action_config.get("actionId")
if "actionId" in action_config and action_config.get("actionId") not in (None, "", {}):
actionId = action_config.get("actionId")
self.has_action_ids = True
else:
actionId = None
selectors = []
if action_config.get("locators"):
selectors = action_config.get("locators")
Expand Down Expand Up @@ -696,6 +719,10 @@ def _gen_action(self, action_config, parent_request=None, index_label=""):

wrapInTransaction = self._is_report_inside_actions(parent_request)

action_tracking = []
if self.has_action_ids:
action_tracking.extend(self._gen_action_start_with_tracking(actionId))

action_elements = []

if atype in self.EXTERNAL_HANDLER_TAGS:
Expand Down Expand Up @@ -781,18 +808,21 @@ def _gen_action(self, action_config, parent_request=None, index_label=""):
if atype.lower() in self.ACTIONS_WITH_WAITER:
action_elements.append(ast_call(func=ast_attr("waiter"), args=[]))

action_body = [ast.Expr(element) for element in action_elements]

if wrapInTransaction:
label = self._create_action_label(parent_request.label, index_label, action)

body = action_tracking + action_body
return [ast.With(
items=[ast.withitem(
context_expr=ast_call(
func=ast_attr("apiritif.transaction"),
args=[self._gen_expr(label)]),
optional_vars=None)],
body=[ast.Expr(element) for element in action_elements])]
body=body)]

return [ast.Expr(element) for element in action_elements]
return action_tracking + action_body

def _gen_foreach_mngr(self, action_config):
self.selenium_extras.add("get_elements")
Expand Down Expand Up @@ -1706,6 +1736,8 @@ def _gen_classdef(self):

def _gen_class_setup(self):
data_sources = [self._gen_default_vars()]
if self.has_action_ids:
data_sources.extend(self._gen_class_actionid_tracking())
for idx in range(len(self.data_sources)):
data_sources.append(ast.Expr(ast_call(func=ast_attr("reader_%s.read_vars" % (idx + 1)))))

Expand Down Expand Up @@ -1786,8 +1818,10 @@ def _gen_class_teardown(self):
body = [
ast.If(
test=ast_attr("self.driver"),
body=ast.Expr(ast_call(func=ast_attr("self.driver.quit"))), orelse=[])]

body=[ast.Expr(ast_call(func=ast_attr("self.driver.quit")))],
orelse=[]
)
]
return ast.FunctionDef(name="tearDown", args=[ast_attr("self")], body=body, decorator_list=[])

def _nfc_preprocess(self, requests):
Expand Down Expand Up @@ -1858,7 +1892,11 @@ def _gen_test_methods(self):
if isinstance(request, SetVariables):
self.service_methods.append(label) # for sample excluding

methods.append(self._gen_test_method(method_name, body))
method_body = body
if self.has_action_ids and not finally_marker:
method_body = self._gen_actionid_exception_wrapped_body(body)

methods.append(self._gen_test_method(method_name, method_body))
if finally_marker:
finally_block_content.append(method_name)
else:
Expand All @@ -1876,13 +1914,100 @@ def _gen_set_vars(self, request):

return res

def _gen_actionid_exception_wrapped_body(self, body):
prefixed_error = ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.Constant("actionId: ", kind=""),
op=ast.Add(),
right=ast.Call(
func=ast.Name(id="str", ctx=ast.Load()),
args=[ast_attr("self._current_actionId")],
keywords=[]
)
),
op=ast.Add(),
right=ast.Constant(" | ", kind="")
),
op=ast.Add(),
right=ast.Call(
func=ast.Name(id="str", ctx=ast.Load()),
args=[ast.Name(id="exc", ctx=ast.Load())],
keywords=[]
)
)

final_error = ast.IfExp(
test=ast.Compare(
left=ast_attr("self._current_actionId"),
ops=[ast.IsNot()],
comparators=[ast.Constant(value=None, kind="")]
),
body=prefixed_error,
orelse=ast.Call(
func=ast.Name(id="str", ctx=ast.Load()),
args=[ast.Name(id="exc", ctx=ast.Load())],
keywords=[]
)
)

if len(body) == 1 and isinstance(body[0], ast.With):
with_stmt = body[0]
wrapped_try = ast.Try(
body=with_stmt.body,
handlers=[ast.ExceptHandler(
type=ast.Name(id="Exception", ctx=ast.Load()),
name="exc",
body=[ast.Raise(
exc=ast.Call(
func=ast.Call(
func=ast.Name(id="type", ctx=ast.Load()),
args=[ast.Name(id="exc", ctx=ast.Load())],
keywords=[]
),
args=[final_error],
keywords=[]
),
cause=ast.Name(id="exc", ctx=ast.Load())
)]
)],
orelse=[],
finalbody=[]
)
with_stmt.body = [wrapped_try]
return [with_stmt]

wrapped_try = ast.Try(
body=body,
handlers=[ast.ExceptHandler(
type=ast.Name(id="Exception", ctx=ast.Load()),
name="exc",
body=[ast.Raise(
exc=ast.Call(
func=ast.Call(
func=ast.Name(id="type", ctx=ast.Load()),
args=[ast.Name(id="exc", ctx=ast.Load())],
keywords=[]
),
args=[final_error],
keywords=[]
),
cause=ast.Name(id="exc", ctx=ast.Load())
)]
)],
orelse=[],
finalbody=[]
)

return [wrapped_try]

def _gen_master_test_method(self, try_block, finally_block):
if not try_block:
raise TaurusConfigError("Supported transactions not found, test is empty")

body = []
main_body = []
for slave_name in try_block:
body.append(ast.Expr(ast_call(func=ast_attr("self." + slave_name))))
main_body.append(ast.Expr(ast_call(func=ast_attr("self." + slave_name))))

finally_body = []
for slave_name in finally_block:
Expand All @@ -1891,7 +2016,15 @@ def _gen_master_test_method(self, try_block, finally_block):
if finally_body:
teardown_marker = ast.Expr(ast_call(func=ast_attr("apiritif.set_stage"), args=[self._gen_expr("teardown")]))
finally_body.insert(0, teardown_marker)
body = [gen_try_except(try_body=body, final_body=finally_body)]

body = [ast.Try(
body=main_body,
handlers=[],
orelse=[],
finalbody=finally_body
)]
else:
body = main_body

name = 'test_' + create_method_name(self.label)
return self._gen_test_method(name=name, body=body)
Expand Down
105 changes: 105 additions & 0 deletions tests/unit/modules/_selenium/test_selenium_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,111 @@ def test_modern_actions_generator(self):
TestSeleniumScriptGeneration.clear_spaces(content),
msg="\n\n%s. %s" % (idx, target_lines[idx]))

def test_actionid_try_except_inside_step_method(self):
self.configure({
"execution": [{
"executor": "apiritif",
"scenario": "loc_sc"
}],
"scenarios": {
"loc_sc": {
"default-address": "https://example.com",
"requests": [{
"label": "tx",
"actions": [{
"type": "go",
"param": "https://example.com",
"value": None,
"actionId": "aid-1"
}]
}]
}
}
})

self.obj.prepare()
with open(self.obj.script) as fds:
content = fds.read()
normalized = TestSeleniumScriptGeneration.clear_spaces(content)

self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("with apiritif.smart_transaction('tx'):\n try:"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("raise type(exc)"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("actionId: "),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("def test_locsc(self):\n self._1_tx()"),
normalized
)

self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("self._current_actionId = None"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("self._current_actionId = 'aid-1'"),
normalized
)

action_id_assignment_pos = normalized.find(
TestSeleniumScriptGeneration.clear_spaces("self._current_actionId = 'aid-1'")
)
action_call_pos = normalized.find(
TestSeleniumScriptGeneration.clear_spaces("self.driver.get('https://example.com')")
)
self.assertNotEqual(-1, action_id_assignment_pos)
self.assertNotEqual(-1, action_call_pos)
self.assertLess(action_id_assignment_pos, action_call_pos)

self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("self._current_actionId is not None"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("str(self._current_actionId)"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("'actionId: '"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("' | '"),
normalized
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("str(exc)"),
normalized
)

def test_actionid_try_except_wraps_non_with_body(self):
generator = bzt.modules._apiritif.generator.ApiritifScriptGenerator.__new__(
bzt.modules._apiritif.generator.ApiritifScriptGenerator
)
body = [ast.Expr(value=ast.Constant(value="noop"))]

wrapped = generator._gen_actionid_exception_wrapped_body(body)

self.assertEqual(1, len(wrapped))
self.assertIsInstance(wrapped[0], ast.Try)

wrapped_code = astunparse.unparse(ast.Module(body=wrapped))
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("try:"),
TestSeleniumScriptGeneration.clear_spaces(wrapped_code)
)
self.assertIn(
TestSeleniumScriptGeneration.clear_spaces("raise type(exc)"),
TestSeleniumScriptGeneration.clear_spaces(wrapped_code)
)

@staticmethod
def clear_spaces(content):
return content.replace(" ", "").replace("\t", "").replace("\n", "")
Expand Down