Unable to navigate to Dynamic Context Variables/Objects with Behave Python

I recently switched from Pycharm to Cursor and am now experiencing different behaviors when running behave tests using gherkin/cucumber syntax.

It appears that the way pycharm and vscode handle python functionality is different and vscode lacks a lot of features.

My tests are creating context variables in the before all/before features/ etc setup that have to be used throughout the tests and updated.

For example, i send the driver to initialize all my page objects since these tests are running in the UI across many different applications.

from e2e_threats.api_objects.alert_management_services.services import AlertManagementServiceManager
from e2e_threats.api_objects.aws import AWSServiceManager
from e2e_threats.api_objects.intelligence_center.services import IntelligenceCenterServiceManager
from e2e_threats.api_objects.notification_center.api_base_page import APIBasePage
from e2e_threats.api_objects.notification_center.live_feed import LiveFeedAPI
from e2e_threats.api_objects.notification_center.subscription import SubscriptionAPI
from e2e_threats.api_objects.notification_center.travel_briefs import TravelBriefAPI
from e2e_threats.page_objects.base_page import BasePage
from e2e_threats.page_objects.intelligence_center.alert import ICAlertPage
from e2e_threats.page_objects.intelligence_center.chat import ICChatPage
from e2e_threats.page_objects.intelligence_center.configuration import ICConfigurationPage
from e2e_threats.page_objects.intelligence_center.content_management import ICContentManagementPage
from e2e_threats.page_objects.intelligence_center.dashboard import ICDashboardPage
from e2e_threats.page_objects.intelligence_center.gator.column import GATORColumnPage
from e2e_threats.page_objects.intelligence_center.tiny_mce import TinyMCEPage
from e2e_threats.page_objects.notification_center.chat import NCChatPage
from e2e_threats.page_objects.notification_center.content.situation_reports import NCSituationReportPage
from e2e_threats.page_objects.notification_center.content.travel_briefs import NCTravelBriefPage
from e2e_threats.page_objects.notification_center.dashboard import NCDashboardPage
from e2e_threats.page_objects.notification_center.live_feed import NCLiveFeedPage
from e2e_threats.page_objects.notification_center.subscription import NCSubscriptionPage


class PageObjectsBank(object):

    def __init__(self, driver=None, environment=None):

        self.ams_services_page = AlertManagementServiceManager(environment)
        self.api_base_page = APIBasePage(driver)
        self.aws_api_page = AWSServiceManager(environment)
        self.base_page = BasePage(driver)
        self.ic_services_page = IntelligenceCenterServiceManager(environment)
        self.tiny_mce_page = TinyMCEPage(driver)

        self.ic_alert_page = ICAlertPage(driver)
        self.ic_chat_page = ICChatPage(driver)
        self.ic_configuration_page = ICConfigurationPage(driver)
        self.ic_content_management_page = ICContentManagementPage(driver)
        self.ic_dashboard_page = ICDashboardPage(driver)

        self.gator_column_page = GATORColumnPage(driver)

        self.nc_chat_page = NCChatPage(driver)
        self.nc_dashboard_page = NCDashboardPage(driver)
        self.nc_live_feed_api_page = LiveFeedAPI(driver)
        self.nc_live_feed_page = NCLiveFeedPage(driver)
        self.nc_situation_report_page = NCSituationReportPage(driver)
        self.nc_subscription_api_page = SubscriptionAPI(driver)
        self.nc_subscription_page = NCSubscriptionPage(driver)
        self.nc_travel_brief_api_page = TravelBriefAPI(driver, environment)
        self.nc_travel_brief_page = NCTravelBriefPage(driver)

def before_feature(c, feature):
    if 'skip' in feature.tags:
        feature.skip('Marked with @skip')
        return

    for scenario in feature.walk_scenarios():
        patch_scenario_with_autoretry(scenario, max_attempts=2)

    c.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=c.chrome_options)

    c.page_objects_bank = PageObjectsBank(driver=c.driver, environment=c.env)

    # Threat Warnings Setup
    c.page_objects_bank.ams_services_page.configure_host(c.env)
    c.redis_client = c.page_objects_bank.ams_services_page.setup_redis()
    c.intel_redis_client = c.page_objects_bank.ic_services_page.setup_intel_redis()
    c.ams_client = c.page_objects_bank.ams_services_page.initialize_ams_client()

    # clean up subscriptions in NC
    c.page_objects_bank.nc_subscription_api_page.cleanup_subscriptions(c)
    # clean up live feeds in NC
    c.page_objects_bank.nc_live_feed_api_page.cleanup_live_feed(c)

I used to be able to ctrl+click on the function that exists in the page object (c.page_objects_bank.ic_alert_page.clean_up), but now i cannot. *The tests run successfully in debugger/terminal since the context is initiated at runtime, but it’s when trying to navigate in the IDE where i experience issues making it really hard to write new/maintain tests.

@when('user selects "(.*)" on "(.*)" alerts')
def step_impl(c, clean_type, status):
    clean_type = validate_clean_type(clean_type)
    status = validate_alert_status(status)
    c.page_objects_bank.ic_alert_page.clean_up(clean_type, status)

Cursor described this issue as such:

The difference in how VSCode/Cursor and PyCharm handle dynamic vs. static objects comes down to their code analysis engines:

  1. Different Code Analysis Approaches:
  2. PyCharm uses a more sophisticated Python-specific code analysis engine that was built from the ground up for Python
  3. VSCode/Cursor use the Pylance/Pyright language server which, while powerful, has a different approach to tracking references
  4. Type Inference Capabilities:
  5. PyCharm has more advanced type inference for dynamic Python code
  6. PyCharm better understands Python’s dynamic nature, tracking object attributes across method calls and variable assignments
  7. Context Tracking:
  8. PyCharm maintains a more complete context model of your entire project
  9. It’s better at tracking the “behave” context object (c) and understanding what attributes are attached to it
  10. Index Depth and Completeness:
  11. PyCharm indexes your entire project more thoroughly by default
  12. VSCode/Cursor limit indexing depth to save resources, which is why we needed those python.analysis.packageIndexDepths settings
  13. Debugging and Navigation Integration:
  14. PyCharm’s debugger is more tightly integrated with its code understanding
  15. When debugging in PyCharm, it has better knowledge of object types at runtime and can use this to improve navigation
  16. Framework-Specific Intelligence:
  17. PyCharm has specific built-in understanding of popular frameworks (including testing frameworks)
  18. It understands patterns like the Page Object Model or Behave’s context passing better

The key reason PyCharm handles these dynamic objects better is that it was designed specifically as a Python IDE with years of refinement for Python’s dynamic nature. VSCode is a more general-purpose editor with Python support added through extensions, so it sometimes struggles with Python’s more dynamic patterns like those used in your test framework.
For VSCode/Cursor to better handle your code, you’d need either:

  1. More explicit type hints
  2. A more static design pattern
  3. Custom configuration like the indexing depth settings we discussed

Are there any plans to support this? Or something i can do differently?

I am using the Cucumber (Gherkin) Full Support, Python (anysphere), ms-python, Python Debugger plugins.

My settings.json file under .vscode:

{
    "editor.formatOnSave": true,
    "python.languageServer": "Default",
    "python.analysis.typeCheckingMode": "basic",
    "python.analysis.extraPaths": [
        "${workspaceFolder}"
    ],
    "python.defaultInterpreterPath": "${workspaceFolder}/venv/bin/python",
    "python.terminal.activateEnvironment": true,
    "editor.quickSuggestions": {
        "comments": false,
        "strings": true,
        "other": true
    },
    "cucumberautocomplete.steps": [
        "ams/steps/**/*.py",
        "e2e_threats/steps/**/*.py"
    ],
    "cucumberautocomplete.syncfeatures": "ams/features/**/*.feature,e2e_threats/features/**/*.feature",
    "cucumberautocomplete.strictGherkinCompletion": false,
    "cucumberautocomplete.strictGherkinValidation": false,
    "cucumberautocomplete.smartSnippets": true,
    "cucumberautocomplete.customParameters": [
        {
            "parameter": "(IC|NC|GATOR)",
            "value": "<app>"
        }
    ]
}