I am trying to build an selenium based automation framework, using, Python, Pytest.
My intention is to create a driver instance at the class level by initializing it in conftest.py and making it available in all testcases, so that user doesn't need to create the driver instance in each testcase.
Driver instance in conftest.py:
#pytest.fixture(scope="class")
def get_driver(request):
from selenium import webdriver
driver = webdriver.Chrome()
request.cls.driver = driver
yield
driver.quit()
BaseTestCase class looks like this:
#pytest.mark.usefixtures("get_driver")
class BaseTestCase(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(BaseTestCase, self).__init__(*args, **kwargs)
#classmethod
def setUpClass(cls):
if hasattr(super(BaseTestCase, cls), "setUpClass"):
super(BaseTestCase, cls).setUpClass()
My testcase is as follows:
from ..pages.google import Google
class Test_Google_Page(BaseTestCase):
#classmethod
def setUpClass(self):
self.page = Google(self.driver, "https://www.google.com/")
My page Google extends to BasePage that looks like this:
class BasePage(object):
def __init__(self, driver, url=None, root_element = 'body'):
super(BasePage, self).__init__()
self.driver = driver
self._root_element = root_element
self.driver.set_script_timeout(script_timeout)
When i execute my testcase, I get the following error:
#classmethod
def setUpClass(self):
> driver = self.driver
E AttributeError: type object 'Test_Google_Page' has no attribute 'driver'
How can I make the driver instance available in my testcases by simply calling self.driver?
The class scoped fixtures are executed after the setUpClass classmethods, so when Test_Google_Page.setUpClass is executed, get_driver did not run yet. Check out the execution ordering:
import unittest
import pytest
#pytest.fixture(scope='class')
def fixture_class_scoped(request):
print(f'{request.cls.__name__}::fixture_class_scoped()')
#pytest.mark.usefixtures('fixture_class_scoped')
class TestCls(unittest.TestCase):
#classmethod
def setUpClass(cls):
print(f'{cls.__name__}::setUpClass()')
def setUp(self):
print(f'{self.__class__.__name__}::setUp()')
#pytest.fixture()
def fixture_instance_scoped(self):
print(f'{self.__class__.__name__}::fixture_instance_scoped()')
#pytest.mark.usefixtures('fixture_instance_scoped')
def test_bar(self):
print(f'{self.__class__.__name__}::test_bar()')
assert True
When running the test with e.g. pytest -sv, the output yields:
TestCls::setUpClass()
TestCls::fixture_class_scoped()
TestCls::fixture_instance_scoped()
TestCls::setUp()
TestCls::test_bar()
So the solution is to move the code from setUpClass to e.g. setUp:
class Test_Google_Page(BaseTestCase):
def setUp(self):
self.page = Google(self.driver, "https://www.google.com/")
I am afraid I may not use setUp because in my most of my class file I have multiple test cases and I just want to do one time setup in setUpClass instead of launching in each time before calling any test method.
I would then move the code from setUpClass to another class-scoped fixture:
import pytest
#pytest.mark.usefixtures('get_driver')
#pytest.fixture(scope='class')
def init_google_page(request):
request.cls.page = Google(request.cls.driver,
"https://www.google.com/")
#pytest.mark.usefixtures('init_google_page')
class Test_Google_Page(BaseTestCase):
...
The former setUpClass is now the init_google_page fixture which will be called after get_driver (because of pytest.mark.usefixtures('get_driver')).
Related
Hi I'm trying to learn Python/Selenium using a Page Object Model.
I have been following this tutorial and as far as I can tell I haven't missed anything. However I can't run my tests because of the init constructor. In the video, he has the same init constructor and the tests run for him.
Inside my project folder I have a subfolder pageObjects and testCases.
In pageObjects I have LoginPage.py where i set up my locators and actions i want to perform on the locators
class LoginPage:
textbox_username_id = "Email"
textbox_password_id = "Password"
button_login_xpath = "//input[#class='button-1 login-button']"
link_logout_linktext = "Logout"
def __init__(self,driver):
self.driver = driver
def setUserName(self,username):
self.driver.find_element_by_id(self.textbox_username_id).clear()
self.driver.find_element_by_id(self.textbox_username_id).send_keys(username)
def setPassword(self,password):
self.driver.find_element_by_id(self.textbox_password_id).clear()
self.driver.find_element_by_id(self.textbox_password_id).send_keys(password)
def clickLogin(self):
self.driver.find_element_by_xpath(self.button_login_xpath).click()
def clickLogout(self):
self.driver.find_element_by_link_text(self.link_logout_linktext).click()
In testCases I have test_login.py and conftest.py
I'll show conftest.py first
from selenium import webdriver
import pytest
#pytest.fixture()
def setup():
driver = webdriver.Chrome()
return driver
and test_login.py
import pytest
from selenium import webdriver
from pageObjects.LoginPage import LoginPage
from testCases import conftest
class Test_001_Login:
baseURL = "https://admin=demo.nopcommerce.com/"
username = "admin#yourstore.com"
password = "admin"
def __init__(self):
self.lp = LoginPage(self.driver)
self.driver = conftest.setup
def test_homePageTitle(self, setup):
self.driver = setup
self.driver.get(self.baseURL)
act_title = self.driver.title
self.driver.close()
if act_title == "Your store. Login":
assert True
else:
assert False
def test_login(self, setup):
self.driver = setup
self.driver.get(self.baseURL)
self.lp.setUserName(self.username)
self.lp.setPassword(self.password)
self.lp.clickLogin()
act_title = self.driver.title
if act_title == "Dashboard / nopCommerce administration":
assert True
else:
assert False
There are 2 issues.
conftest.py is supposed to be a special type of class/fixture. I should be able to call it to create an instance of webdriver.Chrome(). However it does not work. In test_login.py when i call 'setup' I get an error 'unresolved reference 'setup''. So I've had to manually import it as a work around. Isn't the whole point of the conftest.py file so that my test_* files have access to the webdriver in conftest.py wihtout having to import it?
When i try to run the test_login.py file, I get a warning and 0 items are collected - testCases\test_login.py:6
C:\Users\Mirza\PycharmProjects\nopcommerceApp\testCases\test_login.py:6: PytestCollectionWarning: cannot collect test class 'Test_001_Login' because it has a init constructor (from: testCases/test_login.py)
class Test_001_Login:
I have absolutely no idea what to do here. Why can't my class Test_001_Login have an init constructor? I need it to create an instance of the LoginPage class and to create an instance of the webdriver from conftest.py.
Any help would be greatly appreciated.
I have a class with few tests, for example:
class Example(Environment)
def test1(self):
def test2(self):
def test3(self):
def test4(self):
and class environment with setup and teardown methods
class Environment(unittest.TestCase):
def setUp(self):
options_for_console_log = DesiredCapabilities.CHROME
options_for_console_log['loggingPrefs'] = {'browser': 'ALL'}
self.driver = webdriver.Chrome(desired_capabilities=options_for_console_log)
self.driver.maximize_window()
def tearDown(self):
driver = self.driver
driver.close()
After every test chrome reopens, but i want to run it in one session. How can i do it?
setUp and tearDown are called before and after every test method. If you want driver to persist between tests move the relevant code to setUpClass and tearDownClass:
#classmethod
def setUpClass(cls):
options_for_console_log = DesiredCapabilities.CHROME
options_for_console_log['loggingPrefs'] = {'browser': 'ALL'}
cls.driver = webdriver.Chrome(desired_capabilities=options_for_console_log)
cls.driver.maximize_window()
#classmethod
def tearDownClass(cls):
cls.driver.close()
Just make sure that all the test cases are independent and standalone.
Following code does not collect any test cases (i expect 4 to be found). Why?
import pytest
import uuid
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
class TestClass:
def __init__(self):
self.browser = webdriver.Remote(
desired_capabilities=webdriver.DesiredCapabilities.FIREFOX,
command_executor='http://my-selenium:4444/wd/hub'
)
#pytest.mark.parametrize('data', [1,2,3,4])
def test_buybuttons(self, data):
self.browser.get('http://example.com/' + data)
assert '<noindex>' not in self.browser.page_source
def __del__(self):
self.browser.quit()
If i remove __init__ and __del__ methods, it will collect tests correctly. But how i can setup and tear test down? :/
pytest won't collect test classes with an __init__ method, a more detailed explanation of why is that can be found here: py.test skips test class if constructor is defined.
You should use fixtures to define setup and tear down operations, as they are more powerful and flexible.
If you have existing tests that already have setup/tear-down methods and want to convert them to pytest, this is one straightforward approach:
class TestClass:
#pytest.yield_fixture(autouse=True)
def init_browser(self):
self.browser = webdriver.Remote(
desired_capabilities=webdriver.DesiredCapabilities.FIREFOX,
command_executor='http://my-selenium:4444/wd/hub'
)
yield # everything after 'yield' is executed on tear-down
self.browser.quit()
#pytest.mark.parametrize('data', [1,2,3,4])
def test_buybuttons(self, data):
self.browser.get('http://example.com/' + data)
assert '<noindex>' not in self.browser.page_source
More details can be found here: autouse fixtures and accessing other fixtures
I'm attempting to pass parameters to unittest subclass methods. Please forgive my ignorance - I've only started coding in Python a few days ago. I could obviously just hardcode the parameters in the subclass itself but that would eliminate its reuse with other username/password combos. When I run run_tests.py below, I get the error "TypeError: runTest() takes exactly 3 arguments (4 given)".
Here is run_tests.py:
from selenium import webdriver
import unittest
from testcases import login
def my_suite():
suite = unittest.TestSuite()
suite.addTest (login.Login().runTest("username1", "password1", "page title"))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(my_suite())
Here is testcases/basetestcase.py:
from selenium import webdriver
import unittest
class BaseTestCase (unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "http://website"
def tearDown(self):
self.driver.quit()
Here is testcases/login.py
import common_page_elements
from basetestcase import BaseTestCase
class Login (BaseTestCase):
def runTest(username, password, verification):
""" Test logging in. """
driver = self.driver
driver.get(self.base_url)
driver.find_element_by_id(common_page_elements.textfield_username).clear()
driver.find_element_by_id(common_page_elements.textfield_username).send_keys(username)
driver.find_element_by_id(common_page_elements.textfield_password).clear()
driver.find_element_by_id(common_page_elements.textfield_password).send_keys(password)
driver.find_element_by_name(common_page_elements.button_submit).click()
self.assertTrue(verification in self.driver.title)
Since runTest has become a class method, you'll have to include the self argument:
class Login (BaseTestCase):
def runTest(self, username, password, verification):
^^^^
I'm trying to retrieve the session id of a selenium webdriver session during the execution of a test as such:
import unittest
class MyTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
def testSomething(self):
"""selenium tests go here"""
self.driver.get('http://www.example.com')
def tearDown(self):
self.driver.quit()
if __name__ == "__main__":
suite = unittest.TestLoader().loadTestsFromTestCase(MyTest)
testResult = unittest.TextTestRunner(verbosity=2).run(suite)
session_id = ???
I know I could do self.driver.session_id side the setUp method. The problem is I need to get the session id outside of the class instance. Any ideas?
Probably, you can create property/function inside the class instance which could return you sessionID.