From d46ac5c37e89600f836342226fe73c8cbef2be59 Mon Sep 17 00:00:00 2001 From: Alexey Katsman Date: Thu, 11 Dec 2025 19:52:09 +0100 Subject: [PATCH] PY-86174 Fix behave step definitions detection in nested directories Well, here's the thing. We clear step registry after dry run (because we'll reload steps again after running behave for real). Technically we run behave twice, one for dry run (to get the number of tests and some context) and then we invoke their runner again. Here's how behave loads step definitions. It creates a decorator for each step type, then under the hood it compiles all the python scripts with steps (in steps directory) and executes the code with definitions of those decorators as context to execute the script. When we don't have subfolders, it all works as expected. But here's a twist with the subfolder. We now have an import for a subfolder script, but the problem is that sys.modules lives globally for the whole lifecycle of our runner. So when we execute the same script second time, we only go through the import statement, we see that our module is already loaded, and hence don't run the nested steps script, missing the steps from there. The fix involves clearing the modules that were loaded during the dry run, so that we can reimport modules with steps in nested directories. GitOrigin-RevId: 9a6e77819aa0ba5d5789049034f2c0e2af4f3b2c --- python/helpers/pycharm/behave_runner.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/helpers/pycharm/behave_runner.py b/python/helpers/pycharm/behave_runner.py index 2edaf6515a95..1d811ebe5e8e 100644 --- a/python/helpers/pycharm/behave_runner.py +++ b/python/helpers/pycharm/behave_runner.py @@ -235,8 +235,17 @@ class _BehaveRunner(_bdd_utils.BddRunner): return isinstance(expected_tags, TagExpression) and expected_tags.check(scenario.tags) def _get_features_to_run(self): + old_modules = sys.modules.copy() self.__real_runner.dry_run = True self.__real_runner.run() + # During the dry run we can import some modules with steps in nested + # directories. And since we then clear step registry, there's no way to + # get those steps back without reimport. So we clear up the modules that + # were imported during the dry run to support such scenario. + new_modules = sys.modules.copy() + for module in new_modules.keys(): + if module not in old_modules: + del sys.modules[module] features_to_run = self.__real_runner.features self.__real_runner.clean() # To make sure nothing left after dry run