I am using this body(desired_caps are set properly in config file)
Whatever I do I receive 'AttributeError: 'ClassName' object has no attribute 'driver'' or similar errors - no find_element_by_xpath attribute or whatever.
Do you have any suggestions? I am doing in the same way as in lectures, maybe anything related to appium + python setups?
import unittest
from appium import webdriver
import time
import tracemalloc
tracemalloc.start()
from config import desired_caps
# self = webdriver
# self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
class BaseTest(unittest.TestCase):
def test_testcase1(self):
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_credentials(self):
email = self.driver.find_element_by_xpath("proper Xpath")
email.send_keys("Test")
save = self.driver.find_element_by_link_text("Log In")
save.click()
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(BaseTest)
unittest.TextTestRunner(verbosity=3).run(suite)
you need to make your driver in a function named setUp(). The unit test suite executes kinda like this.
setUp()
run test_testcase1()
tearDown()
setUp()
run test_credentials()
teardown()
...etc...
if driver driver is not made in setup() the other tests will not know about it. Unless you make driver in every single test. Same goes for any other test variables you'd need.
This way each test is independent of each other, and each test gets a fresh start.
Related
I'm working through the Flasky tutorial from Miguel Grinberg's book Flask Web Development 2e and I've run into a snag with the end-to-end testing in Chapter 15. When I try to run the code I get a console message
* Ignoring a call to 'app.run()' that would block the current 'flask' CLI command.
Only call 'app.run()' in an 'if __name__ == "__main__"' guard.
followed by the browser reporting "Firefox cannot establish a connection..." This suggest to me that the test server is not starting.
Here's the code, from pages 231-233 of the book (the file is tests/test_selenium.py):
import threading
import unittest
from selenium import webdriver
from app import create_app, db, fake
from app.models import Role, User
class SeleniumTestCase(unittest.TestCase):
browser = None
#classmethod
def setUpClass(cls) -> None:
try:
cls.browser = webdriver.Firefox()
except Exception as e:
pass
if cls.browser:
cls.app = create_app('testing')
cls.app_context = cls.app.app_context()
cls.app_context.push()
import logging
logger = logging.getLogger('werkzeug')
logger.setLevel('ERROR')
db.create_all()
Role.insert_roles()
fake.users(10)
fake.posts(10)
admin_role = Role.query.filter_by(permissions=0xff).first()
admin = User(email='john#example.com', password='cat', username='john', role=admin_role, confirmed=True)
db.session.add(admin)
db.session.commit()
cls.server_thread = threading.Thread(
target=cls.app.run,
kwargs={
'debug': 'false',
'use_reloader': False,
'use_debugger': False,
'host': '0.0.0.0',
'port': 5000
}
)
cls.server_thread.start()
#classmethod
def tearDownClass(cls) -> None:
if cls.browser:
cls.browser.get('http://localhost:5000/shutdown')
cls.browser.quit()
cls.server_thread.join()
db.drop_all()
db.session.remove()
cls.app_context.pop()
def setUp(self) -> None:
if not self.browser:
self.skipTest('Web browser not available')
def tearDown(self) -> None:
pass
def test_admin_home_page(self):
self.browser.get('http://localhost:5000/') # fails here
self.assertRegex(self.browser.page_source, 'Hello,\s+Stranger!')
self.fail('Finish the test!')
How can I get a test server up and running from within the test code? (I putzed around with Flask-Testing for a few days before giving it up as unmaintained.)
ADDENDUM: Further experimentation has determined that the problem lies in the explicit call to app.run() conflicting with the Flask CLI's implicit call to app.run(), but without the explicit call the test server doesn't start.
I want to run this from the Flask CLI the same as my unit tests. This means I need to find a way to start the test server after the test database is populated, which happens after the test class's code begins to run. The CLI command code is:
#app.cli.command()
#click.argument('test_names', nargs=-1)
def test(coverage, test_names):
"""Run the unit tests"""
import unittest
if test_names:
tests = unittest.TestLoader().loadTestsFromNames(test_names)
else:
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
so running from __main__ would bypass the tests' load/run sequence.
I found a feasible solution using Timer
import unittest
from threading import Timer
Create two variables on top in your code
timer = None
myapp = None
class ApplicationTest(unittest.TestCase):
Now at the bottom of the file create main method and custom method of timer, I assume startTest as method name
In the main method you can call create_app and put it in global variable and use that myapp variable inside your selenium testing code
unittest.main() will manually trigger your test class and run the test cases one after one, unfortunately the test runs twice, I don't know why
def startTest():
timer.cancel()
unittest.main()
if __name__ == '__main__':
timer = Timer(6.0, startTest)
timer.start()
myapp = create_app()
myapp.run(debug=True, threaded=True)
Hey I got a simple test where the fixure is not found. I am writting in vsc and using windows cmd to run pytest.
def test_graph_add_node(test_graph):
E fixture 'test_graph' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
This is the error I get, here is the test code:
import pytest
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'giddeon1.settings')
import django
django.setup()
from graphs.models import Graph, Node, Tag
#pytest.fixture
def test_graph():
graph = Graph.objects.get(pk='74921f18-ed5f-4759-9f0c-699a51af4307')
return graph
def test_graph():
new_graph = Graph()
assert new_graph
def test_graph_add_node(test_graph):
assert test_graph.name == 'Test1'
im using python 3.9.2, pytest 6.2.5.
I have see some similar questions but they all handle wider or bigger problems.
You appear to be defining test_graph twice, which means that the second definition will overwrite the first. And you added #pytest.fixture to a test_ method when you used it, but #pytest.fixture should be added to non test methods so that tests can use that fixture. Here's how the code should probably look:
import pytest
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'giddeon1.settings')
import django
django.setup()
from graphs.models import Graph, Node, Tag
#pytest.fixture
def graph():
graph = Graph.objects.get(pk='74921f18-ed5f-4759-9f0c-699a51af4307')
return graph
def test_graph():
new_graph = Graph()
assert new_graph
def test_graph_add_node(graph):
assert graph.name == 'Test1'
Above, the first method has been renamed to graph so that the next method doesn't override it (and now #pytest.fixture is applied to a non-test method). Then, the 3rd method uses the graph fixture. Make any other changes as needed.
Update1:
Thank you for your answer.
It almost solves the problem. If I could be more precise:
How to instantiate the two drivers in 'init_pages', instead of creating two same methods for each driver?
The new snippet as you suggested:
#pytest.mark.usefixtures("driver_init")
class BaseTestFF:
#pytest.fixture
def init_data(self, driver_init):
self.web_driver = driver_init('firefox') #how to inject data via external commands? jenkins?
self.web_driver2 = driver_init('chrome')
#pytest.fixture(autouse=True)
#pytest.mark.usefixtures('init_data')
def init_pages(self, init_data):
#how to instanciate the two drivers here? istead of creating two same methods for each driver
self.login_page = LoginPage(self.web_driver)
self.application_page = ApplicationPage(self.web_driver)
In my selenium project, I would like to be able to send arguments, instead of them to be fixed in the conftest file.
For each different method, I would like it to use different browsers for different things.
Is there any way to send arguments TO the fixture??
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver import DesiredCapabilities
#pytest.fixture(scope="class", params=['chrome']) #i would like to send here dynamic fixtures.
def driver_init(request):
if request.param == 'firefox':
web_driver = webdriver.Firefox()
elif request.param == 'edge':
web_driver = webdriver.Edge() # dk:needs to be added the path
else:
options = Options()
options.add_argument(
r'user-data-dir=Users/dannyk/Library/Application Support/Google/Chrome/Default')
web_driver = webdriver.Chrome(chrome_options=options)
request.cls.driver = web_driver
yield
web_driver.close()
#pytest.mark.usefixtures("driver_init")
class BaseTest:
#pytest.fixture(autouse=True)
def init_pages(self):
self.login_page = lambda driver_type=self.driver: LoginPage(driver_type)
self.application_page = lambda driver_type=self.driver: ApplicationPage(driver_type)
class TestT4(BaseTest):
# #pytest.fixture(autouse=True)
#pytest.mark.run(order=1) #here i would like to use firefox for the driver
def test_init_firefox(self):
self.getting_start_page.go_home_page()
#pytest.mark.run(order=2) #here i would like to use chrome for the driver
def test_init_chrome(self):
self.getting_start_page.go_home_page()
You can use the "factory as fixture" pattern in conftest, which is basically an inner function inside the fixture that accepts parameters. You can then pass them from your test as arguments.
In conftest:
#pytest.fixture
def driver_init():
web_drivers = []
def _driver_init(browser):
if browser == 'firefox':
web_driver = webdriver.Firefox()
web_drivers.append(web_driver)
elif browser == 'edge':
web_driver = webdriver.Edge() # dk:needs to be added the path
web_drivers.append(web_driver)
else:
options = Options()
options.add_argument(
r'user-data-dir=Users/dannyk/Library/Application Support/Google/Chrome/Default')
web_driver = webdriver.Chrome(chrome_options=options)
web_drivers.append(web_driver)
return web_driver
yield _driver_init
for web_driver in web_drivers:
web_driver.quit()
Then, in your test:
class TestT4(BaseTest):
#pytest.mark.run(order=1) #here i would like to use firefox for the driver
def test_init_firefox(self, driver_init):
web_driver = driver_init("firefox")
#pytest.mark.run(order=2) #here i would like to use chrome for the driver
def test_init_chrome(self, driver_init):
web_driver = driver_init("chrome")
Or you can use the same concept on your def_init_pages(self): fixture to instantiate the pages and send the arguments from your tests.
Please check the official documentation here.
Update
For your first new question:
how to inject data via external commands? jenkins?
I believe what you're looking for is the def pytest_addoption(parser) fixture. It allows you to pass arguments to your test automation through the command line, when running Pytest. So you could pass the browser you want your tests to run with, locally or with Jenkins, with something like this:
In conftest:
def pytest_addoption(parser):
parser.addoption("--browser")
#pytest.fixture
def browser(request):
return request.config.getoption("--browser")
Then, on your driver_init fixture you wouldn't use the "factory as fixture" pattern:
#pytest.fixture
def driver_init(browser):
if browser == 'firefox':
web_driver = webdriver.Firefox()
elif browser == 'edge':
web_driver = webdriver.Edge() # dk:needs to be added the path
else:
options = Options()
options.add_argument(
r'user-data-dir=Users/dannyk/Library/Application Support/Google/Chrome/Default')
web_driver = webdriver.Chrome(chrome_options=options)
yield web_driver
web_driver.quit()
Then, to run your tests, you would pass the browser as argument:
pytest --browser=firefox
Please check these documents:
Pass different values to a test function, depending on command line options
pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) → None
For your second new question:
how to instanciate the two drivers here? istead of creating two same methods for each driver
I think what might be the answer is the fixture parametrization that you used on your original question, but then with the driver_init fixture I suggested earlier (you wouldn't need the def init_data(self, driver_init): fixture you wrote):
#pytest.fixture(autouse=True, params=["firefox", "chrome", "edge"])
def init_pages(self, request, driver_init):
self.login_page = LoginPage(driver_init(request.param))
self.application_page = ApplicationPage(driver_init(request.param))
Please note that I have removed the #pytest.mark.usefixtures as it has no effect when applied to another fixture. You need to declare what fixture you're using as a parameter:
#pytest.fixture(autouse=True)
def init_pages(self, init_data):
Please check the pytest.mark.usefixtures documentation
I hope this gives you more tools to figure out what you need to do to sort your problem. I'm not sure if I understand completely what you're trying to accomplish.
I am building a test suite for a web app. I am using fixtures as below:
import pytest
from selenium import webdriver
from common import Common
#pytest.fixture(scope='module')
def driver(module, headless=True):
opts = webdriver.FirefoxOptions()
opts.add_argument('--headless') if headless else None
driver = webdriver.Firefox(options=opts)
driver.get('http://localhost:8080/request')
yield driver
driver.quit()
def test_title(driver):
assert driver.title == 'abc'
if __name__ == '__main__':
test_title() #what I need to execute to see if everything is fine
Suppose I need to see if my test_title function is doing what it needs to by running this module directly inside a if __name__ == '__main__':. How can I call test_title() with driver passed in as an argument?
calling test_title like below:
if __name__ == '__main__':
test_title(driver(None, False))
python produces an error mentioned below:
(virtual) sflash#debian:~/Documents/php/ufj/ufj-test$ ./test_r*
Traceback (most recent call last):
File "./test_request.py", line 30, in <module>
test_empty_all(driver(None, headless=True))
File "/home/sflash/Documents/php/ufj/ufj-test/virtual/lib/python3.7/site-packages/_pytest/fixtures.py", line 1176, in result
fail(message, pytrace=False)
File "/home/sflash/Documents/php/ufj/ufj-test/virtual/lib/python3.7/site-packages/_pytest/outcomes.py", line 153, in fail
raise Failed(msg=msg, pytrace=pytrace)
Failed: Fixture "driver" called directly. Fixtures are not meant to be called directly,
but are created automatically when test functions request them as parameters.
See https://docs.pytest.org/en/stable/fixture.html for more information about fixtures, and
https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code.
As has been discussed in the comments, fixtures cannot be called directly - they only work together with pytest.
To invoke the test directly from your code, you can call pytest.main() in your __main__ section, which has the same effect as calling pytest on the command line. Any command line options can be added as arguments to the call (as a list), for example:
if __name__ == '__main__':
pytest.main(['-vv', 'test.py::test_title'])
To use the driver without involving pytest (which was your intention) you have to extract the driver logic and call it separately both from the fixture and from main:
import pytest
from selenium import webdriver
from common import Common
def get_driver(headless=True):
opts = webdriver.FirefoxOptions()
opts.add_argument('--headless') if headless else None
driver = webdriver.Firefox(options=opts)
driver.get('http://localhost:8080/request')
return driver
):
#pytest.fixture(scope='module')
def driver(module):
yield get_driver()
driver.quit()
def test_title(driver):
assert driver.title == 'abc'
if __name__ == '__main__':
driver = get_driver()
test_title()
driver.quit()
Note that this only works if the test function does not rely on any pytest-specific stuff (for example auto-applied fixtures).
Note also that you cannot use a parameter in your fixture as you did in your example, as you have no way to provide the parameter. Instead you can use parametrized fixtures.
I am using unittest example in selenium python
tried google did not get correct solution
from selenium import webdriver
import unittest
#import HtmlTestRunner
class googlesearch(unittest.TestCase):
driver = 'driver'
#classmethod
def setupClass(self):
self.driver = webdriver.Chrome(chrome_options=options)
self.driver.implicitly_wait(10)
self.driver.maximize_window()
def test_search_automationstepbystep(self):
self.driver.get("https://google.com")
self.driver.find_element_by_name("q").send_keys("Automation Step By step")
self.driver.find_element_by_name("btnk").click()
def test_search_naresh(self):
self.driver.get("https://google.com")
self.driver.find_element_by_name("q").send_keys("Naresh")
self.driver.find_element_by_name("btnk").click()
#classmethod
def teardownClass(self):
self.driver.close()
self.driver.quit()
print("Test completed")
if __name__== "__main__":
unittest.main()
As mentioned #Error - Syntactical Remorse , driver is a string due to your first line of code in your class.
If you are planning to access the driver globally make sure to declare the driver as global.