Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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/")
like image 796
ljs.dev Avatar asked May 18 '14 19:05

ljs.dev


People also ask

How do you pass parameters in behave?

In the step implementation, we shall pass the parameter enclosed in {}. Also, the parameter is passed as one of the arguments to the implementation method. The output shows Shift is: day and Shift is: night printed. Here, the parameters day and night are passed from the step.

How do I run a specific feature file in behave?

You can run a feature file by using -i or --include flags and then the name of the feature file. For more information check the documentation for command line arguments. There's a lot of useful information hidden in their appendix section. NOTE: At the time I'm writing this it won't work with Python 3.6 and Behave 1.2.

What is feature file in behave?

Behave - Feature FilesFeature files which are created by the Business analyst or any project stakeholder and contains behaviour related use cases. Step Implementation file for the scenarios defined in the feature file.


3 Answers

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.

like image 195
Alex Shkop Avatar answered Sep 30 '22 09:09

Alex Shkop


You could define a decorator that 'unpacks' the context for you and passes the 'unpacked' values as arguments:

environment.py

def before_feature(context, feature):
    context.spam = 'spam'

def after_feature(context, feature):
    del context.spam

test.feature

Scenario: Test global env
  Then spam should be "spam"

step.py

def add_context_attrs(func):
    @functools.wraps(func)  # wrap it neatly
    def wrapper(context, *args, **kwargs):  # accept arbitrary args/kwargs
        kwargs['spam'] = context.spam  # unpack 'spam' and add it to the kwargs
        return func(context, *args, **kwargs)  # call the wrapped function
    return wrapper

@step('spam should be "{val}"')
@add_context_attrs
def assert_spam(context, val, spam):
    assert spam == val
like image 26
siebz0r Avatar answered Sep 30 '22 08:09

siebz0r


To stick to the DRY rule I usually use:
* Background stories: http://pythonhosted.org/behave/gherkin.html#background
* or Environmental controls: http://pythonhosted.org/behave/tutorial.html#environmental-controls

like image 29
kowalcj0 Avatar answered Sep 30 '22 08:09

kowalcj0