Refactoring

This commit is contained in:
Michael Mintz 2023-05-03 14:01:44 -04:00
parent dc6fb95b68
commit 3c260affd0
7 changed files with 214 additions and 58 deletions

View File

@ -10,6 +10,7 @@ from seleniumbase.fixtures import constants
python3_11_or_newer = False python3_11_or_newer = False
if sys.version_info >= (3, 11): if sys.version_info >= (3, 11):
python3_11_or_newer = True python3_11_or_newer = True
py311_patch2 = constants.PatchPy311.PATCH
def log_screenshot(test_logpath, driver, screenshot=None, get=False): def log_screenshot(test_logpath, driver, screenshot=None, get=False):
@ -79,7 +80,11 @@ def get_master_time():
def get_browser_version(driver): def get_browser_version(driver):
if python3_11_or_newer and hasattr(sb_config, "_browser_version"): if (
python3_11_or_newer
and py311_patch2
and hasattr(sb_config, "_browser_version")
):
return sb_config._browser_version return sb_config._browser_version
driver_capabilities = driver.capabilities driver_capabilities = driver.capabilities
if "version" in driver_capabilities: if "version" in driver_capabilities:
@ -187,6 +192,25 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None):
traceback_list = traceback.format_list( traceback_list = traceback.format_list(
traceback.extract_tb(traceback_address)[1:] traceback.extract_tb(traceback_address)[1:]
) )
updated_list = []
counter = 0
for traceback_item in traceback_list:
if "self._callTestMethod(testMethod)" in traceback_item:
counter = 1
updated_list.append(traceback_item) # In case not cleared
continue
elif (
", in _callTestMethod" in traceback_item.strip()
and "method()" in traceback_item.strip()
and counter == 1
):
counter = 0
updated_list = []
continue
else:
counter = 0
updated_list.append(traceback_item)
traceback_list = updated_list
traceback_message = "".join(traceback_list).strip() traceback_message = "".join(traceback_list).strip()
except Exception: except Exception:
exc_message = "(Unknown Exception)" exc_message = "(Unknown Exception)"
@ -201,13 +225,17 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None):
if sb_config.behave_step.error_message: if sb_config.behave_step.error_message:
traceback_message = sb_config.behave_step.error_message traceback_message = sb_config.behave_step.error_message
else: else:
traceback_message = "".join( format_exception = traceback.format_exception(
traceback.format_exception( sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
) )
if format_exception:
updated_list = []
for line in format_exception:
if "sb_manager.py" in line and "yield sb" in line:
continue
updated_list.append(line)
format_exception = updated_list
traceback_message = "".join(format_exception)
if ( if (
not traceback_message not traceback_message
or len(str(traceback_message)) < 30 or len(str(traceback_message)) < 30

View File

@ -77,6 +77,7 @@ from seleniumbase.fixtures import js_utils
from seleniumbase.fixtures import page_actions from seleniumbase.fixtures import page_actions
from seleniumbase.fixtures import page_utils from seleniumbase.fixtures import page_utils
from seleniumbase.fixtures import shared_utils from seleniumbase.fixtures import shared_utils
from seleniumbase.fixtures import unittest_helper
from seleniumbase.fixtures import xpath_to_css from seleniumbase.fixtures import xpath_to_css
__all__ = ["BaseCase"] __all__ = ["BaseCase"]
@ -91,6 +92,7 @@ if sys.platform in ["win32", "win64", "x64"]:
python3_11_or_newer = False python3_11_or_newer = False
if sys.version_info >= (3, 11): if sys.version_info >= (3, 11):
python3_11_or_newer = True python3_11_or_newer = True
py311_patch2 = constants.PatchPy311.PATCH
selenium4_or_newer = False selenium4_or_newer = False
if sys.version_info >= (3, 7): if sys.version_info >= (3, 7):
selenium4_or_newer = True selenium4_or_newer = True
@ -307,7 +309,6 @@ class BaseCase(unittest.TestCase):
pass # Odd issue where the open did happen. Continue. pass # Odd issue where the open did happen. Continue.
else: else:
raise raise
unittest.has_exception = False
if ( if (
self.undetectable self.undetectable
or ( or (
@ -4120,7 +4121,6 @@ class BaseCase(unittest.TestCase):
and settings.SKIP_JS_WAITS and settings.SKIP_JS_WAITS
): ):
time.sleep(0.05) time.sleep(0.05)
unittest.has_exception = False
return True return True
def wait_for_angularjs(self, timeout=None, **kwargs): def wait_for_angularjs(self, timeout=None, **kwargs):
@ -6362,14 +6362,12 @@ class BaseCase(unittest.TestCase):
) )
if type(page) is int: if type(page) is int:
if text not in pdf_text: if text not in pdf_text:
unittest.has_exception = True
raise Exception( raise Exception(
"PDF [%s] is missing expected text [%s] on " "PDF [%s] is missing expected text [%s] on "
"page [%s]!" % (pdf, text, page) "page [%s]!" % (pdf, text, page)
) )
else: else:
if text not in pdf_text: if text not in pdf_text:
unittest.has_exception = True
raise Exception( raise Exception(
"PDF [%s] is missing expected text [%s]!" % (pdf, text) "PDF [%s] is missing expected text [%s]!" % (pdf, text)
) )
@ -6495,7 +6493,19 @@ class BaseCase(unittest.TestCase):
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
except Exception: except Exception:
shared_utils.pip_install("Pillow") shared_utils.pip_install("Pillow")
from PIL import Image, ImageDraw try:
from PIL import Image, ImageDraw
except Exception as e:
if (
sys.version_info >= (3, 12)
and "symbol not found" in e.msg
):
raise Exception(
"PIL / Pillow is not supported on Python %s"
% ".".join(str(s) for s in sys.version_info)
)
else:
raise
text_rows = overlay_text.split("\n") text_rows = overlay_text.split("\n")
len_text_rows = len(text_rows) len_text_rows = len(text_rows)
max_width = 0 max_width = 0
@ -6744,44 +6754,32 @@ class BaseCase(unittest.TestCase):
def assert_true(self, expr, msg=None): def assert_true(self, expr, msg=None):
"""Asserts that the expression is True. """Asserts that the expression is True.
Will raise an exception if the statement if False.""" Will raise an exception if the statement if False."""
unittest.has_exception = True
self.assertTrue(expr, msg=msg) self.assertTrue(expr, msg=msg)
unittest.has_exception = False
def assert_false(self, expr, msg=None): def assert_false(self, expr, msg=None):
"""Asserts that the expression is False. """Asserts that the expression is False.
Will raise an exception if the statement if True.""" Will raise an exception if the statement if True."""
unittest.has_exception = True
self.assertFalse(expr, msg=msg) self.assertFalse(expr, msg=msg)
unittest.has_exception = False
def assert_equal(self, first, second, msg=None): def assert_equal(self, first, second, msg=None):
"""Asserts that the two values are equal. """Asserts that the two values are equal.
Will raise an exception if the values are not equal.""" Will raise an exception if the values are not equal."""
unittest.has_exception = True
self.assertEqual(first, second, msg=msg) self.assertEqual(first, second, msg=msg)
unittest.has_exception = False
def assert_not_equal(self, first, second, msg=None): def assert_not_equal(self, first, second, msg=None):
"""Asserts that the two values are not equal. """Asserts that the two values are not equal.
Will raise an exception if the values are equal.""" Will raise an exception if the values are equal."""
unittest.has_exception = True
self.assertNotEqual(first, second, msg=msg) self.assertNotEqual(first, second, msg=msg)
unittest.has_exception = False
def assert_in(self, first, second, msg=None): def assert_in(self, first, second, msg=None):
"""Asserts that the first string is in the second string. """Asserts that the first string is in the second string.
Will raise an exception if the first string is not in the second.""" Will raise an exception if the first string is not in the second."""
unittest.has_exception = True
self.assertIn(first, second, msg=msg) self.assertIn(first, second, msg=msg)
unittest.has_exception = False
def assert_not_in(self, first, second, msg=None): def assert_not_in(self, first, second, msg=None):
"""Asserts that the first string is not in the second string. """Asserts that the first string is not in the second string.
Will raise an exception if the first string is in the second string.""" Will raise an exception if the first string is in the second string."""
unittest.has_exception = True
self.assertNotIn(first, second, msg=msg) self.assertNotIn(first, second, msg=msg)
unittest.has_exception = False
def assert_raises(self, *args, **kwargs): def assert_raises(self, *args, **kwargs):
"""Asserts that the following block of code raises an exception. """Asserts that the following block of code raises an exception.
@ -6900,11 +6898,9 @@ class BaseCase(unittest.TestCase):
self.wait_for_ready_state_complete() self.wait_for_ready_state_complete()
time.sleep(2) time.sleep(2)
actual = self.get_page_title().strip() actual = self.get_page_title().strip()
unittest.has_exception = True
self.assertEqual( self.assertEqual(
expected, actual, error % (expected, actual) expected, actual, error % (expected, actual)
) )
unittest.has_exception = False
if self.demo_mode and not self.recorder_mode: if self.demo_mode and not self.recorder_mode:
a_t = "ASSERT TITLE" a_t = "ASSERT TITLE"
if self._language != "English": if self._language != "English":
@ -6949,11 +6945,9 @@ class BaseCase(unittest.TestCase):
self.wait_for_ready_state_complete() self.wait_for_ready_state_complete()
time.sleep(2) time.sleep(2)
actual = self.get_page_title().strip() actual = self.get_page_title().strip()
unittest.has_exception = True
self.assertIn( self.assertIn(
expected, actual, error % (expected, actual) expected, actual, error % (expected, actual)
) )
unittest.has_exception = False
if self.demo_mode and not self.recorder_mode: if self.demo_mode and not self.recorder_mode:
a_t = "ASSERT TITLE CONTAINS" a_t = "ASSERT TITLE CONTAINS"
if self._language != "English": if self._language != "English":
@ -6988,9 +6982,7 @@ class BaseCase(unittest.TestCase):
self.wait_for_ready_state_complete() self.wait_for_ready_state_complete()
time.sleep(2) time.sleep(2)
actual = self.get_current_url().strip() actual = self.get_current_url().strip()
unittest.has_exception = True
self.assertEqual(expected, actual, error % (expected, actual)) self.assertEqual(expected, actual, error % (expected, actual))
unittest.has_exception = False
if self.demo_mode and not self.recorder_mode: if self.demo_mode and not self.recorder_mode:
a_u = "ASSERT URL" a_u = "ASSERT URL"
if self._language != "English": if self._language != "English":
@ -7028,9 +7020,7 @@ class BaseCase(unittest.TestCase):
self.wait_for_ready_state_complete() self.wait_for_ready_state_complete()
time.sleep(2) time.sleep(2)
actual = self.get_current_url().strip() actual = self.get_current_url().strip()
unittest.has_exception = True
self.assertIn(expected, actual, error % (expected, actual)) self.assertIn(expected, actual, error % (expected, actual))
unittest.has_exception = False
if self.demo_mode and not self.recorder_mode: if self.demo_mode and not self.recorder_mode:
a_u = "ASSERT URL CONTAINS" a_u = "ASSERT URL CONTAINS"
if self._language != "English": if self._language != "English":
@ -7127,7 +7117,6 @@ class BaseCase(unittest.TestCase):
er_str = str(errors) er_str = str(errors)
er_str = er_str.replace("[{", "[\n{").replace("}, {", "},\n{") er_str = er_str.replace("[{", "[\n{").replace("}, {", "},\n{")
current_url = self.get_current_url() current_url = self.get_current_url()
unittest.has_exception = True
raise Exception( raise Exception(
"JavaScript errors found on %s => %s" % (current_url, er_str) "JavaScript errors found on %s => %s" % (current_url, er_str)
) )
@ -7683,9 +7672,7 @@ class BaseCase(unittest.TestCase):
def fail(self, msg=None): def fail(self, msg=None):
"""Fail immediately, with the given message.""" """Fail immediately, with the given message."""
unittest.has_exception = True
super().fail(msg) super().fail(msg)
raise self.failureException(msg)
def skip(self, reason=""): def skip(self, reason=""):
"""Mark the test as Skipped.""" """Mark the test as Skipped."""
@ -9463,7 +9450,10 @@ class BaseCase(unittest.TestCase):
raise VisualException(minified_exception) raise VisualException(minified_exception)
def _process_visual_baseline_logs(self): def _process_visual_baseline_logs(self):
if not (python3_11_or_newer or "--pdb" in sys.argv): if not (
(python3_11_or_newer and py311_patch2)
or "--pdb" in sys.argv
):
return return
self.__process_visual_baseline_logs() self.__process_visual_baseline_logs()
@ -9837,7 +9827,6 @@ class BaseCase(unittest.TestCase):
def __check_scope(self): def __check_scope(self):
if hasattr(self, "browser"): # self.browser stores the type of browser if hasattr(self, "browser"): # self.browser stores the type of browser
unittest.has_exception = False
return # All good: setUp() already initialized variables in "self" return # All good: setUp() already initialized variables in "self"
else: else:
message = ( message = (
@ -9853,7 +9842,6 @@ class BaseCase(unittest.TestCase):
"\n variables, which are initialized during the setUp() method" "\n variables, which are initialized during the setUp() method"
"\n that runs automatically before all tests called by pytest." "\n that runs automatically before all tests called by pytest."
) )
unittest.has_exception = True
raise OutOfScopeException(message) raise OutOfScopeException(message)
############ ############
@ -10109,7 +10097,6 @@ class BaseCase(unittest.TestCase):
if print_only: if print_only:
print(exception_output) print(exception_output)
else: else:
unittest.has_exception = True
raise Exception(exception_output.replace("\\n", "\n")) raise Exception(exception_output.replace("\\n", "\n"))
############ ############
@ -13408,11 +13395,9 @@ class BaseCase(unittest.TestCase):
"""This method runs before every test begins. """This method runs before every test begins.
Be careful if a subclass of BaseCase overrides setUp(). Be careful if a subclass of BaseCase overrides setUp().
If so, add the following line to the subclass setUp() method: If so, add the following line to the subclass setUp() method:
super().setUp() super().setUp() """
"""
if not hasattr(self, "_using_sb_fixture") and self.__called_setup: if not hasattr(self, "_using_sb_fixture") and self.__called_setup:
# This test already called setUp() return # This test already called setUp()
return
self.__called_setup = True self.__called_setup = True
self.__called_teardown = False self.__called_teardown = False
self.masterqa_mode = masterqa_mode self.masterqa_mode = masterqa_mode
@ -13902,8 +13887,6 @@ class BaseCase(unittest.TestCase):
# Some actions such as hover-clicking are different on mobile. # Some actions such as hover-clicking are different on mobile.
self.mobile_emulator = False self.mobile_emulator = False
unittest.has_exception = False
# Configure the test time limit (if used). # Configure the test time limit (if used).
self.set_time_limit(self.time_limit) self.set_time_limit(self.time_limit)
@ -14080,7 +14063,10 @@ class BaseCase(unittest.TestCase):
self.testcase_manager.update_testcase_data(data_payload) self.testcase_manager.update_testcase_data(data_payload)
def _add_pytest_html_extra(self): def _add_pytest_html_extra(self):
if not (python3_11_or_newer or "--pdb" in sys.argv): if not (
(python3_11_or_newer and py311_patch2)
or "--pdb" in sys.argv
):
return return
self.__add_pytest_html_extra() self.__add_pytest_html_extra()
@ -14188,8 +14174,6 @@ class BaseCase(unittest.TestCase):
has_exception = sys.exc_info()[1] is not None has_exception = sys.exc_info()[1] is not None
if self.__will_be_skipped and hasattr(self, "_using_sb_fixture"): if self.__will_be_skipped and hasattr(self, "_using_sb_fixture"):
has_exception = False has_exception = False
if python3_11_or_newer and unittest.has_exception:
has_exception = True
return has_exception return has_exception
def __get_test_id(self): def __get_test_id(self):
@ -14731,7 +14715,7 @@ class BaseCase(unittest.TestCase):
if ( if (
self.__has_exception() self.__has_exception()
or self.save_screenshot_after_test or self.save_screenshot_after_test
or python3_11_or_newer or (python3_11_or_newer and py311_patch2)
or "--pdb" in sys.argv or "--pdb" in sys.argv
): ):
self.__set_last_page_screenshot() self.__set_last_page_screenshot()
@ -14742,7 +14726,10 @@ class BaseCase(unittest.TestCase):
self.__add_pytest_html_extra() self.__add_pytest_html_extra()
def _log_fail_data(self): def _log_fail_data(self):
if not (python3_11_or_newer or "--pdb" in sys.argv): if not (
(python3_11_or_newer and py311_patch2)
or "--pdb" in sys.argv
):
return return
test_id = self.__get_test_id() test_id = self.__get_test_id()
test_logpath = os.path.join(self.log_path, test_id) test_logpath = os.path.join(self.log_path, test_id)
@ -14801,12 +14788,102 @@ class BaseCase(unittest.TestCase):
else: else:
return None return None
def _addSkip(self, result, test_case, reason):
"""This method should NOT be called directly from tests.
(It will be called AUTOMATICALLY as needed.)"""
addSkip = getattr(result, 'addSkip', None)
if addSkip is not None:
addSkip(test_case, reason)
else:
import warnings
warnings.warn(
"TestResult has no addSkip method! Skips not reported!",
RuntimeWarning, 2
)
result.addSuccess(test_case)
def _callTestMethod(self, method):
"""This method should NOT be called directly from tests.
(It will be called AUTOMATICALLY as needed.)"""
method()
def run(self, result=None):
"""Overwrite the unittest run() method for Python 3.11 or newer.
This method should NOT be called directly from tests.
(It will be called AUTOMATICALLY as needed.)"""
if not python3_11_or_newer:
return super().run(result=result)
if result is None:
result = self.defaultTestResult()
startTestRun = getattr(result, 'startTestRun', None)
stopTestRun = getattr(result, 'stopTestRun', None)
if startTestRun is not None:
startTestRun()
else:
stopTestRun = None
result.startTest(self)
try:
testMethod = getattr(self, self._testMethodName)
if (
getattr(self.__class__, "__unittest_skip__", False)
or getattr(testMethod, "__unittest_skip__", False)
):
skip_why = (
getattr(self.__class__, '__unittest_skip_why__', '')
or getattr(testMethod, '__unittest_skip_why__', '')
)
self._addSkip(result, self, skip_why)
return result
expecting_failure = (
getattr(self, "__unittest_expecting_failure__", False)
or getattr(testMethod, "__unittest_expecting_failure__", False)
)
outcome = unittest_helper._Outcome(result)
try:
self._outcome = outcome
with outcome.testPartExecutor(self):
self._callSetUp()
if outcome.success:
outcome.expecting_failure = expecting_failure
with outcome.testPartExecutor(self, isTest=True):
self._callTestMethod(testMethod)
outcome.expecting_failure = False
with outcome.testPartExecutor(self):
self._callTearDown()
self.doCleanups()
for test, reason in outcome.skipped:
self._addSkip(result, test, reason)
for test, exc_info in outcome.errors:
if exc_info is not None:
if issubclass(exc_info[0], self.failureException):
result.addFailure(test, exc_info)
else:
result.addError(test, exc_info)
if outcome.success:
if expecting_failure:
if outcome.expectedFailure:
self._addExpectedFailure(
result, outcome.expectedFailure
)
else:
self._addUnexpectedSuccess(result)
else:
result.addSuccess(self)
return result
finally:
outcome.errors.clear()
outcome.expectedFailure = None
self._outcome = None
finally:
result.stopTest(self)
if stopTestRun is not None:
stopTestRun()
def tearDown(self): def tearDown(self):
"""This method runs after every test completes. """This method runs after every test completes.
Be careful if a subclass of BaseCase overrides setUp(). Be careful if a subclass of BaseCase overrides setUp().
If so, add the following line to the subclass's tearDown() method: If so, add the following line to the subclass's tearDown() method:
super().tearDown() super().tearDown() """
"""
if not hasattr(self, "_using_sb_fixture") and self.__called_teardown: if not hasattr(self, "_using_sb_fixture") and self.__called_teardown:
# This test already called tearDown() # This test already called tearDown()
return return
@ -14900,7 +14977,10 @@ class BaseCase(unittest.TestCase):
self.__add_pytest_html_extra() self.__add_pytest_html_extra()
sb_config._has_logs = True sb_config._has_logs = True
elif ( elif (
(python3_11_or_newer or "--pdb" in sys.argv) (
(python3_11_or_newer and py311_patch2)
or "--pdb" in sys.argv
)
and not has_exception and not has_exception
): ):
# Handle a bug where exceptions aren't seen # Handle a bug where exceptions aren't seen

View File

@ -41,6 +41,13 @@ class ValidEnvs:
] ]
class PatchPy311:
# Now that unittest is "patched/fixed" in Python 3.11 and up,
# this second patch might not be needed to fix error-handling.
# Enabling this might slow things slightly to fix some things.
PATCH = False
class PageLoadStrategy: class PageLoadStrategy:
# Usage Example => "--pls=none" # Usage Example => "--pls=none"
NORMAL = "normal" NORMAL = "normal"

View File

@ -23,7 +23,6 @@ By.PARTIAL_LINK_TEXT # "partial link text"
import codecs import codecs
import os import os
import time import time
import unittest
from selenium.common.exceptions import ElementNotInteractableException from selenium.common.exceptions import ElementNotInteractableException
from selenium.common.exceptions import ElementNotVisibleException from selenium.common.exceptions import ElementNotVisibleException
from selenium.common.exceptions import NoAlertPresentException from selenium.common.exceptions import NoAlertPresentException
@ -225,7 +224,6 @@ def hover_element(driver, element):
def timeout_exception(exception, message): def timeout_exception(exception, message):
unittest.has_exception = True
exc, msg = shared_utils.format_exc(exception, message) exc, msg = shared_utils.format_exc(exception, message)
raise exc(msg) raise exc(msg)

View File

@ -0,0 +1,41 @@
import sys
from contextlib import contextmanager
from unittest.case import _ShouldStop, SkipTest
class _Outcome(object):
def __init__(self, result=None):
self.expecting_failure = False
self.result = result
self.result_supports_subtests = hasattr(result, "addSubTest")
self.success = True
self.skipped = []
self.expectedFailure = None
self.errors = []
@contextmanager
def testPartExecutor(self, test_case, isTest=False):
old_success = self.success
self.success = True
try:
yield
except KeyboardInterrupt:
raise
except SkipTest as e:
self.success = False
self.skipped.append((test_case, str(e)))
except _ShouldStop:
pass
except Exception:
exc_info = sys.exc_info()
if self.expecting_failure:
self.expectedFailure = exc_info
else:
self.success = False
self.errors.append((test_case, exc_info))
exc_info = None
else:
if self.result_supports_subtests and self.success:
self.errors.append((test_case, None))
finally:
self.success = self.success and old_success

View File

@ -13,6 +13,7 @@ from seleniumbase.fixtures import constants
python3_11_or_newer = False python3_11_or_newer = False
if sys.version_info >= (3, 11): if sys.version_info >= (3, 11):
python3_11_or_newer = True python3_11_or_newer = True
py311_patch2 = constants.PatchPy311.PATCH = True
class Base(Plugin): class Base(Plugin):
@ -276,7 +277,7 @@ class Base(Plugin):
test, self.test_count, self.duration test, self.test_count, self.duration
) )
) )
if python3_11_or_newer: if python3_11_or_newer and py311_patch2:
# Handle a bug on Python 3.11 where exceptions aren't seen # Handle a bug on Python 3.11 where exceptions aren't seen
sb_config._browser_version = None sb_config._browser_version = None
try: try:

View File

@ -15,6 +15,7 @@ if sys.platform in ["win32", "win64", "x64"]:
python3_11_or_newer = False python3_11_or_newer = False
if sys.version_info >= (3, 11): if sys.version_info >= (3, 11):
python3_11_or_newer = True python3_11_or_newer = True
py311_patch2 = constants.PatchPy311.PATCH
sys_argv = sys.argv sys_argv = sys.argv
pytest_plugins = ["pytester"] # Adds the "testdir" fixture pytest_plugins = ["pytester"] # Adds the "testdir" fixture
@ -1876,7 +1877,7 @@ def pytest_runtest_teardown(item):
if ( if (
( (
sb_config._has_exception sb_config._has_exception
or python3_11_or_newer or (python3_11_or_newer and py311_patch2)
or "--pdb" in sys_argv or "--pdb" in sys_argv
) )
and sb_config.list_fp and sb_config.list_fp
@ -2209,7 +2210,7 @@ def pytest_runtest_makereport(item, call):
sb_config._extra_dash_entries.append(test_id) sb_config._extra_dash_entries.append(test_id)
elif ( elif (
sb_config._sbase_detected sb_config._sbase_detected
and (python3_11_or_newer or "--pdb" in sys_argv) and ((python3_11_or_newer and py311_patch2) or "--pdb" in sys_argv)
and (report.outcome == "failed" or "AssertionError" in str(call)) and (report.outcome == "failed" or "AssertionError" in str(call))
and not sb_config._has_exception and not sb_config._has_exception
): ):