reliably kill phantomjs launched in setUpClass if derived class' setUpClass fails - unit-testing

I wrote a SeleniumTestCase class that launches PhantomJS in its setUpClass and kills it in its tearDownClass. However, if a derived class' setUpClass raises an error, the PhantomJS process is left hanging because SeleniumTestCase.tearDownClass doesn't get called.
from django.test import LiveServerTestCase
import sys, signal, os
from selenium import webdriver
errorShots = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', "errorShots")
class SeleniumTestCase(LiveServerTestCase):
#classmethod
def setUpClass(cls):
"""
Launches PhantomJS
"""
super(SeleniumTestCase, cls).setUpClass()
cls.browser = webdriver.PhantomJS()
#classmethod
def tearDownClass(cls):
"""
Saves a screenshot if the test failed, and kills PhantomJS
"""
print 'Tearing down...'
if cls.browser:
if sys.exc_info()[0]:
try:
os.mkdir(errorShots)
except:
pass
errorShotPath = os.path.join(
errorShots,
"ERROR_phantomjs_%s_%s.png" % (cls._testMethodName, datetime.datetime.now().isoformat())
)
cls.browser.save_screenshot(errorShotPath)
print 'Saved screenshot to', errorShotPath
cls.browser.service.process.send_signal(signal.SIGTERM)
cls.browser.quit()
class SetUpClassTest(SeleniumTestCase):
#classmethod
def setUpClass(cls):
print 'Setting Up'
super(SetUpClassTest, cls).setUpClass()
raise Error('gotcha!')
def test1(self):
pass
Output (note that "Tearing Down" doesn't get printed)
$ ./manage.py test
Creating test database for alias 'default'...
Setting Up
E
======================================================================
ERROR: setUpClass (trucks.tests.SetUpClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/andy/leased-on/trucks/tests.py", line 1416, in setUpClass
raise Error('gotcha!')
NameError: global name 'Error' is not defined
----------------------------------------------------------------------
Ran 0 tests in 1.034s
FAILED (errors=1)
Destroying test database for alias 'default'...
How can I kill PhantomJS after a suite's setUpClass fails?
I know I could switch to using setUp and addCleanup, but I want to avoid relaunching PhantomJS (and logging back into my app with it) before every single test.

I decided to use setUpModule and tearDownModule to launch and kill PhantomJS. I put the screenshot-saving code in an addCleanup hook.
from django.test import LiveServerTestCase
from selenium import webdriver
import sys
import signal
import os
import unittest
errorShots = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', "errorShots")
browser = None
def setUpModule():
"""
Launches PhantomJS
"""
global browser
sys.stdout.write('Starting PhantomJS...')
sys.stdout.flush()
browser = webdriver.PhantomJS()
print 'done'
def tearDownModule():
"""
kills PhantomJS
"""
if browser:
sys.stdout.write('Killing PhantomJS...')
sys.stdout.flush()
browser.service.process.send_signal(signal.SIGTERM)
browser.quit()
print 'done'
class SeleniumTestCase(LiveServerTestCase):
def setUp(self):
self.addCleanup(self.cleanup)
def cleanup(self):
"""
Saves a screenshot if the test failed
"""
if sys.exc_info()[0]:
try:
os.mkdir(errorShots)
except:
pass
errorShotPath = os.path.join(
errorShots,
"ERROR_phantomjs_%s_%s.png" % (self._testMethodName, datetime.datetime.now().isoformat())
)
browser.save_screenshot(errorShotPath)
print '\nSaved screenshot to', errorShotPath

Related

How to run Django selenium test in github actions

I have a Django selenium test that runs fine on local machine using Firefox Webdrive.
When I try to run it on github actions I get following error:
Traceback (most recent call last):
File "/home/runner/work/Pangea/Pangea/core/tests/test_selenium.py", line 12, in setUpClass
cls.selenium = WebDriver()
File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/selenium/webdriver/firefox/webdriver.py", line 181, in __init__
RemoteWebDriver.__init__(
File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py", line 269, in __init__
self.start_session(capabilities, browser_profile)
File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py", line 360, in start_session
response = self.execute(Command.NEW_SESSION, parameters)
File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py", line 425, in execute
self.error_handler.check_response(response)
File "/opt/hostedtoolcache/Python/3.8.12/x64/lib/python3.8/site-packages/selenium/webdriver/remote/errorhandler.py", line 247, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: Process unexpectedly closed with status 1
The test that I am trying to run is very basic as shown below
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from rest_framework.reverse import reverse
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
class TestLoginWithSelenium(StaticLiveServerTestCase):
#classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.maximize_window()
cls.username = "xyz"
cls.password = "mnbvcxza"
cls.server_url = 'http://127.0.0.1:8000'
#classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
"""
Test Login for a user
"""
self.assertTrue(1==1)
I am running following commands on Github Actions:
pip install selenium
sudo apt install firefox-geckodriver
which geckodriver
geckodriver -V
sudo mv /usr/bin/geckodriver /usr/local/bin/geckodriver
which geckodriver
Mozilla Firefox 99.0
geckodriver 0.30.0
Reference on headless firefox option
Following is the modified code to run headless firefox on github actions
test.py
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from rest_framework.reverse import reverse
from selenium import webdriver
from selenium.webdriver import FirefoxOptions
from selenium.webdriver.common.by import By
class TestLoginWithSelenium(StaticLiveServerTestCase):
#classmethod
def setUpClass(cls):
super().setUpClass()
opts = FirefoxOptions()
opts.add_argument("--headless")
cls.selenium = webdriver.Firefox(options=opts)
# this will run with head and you can actually see the
# browser open up in tests
# cls.selenium = webdriver.Firefox()
cls.username = "xyz"
cls.password = "mnbvcxza"
#classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
"""
Test Login for a user
"""
self.client.logout()
self.selenium.get(f"{self.live_server_url}{reverse('login')}")
username_input = self.selenium.find_element(By.ID, "id_auth-username")
username_input.send_keys(self.username)
password_input = self.selenium.find_element(By.ID, "id_auth-password")
password_input.send_keys(self.password)
self.selenium.find_element(By.ID, 'idLogin').click()
github actions
pip install selenium
sudo apt install firefox-geckodriver

Django and selenium TypeError: setUpClass() missing 1 required positional argument: 'cls'

I'am trying to experiment django v2 with selenium and got this error:
======================================================================
ERROR: setUpClass (level.tests.LevelListViewTest)
----------------------------------------------------------------------
TypeError: setUpClass() missing 1 required positional argument: 'cls'
----------------------------------------------------------------------
Ran 0 tests in 0.000s
FAILED (errors=1)
This is how my test looks like:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class LevelListViewTest(StaticLiveServerTestCase):
#staticmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
#staticmethod
def tearDownClass(cls):
cls.selenium.quit()
cls.selenium.tearDownClass()
def test_level_is_in_admin_panel(self):
self.selenium.get('%s%s' % (self.live_server_url, '/admin/login/?next=/admin/'))
I use sqlite as database and I have already created a superuser and installed selenium using pip
I believe you want the #classmethod decorator on your setup and teardown functions, #staticmethod is for instances.
These should use the #classmethod decorator, not #staticmethod.

Update to Django 1.8 - AttributeError: django.test.TestCase has no attribute 'cls_atomics'

I updated a Django 1.7 project to Django 1.8 and now get errors when I run the tests (that are subclasses of django.test.TestCase).
Traceback (most recent call last):
File "env\lib\site-packages\django\test\testcases.py", line 962, in tearDownClass
cls._rollback_atomics(cls.cls_atomics)
AttributeError: type object 'SomeTests' has no attribute 'cls_atomics'
If I debug through the test I can step through all lines without problems, but after the last line the exception is thrown.
This is an example test:
import django
import unittest
from django.test import TestCase
import logging
import sys
from builtins import classmethod, isinstance
class ATestTests(TestCase):
#classmethod
def setUpClass(cls):
django.setup()
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
def setUp(self):
self._app = Application(name="a")
def testtest(self):
self.assertIsNotNone(self._app)
My environment:
astroid==1.3.4
colorama==0.3.3
defusedxml==0.4.1
Django==1.8
django-extensions==1.5.2
django-filter==0.9.2
djangorestframework==3.0.5
djangorestframework-xml==1.0.1
eight==0.3.0
future==0.11.4
logilab-common==0.63.2
Markdown==2.5.2
pylint==1.4.1
python-dateutil==2.4.1
python-mimeparse==0.1.4
six==1.9.0
xmltodict==0.9.2
How can I fix this?
I believe the reason is that your setUpClass(cls) class method is not calling super. Because of that, django.tests.TestCase.setUpClass is not called and
cls.cls_atomics = cls._enter_atomics()
is not called, naturally causing cls_atomics to be undefined.
You should add super(ATestTests, cls).setUpClass() to your setUpClass.
For Django 1.8+, you should use TestCase.setUpTestData instead of TestCase.setUpClass.
class MyTests(TestCase):
#classmethod
def setUpTestData(cls):
# Set up data for the whole TestCase
cls.foo = Foo.objects.create(bar="Test")
def test1(self):
self.assertEqual(self.foo.bar, 'Test')
The documentation is here.
I had a similar problem where a TestCase used setUpClass but did not have a tearDownClass method. My tests pass when I add an empty one:
#classmethod
def tearDownClass(cls):
pass
I also do not call django.setup.
Here is the complete code with the call to the base class (as suggested by #J. C. Leitão):
import django
import unittest
from django.test import TestCase
import logging
import sys
from builtins import classmethod
class ATestTests(TestCase):
#classmethod
def setUpClass(cls):
super(ATestTests, cls).setUpClass()
django.setup()
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
def setUp(self):
self._app = Application(name="a")
def testtest(self):
self.assertIsNotNone(self._app)

No forms exist in unit test with django-webtest

I want to write a test that will test change the password in the application. I use the django-allauth. For testing, I use django-WebTest.
When I run my code, I get the message:
FAILED (errors=1)
Destroying test database for alias 'default'...
mark#mariusz-K73E:~/myapp$ python manage.py test users
Creating test database for alias 'default'...
.E
======================================================================
ERROR: test_password_change_use_template (myapp.users.tests.ChangePasswordTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mark/myapp/users/tests.py", line 16, in test_password_change_use_template
password_change.form['oldpassword'] = "test123"
File "/home/mark/.virtualenvs/urlop/local/lib/python2.7/site-packages/webtest/response.py", line 56, in form
"You used response.form, but no forms exist")
TypeError: You used response.form, but no forms exist
My code:
from django_webtest import WebTest
from django_dynamic_fixture import G
from users.models import User
from django.core.urlresolvers import reverse
class ChangePasswordTest(WebTest):
def setUp(self):
self.user = G(User)
def test_password_change_code(self):
password_change = self.app.get(reverse('account_change_password'), user=self.user)
def test_password_change_use_template(self):
password_change = self.app.get(reverse('account_change_password'), user=self.user)
password_change.form['oldpassword'] = "test123"
password_change.form['password1'] = "test456"
password_change.form['password2'] = "test456"
password_change.form.submit()
self.assertRedirects(password_change, reverse('change_password'))
WebTest tests the rendered content. There is no form found in the HTML (maybe the form tag is incorrect or missing).
If it's working in your manual test. You can try to print the response to see what's different:
def test_password_change_use_template(self):
response = self.app.get(reverse('account_change_password'), user=self.user)
print response

Django Lettuce built-in server 500 response

I am running the Lettuce built-in server to test that it returns a given reponse however, it shows a 500 response.
My features file:
Feature: home page loads
Scenario: Check that home page loads with header
Given I access the home url
then the home page should load with the title "Movies currently showing"
My steps file:
#step(u'Given I access the home url')
def given_i_access_the_home_url(step):
world.response = world.browser.get(django_url('/'))
sleep(10)
#step(u'then the home page should load with the title "([^"]*)"')
def then_the_home_page_should_load_with_the_title_group1(step, group1):
assert group1 in world.response
My Terrains file:
from django.core.management import call_command
from django.test.simple import DjangoTestSuiteRunner
from lettuce import before, after, world
from logging import getLogger
from selenium import webdriver
try:
from south.management.commands import patch_for_test_db_setup
except:
pass
logger = getLogger(__name__)
logger.info("Loading the terrain file...")
#before.runserver
def setup_database(actual_server):
'''
This will setup your database, sync it, and run migrations if you are using South.
It does this before the Test Django server is set up.
'''
logger.info("Setting up a test database...")
# Uncomment if you are using South
# patch_for_test_db_setup()
world.test_runner = DjangoTestSuiteRunner(interactive=False)
DjangoTestSuiteRunner.setup_test_environment(world.test_runner)
world.created_db = DjangoTestSuiteRunner.setup_databases(world.test_runner)
call_command('syncdb', interactive=False, verbosity=0)
# Uncomment if you are using South
# call_command('migrate', interactive=False, verbosity=0)
#after.runserver
def teardown_database(actual_server):
'''
This will destroy your test database after all of your tests have executed.
'''
logger.info("Destroying the test database ...")
DjangoTestSuiteRunner.teardown_databases(world.test_runner, world.created_db)
#before.all
def setup_browser():
world.browser = webdriver.Firefox()
#after.all
def teardown_browser(total):
world.browser.quit()
What could be the problem with the server, why a 500 response error?
I managed to find what the problem is, the migrations were not running on syncdb