From bf637ba760e2bfe229511e36745b225970718c97 Mon Sep 17 00:00:00 2001 From: Yao Yao Date: Tue, 14 Jul 2026 09:45:14 +0000 Subject: [PATCH 1/2] feat: Auto-inject key frame data upon assertion failure Automatically captures key frame data (action, timestamp, details) upon any Mobly assertion failure and injects it into the extras dictionary to ensure it is captured in the test_summary.yaml file. --- mobly/asserts.py | 17 +++++++++++++++++ tests/mobly/base_test_test.py | 14 +++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/mobly/asserts.py b/mobly/asserts.py index e877e05f5..5d1b3fbb7 100644 --- a/mobly/asserts.py +++ b/mobly/asserts.py @@ -15,6 +15,7 @@ import re import unittest +from mobly import logger from mobly import signals # Have an instance of unittest.TestCase so we could reuse some logic @@ -23,6 +24,20 @@ _pyunit_proxy.maxDiff = None +def _add_key_frame(extras, action, details): + """Helper to inject key frame data into assertion extras.""" + key_frame_data = { + 'action': action, + 'timestamp': logger.get_log_line_timestamp(), + 'details': details, + } + if extras is None: + extras = {} + if isinstance(extras, dict): + extras['key_frame'] = key_frame_data + return extras + + def _call_unittest_assertion( assertion_method, *args, msg=None, extras=None, **kwargs ): @@ -47,6 +62,7 @@ def _call_unittest_assertion( # This raise statement is outside of the above except statement to # prevent Python3's exception message from having two tracebacks. if my_msg is not None: + extras = _add_key_frame(extras, assertion_method.__name__, msg) raise signals.TestFailure(my_msg, extras=extras) @@ -438,6 +454,7 @@ def fail(msg, extras=None): Raises: signals.TestFailure: Mark a test as failed. """ + extras = _add_key_frame(extras, 'fail', msg) raise signals.TestFailure(msg, extras) diff --git a/tests/mobly/base_test_test.py b/tests/mobly/base_test_test.py index f38913352..070f95684 100755 --- a/tests/mobly/base_test_test.py +++ b/tests/mobly/base_test_test.py @@ -585,7 +585,7 @@ def test_something(self): self.assertEqual(actual_record.test_name, self.mock_test_name) self.assertEqual(actual_record.details, MSG_EXPECTED_EXCEPTION) self.assertTrue(actual_record.end_time) - self.assertIsNone(actual_record.extras) + self.assertIn('key_frame', actual_record.extras) expected_summary = ( 'Error 1, Executed 1, Failed 0, Passed 0, Requested 1, Skipped 0' ) @@ -626,7 +626,7 @@ def test_something(self): actual_record = bt_cls.results.error[0] self.assertEqual(actual_record.test_name, self.mock_test_name) self.assertEqual(actual_record.details, MSG_EXPECTED_EXCEPTION) - self.assertIsNone(actual_record.extras) + self.assertIn('key_frame', actual_record.extras) self.assertFalse(actual_record.extra_errors) self.assertTrue(actual_record.end_time) expected_summary = ( @@ -761,7 +761,7 @@ def test_something(self): actual_record = bt_cls.results.failed[0] self.assertEqual(actual_record.test_name, self.mock_test_name) self.assertEqual(actual_record.details, MSG_EXPECTED_EXCEPTION) - self.assertIsNone(actual_record.extras) + self.assertIn('key_frame', actual_record.extras) expected_summary = ( 'Error 0, Executed 1, Failed 1, Passed 0, Requested 1, Skipped 0' ) @@ -935,7 +935,7 @@ def test_something(self): self.assertIn('on_fail', actual_record.extra_errors) self.assertEqual(actual_record.test_name, self.mock_test_name) self.assertEqual(actual_record.details, MSG_EXPECTED_EXCEPTION) - self.assertIsNone(actual_record.extras) + self.assertIn('key_frame', actual_record.extras) expected_summary = ( 'Error 0, Executed 1, Failed 1, Passed 0, Requested 1, Skipped 0' ) @@ -985,7 +985,7 @@ def test_something(self): actual_record.extra_errors['teardown_test'].details, MSG_EXPECTED_EXCEPTION, ) - self.assertIsNone(actual_record.extra_errors['teardown_test'].extras) + self.assertIn('key_frame', actual_record.extra_errors['teardown_test'].extras) expected_summary = ( 'Error 1, Executed 1, Failed 0, Passed 0, Requested 1, Skipped 0' ) @@ -1059,7 +1059,7 @@ def test_something(self): actual_record.extra_errors['teardown_test'].details, MSG_EXPECTED_EXCEPTION, ) - self.assertIsNone(actual_record.extra_errors['teardown_test'].extras) + self.assertIn('key_frame', actual_record.extra_errors['teardown_test'].extras) expected_summary = ( 'Error 1, Executed 1, Failed 0, Passed 0, Requested 1, Skipped 0' ) @@ -2006,7 +2006,7 @@ def test_func(self): actual_record = bt_cls.results.failed[0] self.assertEqual(actual_record.test_name, 'test_func') self.assertEqual(actual_record.details, 'failed from assert_true') - self.assertIsNone(actual_record.extras) + self.assertIn('key_frame', actual_record.extras) def test_unpack_userparams_required(self): """Missing a required param should raise an error.""" From 52944b3cb5c5ecea7e0360ac83cb8ca62e409d40 Mon Sep 17 00:00:00 2001 From: Yao Yao Date: Tue, 14 Jul 2026 09:53:00 +0000 Subject: [PATCH 2/2] feat: Add event_type: key_frame --- mobly/asserts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mobly/asserts.py b/mobly/asserts.py index 5d1b3fbb7..014847bf8 100644 --- a/mobly/asserts.py +++ b/mobly/asserts.py @@ -27,6 +27,7 @@ def _add_key_frame(extras, action, details): """Helper to inject key frame data into assertion extras.""" key_frame_data = { + 'event_type': 'key_frame', 'action': action, 'timestamp': logger.get_log_line_timestamp(), 'details': details,