pytest-dev / pytest

The pytest framework makes it easy to write small tests, yet scales to support complex functional testing
https://pytest.org
MIT License
12.09k stars 2.68k forks source link

Fixture that calls getattr() on request.function throws an AttributeError: 'function' object has no attribute #1805

Closed arosszay closed 8 years ago

arosszay commented 8 years ago

Hello good people of pytest!

I have a major issue when trying to introspect the requesting test function from within a fixture. I am able to do the introspection successfully when doing it on the requesting test module, but NOT on the test function. I'd like for my fixture functions to be able to get data from my test functions.

Currently, I receive the following error: > x = getattr(request.function, "url") E AttributeError: 'function' object has no attribute 'url'

I've tried a couple things, like making the url variable in the test function to be nonlocal or global, using the concept of a closure(), ...all to no avail. I can't seem to understand how to make the fixture understand that yes, the 'function' object does indeed have an attribute 'url'.

I am using python 3.5.1 installed via the Anaconda 3 package on a Windows 10 machine. I run my code within Pycharm 2016.1.2 using pytest 2.9.1. I do not use a python virtual env.

Would a kind soul over here be able to help me with my situation? I seem to be stuck :/

Thanks!

Here's the fixture I wrote in a conftest.py file: @pytest.fixture(scope="function") def navigate_to(request): """Fixture used to determine the correct url to use when test execution starts""" x = getattr(request.function, "url") return x

Here's my test class including the test function I'm trying to make work _(NOTE: my fixture 'login_and_nav' takes in the 'navigateto' fixture above as a parameter): @pytest.mark.usefixtures('login_and_nav') class TestModuleNav(object): """Module Navigation Panel Selenium Tests."""

@pytest.mark.smoke
def test_diagnostics_module_loads(self):
    """Verifies the Diagnostics Module loads successfully."""

    url = URL.base_url
    login_credentials = Users.auto8, Client.demo_site, Provider=None

    # Verify the Home Page loaded successfully after login
    assert HomePage()
    # Verify the Module Navigation Pane is correct
    module_nav = ModuleNav()
    # Navigate to the Diagnostics Module
    diagnostics_module = module_nav.go_to_diagnostics()
    # Verify the Diagnostics Module is correct
    assert diagnostics_module.check_elements_visible(diagnostics_module.page_visible_element_list)`
The-Compiler commented 8 years ago

That's just not how Python works :wink:

request.function is the function object, and an attribute on it isn't the same thing as a local:

def fun():
    x = 42

print(fun.x)  # AttributeError
fun.x = 23
print(fun.x)  # 23, nothing to do with `x` inside the function

Why do you need to do this?

arosszay commented 8 years ago

I'm not sure I need to do this in order to accomplish what I want. Perhaps it's more accurate to ask why I want to do this? The reason why is because I have several test functions within a test class. For most of the tests, I want to begin the tests from a certain URL, however for a couple of the other ones I want to begin the test from a different URL. Additionally, I need to use different login credentials for the different tests because they are testing different things that I require different login credentials in order to test correctly.

I am trying to understand pytest as best I can in order to help me develop the best Selenium automation framework I can build! So because of this, I'm reading in the official pytest docs the following line:

Fixture function can accept the request object to introspect the “requesting” test function, class or module context.

Assuming I'm reading the truth, I went ahead and tried to utilize the ability of a fixture function to accept the request object to introspect the requesting test function. This didn't work, so that's why I'm asking the question.

I know I can introspect the requesting test module...this works on my machine. However, that convention does not make it easy to manage the specific urls and login credentials that need to be changed PER test within a single test module.

What do you think I should do? It would be really awesome if my test functions could communicate with the fixture functions to tell them what URL and login credentials to use when setting up the tests.

Here's the main fixture function I use to setup the tests:

@pytest.yield_fixture

def login_and_nav(request, navigate_to, login_with):

"""Fixture used to login and navigate to the page best suited for the test to begin conducting verifications."""

driver = Browser()

driver.maximize()

# Navigate to test url

driver.go_to_url(navigate_to)

# Login page will be displayed when user is not logged in, so we need to login at this step
login_page = LoginPage()
yield login_page.login(login_with)

print("Teardown fixture function is executed.")

if request.node.rep_call.failed:
    print("Test FAILED. Name of test = {}. \n".format(request.node.nodeid))
    driver.quit()
    driver.__init__()
elif request.node.rep_call.passed:
    print("Test PASSED. Name of test = {}. \n".format(request.node.nodeid))

`

NOTE: I'm really sorry, but I am having the most difficult and frustrating time figuring out how to correctly insert code in this text editor. I wish I could have it display for you all nice and everything, but I'm sorry I can't figure out how to do it as nice as you seem to have been able to do. Please pardon the lack of proper indentation throughout the entire stream of code inserts, hopefully you can still follow along and help!

The-Compiler commented 8 years ago

First of all, are you aware of the existing plugins? There's pytest-selenium and pytest-splinter already :wink:

As for your issue, the usual thing to do is to use a marker like @pytest.mark.base_url('http://www.example.com/') and then getting that via request.node.get_marker('base_url') if I remember correctly.

To format code, you can use triple-backticks on their own line before/after the code. You can also simply select all lines and press the <> button at the top right :wink:

arosszay commented 8 years ago

@The-Compiler , no I was not aware of the existing plugins. Thank you for letting me know about them!

And for my issue, I mean, geez, you just solved it lickedy-split like that! Thank you very very much!!! Using the markers is an excellent solution to my problem! I knew what I was trying to do wasn't necessarily "needed" haha

You are the man. Thanks again! Also, since I am feeling quit good recently, I'd like to show you my appreciation for your help...similar to what I'm doing with nicco. It's the least I can do for you after you have already done so much for me!! Just hit me up with your paypal email and I'll make sure to let you know how much you've helped me. Cheers!!

The-Compiler commented 8 years ago

Glad to hear that solves your issue! As nicoddemus said, that's much appreciated but of course by no means required :wink: My paypal mail is me@the-compiler.org.

I'm closing this then, as I assume you're all set.