Search code examples
pythonooppython-behave

Adding common attributes to a Behave method


Using the great Behave framework, but having trouble with my lack of OOP skills.

Behave has an inbuilt context namespace where objects can be shared between test execution steps. After initializing my WebDriver session, I keep passing it between my steps using this context to hold everything. Functionality is fine, but as you can see below, it is anything but DRY.

How/where can I add these attributes to the step_impl() or context once only?

environment.py

from selenium import webdriver

def before_feature(context, scenario):
    """Initialize WebDriver instance"""

    driver = webdriver.PhantomJS(service_args=service_args, desired_capabilities=dcap)

    """
    Do my login thing..
    """

    context.driver = driver
    context.wait = wait
    context.expected_conditions = expected_conditions
    context.xenv = env_data

steps.py

@given('that I have opened the blah page')
def step_impl(context):

    driver = context.driver
    wait = context.wait
    expected_conditions = context.expected_conditions
    xenv = context.xenv

    driver.get("http://domain.com")
    driver.find_element_by_link_text("blah").click()
    wait.until(expected_conditions.title_contains("Blah page"))

@given(u'am on the yada subpage')
def step_impl(context):
    driver = context.driver
    wait = context.wait
    expected_conditions = context.expected_conditions
    xenv = context.xenv

    if driver.title is not "MySubPage/":
        driver.get("http://domain.MySubPage/")
        wait.until(expected_conditions.title_contains("Blah | SubPage"))

@given(u'that I have gone to another page')
def step_impl(context):
    driver = context.driver
    wait = context.wait
    expected_conditions = context.expected_conditions
    xenv = context.xenv

    driver.get("http://domain.com/MyOtherPahge/")

Solution

  • First of all you can just skip this unpacking and use context attributes everywhere, like context.driver.get("http://domain.com")

    If you don't like it and you really want to have local variables you can use tuple unpacking to make code little better:

    import operator
    def example_step(context):
        driver, xenv = operator.attrgetter('driver', 'xenv')(context)
    

    You can factor out default list of attributes like that, but that makes the whole thing a little bit implicit:

    import operator
    
    def unpack(context, field_list=('driver', 'xenv')):
        return operator.attrgetter(*field_list)(context)
    
    def example_step(context):
        driver, xenv = unpack(context)
    

    If you still don't like that you can mangle with globals(). For example crate a function like that:

    def unpack(context, loc, field_list):
        for field in field_list:
            loc[field]  = getattr(context, field, None)
    

    And use it in your step:

    def example_step(context):
        unpack(context, globals(), ('driver', 'xenv'))
    
        # now you can use driver and xenv local variables
        driver.get('http://domain.com')
    

    This will reduce repetition in your code, but it is very implicit and could be dangerous. So it's not recommended to do it like that.

    I'd just use tuple unpacking. It is simple and explicit so won't cause additional errors.