diff --git a/examples/single/robot/tests/conditional.robot b/examples/single/robot/tests/conditional.robot new file mode 100644 index 00000000..f6ec68e1 --- /dev/null +++ b/examples/single/robot/tests/conditional.robot @@ -0,0 +1,50 @@ +*** Settings *** +Documentation Tests exercising IF/ELSE IF/ELSE branches. +... Used to verify that conditional steps carry correct +... start_time / end_time / duration into Qase TestOps. +Library Collections +Library String + +*** Test Cases *** + +IF Branch Taken + [Documentation] The first IF branch executes; ELSE branch is skipped. + [Tags] Q-100 + ${value}= Set Variable yes + IF "${value}" == "yes" + Log Going through the IF branch + Sleep 50ms + Should Be Equal ${value} yes + ELSE + Log This ELSE must remain skipped + Sleep 200ms + END + +ELSE Branch Taken + [Documentation] IF condition is false; ELSE branch executes. + [Tags] Q-101 + ${value}= Set Variable no + IF "${value}" == "yes" + Log This IF must remain skipped + Sleep 200ms + ELSE + Log Going through the ELSE branch + Sleep 100ms + Should Be Equal ${value} no + END + +ELSE IF Chain Middle Branch + [Documentation] Three-way chain: middle ELSE IF branch executes. + [Tags] Q-102 + ${value}= Set Variable medium + IF "${value}" == "low" + Log Low branch (skipped) + Sleep 150ms + ELSE IF "${value}" == "medium" + Log Middle branch executing + Sleep 75ms + Should Be Equal ${value} medium + ELSE + Log High branch (skipped) + Sleep 150ms + END diff --git a/qase-robotframework/pyproject.toml b/qase-robotframework/pyproject.toml index e834447b..4328dc52 100644 --- a/qase-robotframework/pyproject.toml +++ b/qase-robotframework/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "qase-robotframework" -version = "6.0.1" +version = "6.0.2" description = "Qase Robot Framework Plugin" readme = "README.md" authors = [{name = "Qase Team", email = "support@qase.io"}] diff --git a/qase-robotframework/src/qase/robotframework/listener.py b/qase-robotframework/src/qase/robotframework/listener.py index 894e676b..a84558fe 100644 --- a/qase-robotframework/src/qase/robotframework/listener.py +++ b/qase-robotframework/src/qase/robotframework/listener.py @@ -554,8 +554,14 @@ def __parse_condition_steps(self, result_step, accumulated_vars: dict = None) -> else: step = Listener._create_gherkin_step_from_name(body_element) - step.execution.start_time = None - step.execution.end_time = None + start_time = getattr(body_element, "start_time", None) + end_time = getattr(body_element, "end_time", None) + elapsed = getattr(body_element, "elapsed_time", None) + + step.execution.start_time = start_time.timestamp() if start_time is not None else None + step.execution.end_time = end_time.timestamp() if end_time is not None else None + if elapsed is not None: + step.execution.duration = int(elapsed.total_seconds() * 1000) steps.append(step) diff --git a/qase-robotframework/tests/tests_qaseio_robotframework/test_listener.py b/qase-robotframework/tests/tests_qaseio_robotframework/test_listener.py index 0ded2c7a..e7263c04 100644 --- a/qase-robotframework/tests/tests_qaseio_robotframework/test_listener.py +++ b/qase-robotframework/tests/tests_qaseio_robotframework/test_listener.py @@ -287,3 +287,68 @@ def test_start_and_end_time_round_trip(self): assert steps[0].execution.start_time == keyword.start_time.timestamp() assert steps[0].execution.end_time == keyword.end_time.timestamp() + + +class _FakeIfBranch: + """Stand-in for a Robot Framework IF/ELSE branch body element.""" + + def __init__(self, branch_type: str, elapsed_seconds: float, body=None): + start = datetime(2026, 1, 1, tzinfo=timezone.utc) + self.type = branch_type # "IF" / "ELSE IF" / "ELSE" + self.status = "PASS" + self.start_time = start + self.elapsed_time = timedelta(seconds=elapsed_seconds) + self.end_time = start + self.elapsed_time + self.body = body or [] + self.values = () + + +class TestParseConditionStepsTiming: + """IF/ELSE branches must carry their RF start_time/end_time/duration. + + Regression for the bug where __parse_condition_steps unconditionally + overwrote start_time/end_time with None, so conditional branches + showed up in TestOps without any timing data. + """ + + def _parse(self, listener, branches): + result_step = MagicMock(spec=["body"]) + result_step.body = branches + return listener._Listener__parse_condition_steps(result_step) + + def test_executed_if_branch_carries_real_timestamps(self): + listener = _bare_listener() + branch = _FakeIfBranch("IF", elapsed_seconds=0.053) + + steps = self._parse(listener, [branch]) + + assert len(steps) == 1 + assert steps[0].execution.start_time == branch.start_time.timestamp() + assert steps[0].execution.end_time == branch.end_time.timestamp() + assert steps[0].execution.duration == 53 + + def test_skipped_else_branch_still_gets_timestamps(self): + """Skipped branches have near-zero elapsed time but must still have + non-None start_time/end_time so the timeline shows them.""" + listener = _bare_listener() + branch = _FakeIfBranch("ELSE", elapsed_seconds=0.0001) + + steps = self._parse(listener, [branch]) + + assert steps[0].execution.start_time is not None + assert steps[0].execution.end_time is not None + assert steps[0].execution.duration == 0 # int(0.0001 * 1000) == 0 + + def test_else_if_chain_each_branch_keeps_timing(self): + listener = _bare_listener() + branches = [ + _FakeIfBranch("IF", elapsed_seconds=0.0001), # not taken + _FakeIfBranch("ELSE IF", elapsed_seconds=0.076), # taken + _FakeIfBranch("ELSE", elapsed_seconds=0.0001), # not taken + ] + + steps = self._parse(listener, branches) + + assert [s.execution.duration for s in steps] == [0, 76, 0] + assert all(s.execution.start_time is not None for s in steps) + assert all(s.execution.end_time is not None for s in steps)