Use Pytest fixture with Flask-testing LiveServerTestCase - python

I am writing automated UI tests for my Flask app using Selenium and LiveServerTestCase from Flask testing.
This is how I have everything set up:
conftest.py
import pytest
from selenium import webdriver
#pytest.yield_fixture(scope="session")
def chrome_browser():
browser = webdriver.Chrome()
yield browser
browser.quit()
test_main_page.py
from app import create_app
from flask_testing import LiveServerTestCase
import multiprocessing
class TestMainPage(LiveServerTestCase):
multiprocessing.set_start_method("fork")
def create_app(self):
app = create_app()
app.config['TESTING'] = True
app.config.update(LIVESERVER_PORT=9898)
return app
def test_main_page(self, chrome_browser):
driver = chrome_browser
assert 1 == 1
Running Pytest giving me the following exception:
FAILED test_main_page.py::TestMainPage::test_main_page - TypeError: test_main_page() missing 1 required positional argument: 'chrome_browser'
When removing the LiveServerTestCase from the TestMainPage class, the fixture chrome_browser is working just fine.
How can I use both LiveServerTestCase and Pytest fixtures?
Thanks

Related

I have been trying to Run a simple Page Object Model in Python with Selenium but for some reason the browser do not open

'''
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import pytest
#Google.py File
class Google:
txtbox_searchbar_xpath = "//input[#class='gLFyf gsfi']"
def __init__(self,driver):
self.driver = driver
def enterSearchTerm(self,search_term):
self.driver.find_element(By.XPATH, self.txtbox_searchbar_xpath).claer()
self.driver.find_element(By.XPATH, self.txtbox_searchbar_xpath).send_keys(search_term,Keys.RETURN)
'''
'''
from selenium import webdriver
from PageObjects.Google import Google
import pytest
#test_Google.py
class Google_Test:
url = "https://www.google.com/"
search_word = "Python.org"
searchbar_xpath ="//input[#class='gLFyf gsfi']"
def test_searchGoogle(self):
self.driver = webdriver.Chrome(executable_path="C:\\Users\\mm195\\PycharmProjects\\pythonProject\\drivers\\chromedriver.exe")
self.driver.get(self.url)
# Initiate Google Class
self.google = Google(self.driver)
self.google.enterSearchTerm(self.search_word)
act_title = self.driver.title
if act_title == "Google":
assert True
else:
assert False
'''
This answer solves only title-question "why the browser is not opening".
Pytest naming convention requires to have Classes and method starting with "Test/test".
So you need to change Class name to Test_Google.
Update - 06/02/2021 - 19:26 PLN Time
How are you running tests? Pytest has own test discovery mechanism. If you call simply from cmd line:
pytest test_script.py
then it should discover existing test/tests
Update - 08/02/2021 - 12:54 PLN Time
So
You would need to provide more information:
directory structure
errors during firs/second run

PytestCollectionWarning: cannot collect test class 'Test_001_Login' because it has a __init__ constructor

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.

How to repeat running tests in python selenium using unittest?

Below is displayed my test class . This is good test when i call it once - python3 main.py.
There is a problem if I want this test run for example 100 times. How to do that?
When I try call by pytest there are warnings sth like this:
main.py::TestLoginPage::test_login_invalid_credentials
/home/-----/.local/lib/python3.5/site-packages/pytest_repeat.py:31: UserWarning: Repeating unittest class tests not supported
"Repeating unittest class tests not supported")
main.py::TestLoginPage::test_login_valid_credentials
/home/-----/.local/lib/python3.5/site-packages/pytest_repeat.py:31: UserWarning: Repeating unittest class tests not supported
"Repeating unittest class tests not supported")
This is my test class - test_login_page.py
import unittest
from selenium import webdriver
from tests.test_driver import TestDriver
from pages.login_page import LoginPage
class TestLoginPage(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.page_url = LoginPage.login_url
cls.webdriver = webdriver
TestDriver.setUp(cls, cls.page_url)
cls.page = LoginPage(cls.webdriver)
cls.option = 1
def __init_test_method(self):
# [TS001].Check if page is loaded correctly
self.assertTrue(self.page.check_page_loaded_correctly)
# [TS002].Check button contains 'login' text
self.assertEqual(self.page.get_button_text(), 'Login')
# TC001
def test_login_valid_credentials(self):
"""Test login with valid credentials"""
self.__init_test_method()
# [TS003].Clear form, fill with correct data and submit
self.assertEqual(self.page.login_process(self.option), 'Complexes')
# TC002 (invalid password) & TC003 (invalid username)
def test_login_invalid_credentials(self):
"""Test login with invalid credentials"""
for option in range(2, 4):
self.__init_test_method()
self.assertTrue(self.page.login_process(option))
#classmethod
def tearDownClass(cls):
cls.webdriver.quit()
This is main which I run all tests from console - main.py
import unittest
import os
import sys
cwd = os.getcwd()
sys.path.append(cwd)
from tests.test_login_page import TestLoginPage
if __name__ == '__main__':
unittest.main()
If you want to run your tests over and over, I would suggest you use ddt or data driven tests. You can find the documentation for complete usage here.
You will decorate your test class with ddt and your test method with file_data or data to pass in unique test parameters.
This will allow you to feed new test parameters for each iteration of your test and will cycle through and run the same test method for however many times you need.
Here is an example of how I use it:
from ddt import ddt, file_data
#ddt
class MyTestClass(unittest.TestCase):
#file_data('test_parameter_file.json')
def some_test_method1(self, test_package):
Some test method
I have the different test parameters in the test_parameter_file.json file and my test method is run for each parameter in the file.

Set up Django DiscoverRunner to always recreate database on testing with radish

I am using radish bdd with selenium to test my django app, however sometimes django ask to delete database because it's already exists in database. here's my terrain.py:
import os
import django
from django.test.runner import DiscoverRunner
from django.test import LiveServerTestCase
from radish import before, after
from selenium import webdriver
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tangorblog.settings.features')
BASE_URL = os.environ.get('BASE_URL', 'http://localhost:8000')
#before.each_scenario
def setup_django_test(scenario):
django.setup()
scenario.context.test_runner = DiscoverRunner()
scenario.context.test_runner.setup_test_environment()
scenario.context.old_db_config =\
scenario.context.test_runner.setup_databases()
scenario.context.base_url = BASE_URL
scenario.context.test_case = LiveServerTestCase()
scenario.context.test_case.setUpClass()
scenario.context.browser = webdriver.Chrome()
#after.each_scenario
def teardown_django(scenario):
scenario.context.browser.quit()
scenario.context.test_case.tearDownClass()
del scenario.context.test_case
scenario.context.test_runner.teardown_databases(
scenario.context.old_db_config)
scenario.context.test_runner.teardown_test_environment()
I think that, I can somehow could alter this on
scenario.context.old_db_config =\
scenario.context.test_runner.setup_databases()
But I don't know how. Any help?
It seems to me that recreating the database for every scenario would end up being highly inefficient (and super slow). It should only be necessary to create the database once per test run and then drop it at the end.
I've come up with a solution I think integrates better with Django. It allows you to run the tests with manage.py test, only creates/drops the database once per test run, and clears the database tables after every feature is tested.
Note that this runs both the Django unit tests and radish tests by default. To run just the radish tests, you can do RADISH_ONLY=1 manage.py test. Also, for the live server/Selenium tests to work, you have to run manage.py collectstatic first.
# package/settings.py
TEST_RUNNER = 'package.test.runner.RadishTestRunner'
# package/test/runner
import os
from django.test.runner import DiscoverRunner
import radish.main
class RadishTestRunner(DiscoverRunner):
def run_suite(self, suite, **kwargs):
# Run unit tests
if os.getenv('RADISH_ONLY') == '1':
result = None
else:
result = super().run_suite(suite, **kwargs)
# Run radish behavioral tests
self._radish_result = radish.main.main(['features'])
return result
def suite_result(self, suite, result, **kwargs):
if result is not None:
# Django unit tests were run
result = super().suite_result(suite, result, **kwargs)
else:
result = 0
result += self._radish_result
return result
# radish/world.py
from django.db import connections
from django.test.testcases import LiveServerThread, _StaticFilesHandler
from django.test.utils import modify_settings
from radish import pick
from selenium import webdriver
#pick
def get_browser():
return webdriver.Chrome()
#pick
def get_live_server():
live_server = LiveServer()
live_server.start()
return live_server
class LiveServer:
host = 'localhost'
port = 0
server_thread_class = LiveServerThread
static_handler = _StaticFilesHandler
def __init__(self):
connections_override = {}
for conn in connections.all():
if conn.vendor == 'sqlite' and conn.is_in_memory_db():
conn.allow_thread_sharing = True
connections_override[conn.alias] = conn
self.modified_settings = modify_settings(ALLOWED_HOSTS={'append': self.host})
self.server_thread = self.server_thread_class(
self.host,
self.static_handler,
connections_override=connections_override,
port=self.port,
)
self.server_thread.daemon = True
#property
def url(self):
self.server_thread.is_ready.wait()
return 'http://{self.host}:{self.server_thread.port}'.format(self=self)
def start(self):
self.modified_settings.enable()
self.server_thread.start()
self.server_thread.is_ready.wait()
if self.server_thread.error:
self.stop()
raise self.server_thread.error
def stop(self):
if hasattr(self, 'server_thread'):
self.server_thread.terminate()
for conn in connections.all():
if conn.vendor == 'sqlite' and conn.is_in_memory_db():
conn.allow_thread_sharing = False
self.modified_settings.disable()
# radish/terrain.py
from django.db import connections, transaction
from radish import world, before, after
#before.all
def set_up(features, marker):
world.get_live_server()
#after.all
def tear_down(features, marker):
browser = world.get_browser()
live_server = world.get_live_server()
browser.quit()
live_server.stop()
#before.each_scenario
def set_up_scenario(scenario):
live_server = world.get_live_server()
scenario.context.base_url = live_server.url
scenario.context.browser = world.get_browser()
# XXX: Only works with the default database
# XXX: Assumes the default database supports transactions
scenario.context.transaction = transaction.atomic(using='default')
scenario.context.transaction.__enter__()
#after.each_scenario
def tear_down_scenario(scenario):
transaction.set_rollback(True, using='default')
scenario.context.transaction.__exit__(None, None, None)
for connection in connections.all():
connection.close()
#Wyatt, again I'm just gonna modify your answer. I have try and run your solution, however it didn't manage to make each scenario independent, I even encounter an Integrity error when I try to make model object inside scenario. Regardless I still use your solution (especially RadishTestRunner, as the idea comes from you. I modified it so I could run django unittest separately from radish. I use LiveServerTestCase directly and remove LiveServer as I notice the similirity between the two, except that LiveServerTestCase inherits from TransactionTestCase and it also has the LiveServerThread and _StaticFilesHandler built in. Here's how it is:
# package/test/runner.py
import os
from django.test.runner import DiscoverRunner
import radish.main
class RadishTestRunner(DiscoverRunner):
radish_features = ['features']
def run_suite(self, suite, **kwargs):
# run radish test
return radish.main.main(self.radish_features)
def suite_result(self, suite, result, **kwargs):
return result
def set_radish_features(self, features):
self.radish_features = features
# radish/world.py
from django.test import LiveServerTestCase
from radish import pick
from selenium import webdriver
#pick
def get_browser():
return webdriver.Chrome()
#pick
def get_live_server():
live_server = LiveServerTestCase
live_server.setUpClass()
return live_server
# radish/terrain.py
from radish import world, before, after
from selenium import webdriver
#before.all
def set_up(features, marker):
world.get_live_server()
#after.all
def tear_down(features, marker):
live_server = world.get_live_server()
live_server.tearDownClass()
#before.each_scenario
def set_up_scenario(scenario):
live_server = world.get_live_server()
scenario.context.browser = webdriver.Chrome()
scenario.context.base_url = live_server.live_server_url
scenario.context.test_case = live_server()
scenario.context.test_case._pre_setup()
#after.each_scenario
def tear_down_scenario(scenario):
scenario.context.test_case._post_teardown()
scenario.context.browser.quit()
That's it. This also fix the problem with PostgreSQL on my other question that you point out. I also open and quit browser on each scenario, as it gives me more control over the browser inside scenario. Thank you so much for you effort to point me at the right direction.
Finally I return to PostgreSQL.
PostgreSQL seem to better than MySQL in terms of speed. It greatly reduced the time to run test.
And oh ya, I need to run ./manage.py collectstatic first after specify STATIC_ROOT in django settings file.
I also modify RadishTestRunner, so instead of running with RADISH_ONLY=1, I could run it with python manage.py radish /path/to/features/file. Here's my radish command:
# package.management.commands.radish
from __future__ import absolute_import
import sys
from django.core.management.base import BaseCommand, CommandError
from package.test.runner import RadishTestRunner
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('features', nargs='+', type=str)
def handle(self, *args, **options):
test_runner = RadishTestRunner(interactive=False)
if options['features']:
test_runner.set_radish_features(options['features'])
result = test_runner.run_suite(None)
if result:
sys.exit(result)
By using radish with django management command, we have control over which feature file we want to run.

Why does flask-testing spawn two test instances?

I want to use the LiveServerTestCase class which is provided by flask-testing to test my flask application in combination with Selenium.
I tried implementing the tests the way described in the flask-testing documentation. But documentation on the LiveServerTestCase is very sparse and I always end up getting two instances of my testcases which are executed at the same time.
I ran my tests through Eclipse and PyCharm with the same behaviour.
How do I have to run/configure my tests to only get one testing instance?
This is how I setup my tests:
import unittest
import urllib2
from selenium import webdriver
from CodeLoad import app
from flask_testing import LiveServerTestCase
class flask_tests(LiveServerTestCase):
def create_app(self):
return app
def setUp(self):
self.driver = webdriver.Firefox()
def tearDown(self):
self.driver.close()
def test_0_server_is_up_and_running(self):
response = urllib2.urlopen(self.get_server_url())
self.assertEqual(response.code, 200)
if __name__ == '__main__':
unittest.main()
Because of a bug.
https://github.com/jarus/flask-testing/issues/33
Try turning DEBUG off

Categories

Resources