django1/django/contrib/admin/tests.py

125 lines
4.8 KiB
Python
Raw Normal View History

import os
from django.test import LiveServerTestCase
from django.utils.module_loading import import_by_path
from django.utils.unittest import SkipTest
from django.utils.translation import ugettext as _
class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
available_apps = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
]
webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
@classmethod
def setUpClass(cls):
if not os.environ.get('DJANGO_SELENIUM_TESTS', False):
raise SkipTest('Selenium tests not requested')
try:
cls.selenium = import_by_path(cls.webdriver_class)()
except Exception as e:
raise SkipTest('Selenium webdriver "%s" not installed or not '
'operational: %s' % (cls.webdriver_class, str(e)))
[1.6.x] Final attempt to solve sporadic test failures. tearDownClass is not called if setUpClass throws an exception, in our case this means that LiveServerTestCase leaks LiveServerThread sockets if the test happens to be skipped later on, and AdminSeleniumWebDriverTestCase doesn't close it's already open browser window. To prevent this leakage we catch errors where needed and manually call _tearDownClassInternal. _tearDownClassInternal should be written as defensively as possible since it is not allowed to make any assumptions on how far setUpClass got. This patch should fix the sporadic "Address already in use"-errors on jenkins and also the "This code isn't under transaction management"-error for sqlite (also just on jenkins). After discussion with koniiiik, jezdez, kmtracey, tos9, lifeless, nedbat and voidspace it was decided that this is the safest approach (thanks to everyone for their comments and help). Manually calling tearDownClass was shut down cause we don't know how our users override our classes. This is a private and very specialized API on purpose and should not be used without a strong reason! This patch partially reverts the earlier attempts to fix those issues, namely: 2fa0dd73b18f55d0fdd1c1d54b1d18031bfcf1ed and 3c5775d36f7e431d9691829a78580873111cb714 Final note: If this patch breaks in a later version of Django, please be very careful on how you fix it, you might not see test failures locally. That said, this patch hopefully doesn't produce even more failures. Backport of 73a610d2a81bc3bf2d3834786b2458bc85953ed0 from master.
2013-09-17 22:28:20 +08:00
# This has to be last to ensure that resources are cleaned up properly!
super(AdminSeleniumWebDriverTestCase, cls).setUpClass()
@classmethod
[1.6.x] Final attempt to solve sporadic test failures. tearDownClass is not called if setUpClass throws an exception, in our case this means that LiveServerTestCase leaks LiveServerThread sockets if the test happens to be skipped later on, and AdminSeleniumWebDriverTestCase doesn't close it's already open browser window. To prevent this leakage we catch errors where needed and manually call _tearDownClassInternal. _tearDownClassInternal should be written as defensively as possible since it is not allowed to make any assumptions on how far setUpClass got. This patch should fix the sporadic "Address already in use"-errors on jenkins and also the "This code isn't under transaction management"-error for sqlite (also just on jenkins). After discussion with koniiiik, jezdez, kmtracey, tos9, lifeless, nedbat and voidspace it was decided that this is the safest approach (thanks to everyone for their comments and help). Manually calling tearDownClass was shut down cause we don't know how our users override our classes. This is a private and very specialized API on purpose and should not be used without a strong reason! This patch partially reverts the earlier attempts to fix those issues, namely: 2fa0dd73b18f55d0fdd1c1d54b1d18031bfcf1ed and 3c5775d36f7e431d9691829a78580873111cb714 Final note: If this patch breaks in a later version of Django, please be very careful on how you fix it, you might not see test failures locally. That said, this patch hopefully doesn't produce even more failures. Backport of 73a610d2a81bc3bf2d3834786b2458bc85953ed0 from master.
2013-09-17 22:28:20 +08:00
def _tearDownClassInternal(cls):
if hasattr(cls, 'selenium'):
cls.selenium.quit()
[1.6.x] Final attempt to solve sporadic test failures. tearDownClass is not called if setUpClass throws an exception, in our case this means that LiveServerTestCase leaks LiveServerThread sockets if the test happens to be skipped later on, and AdminSeleniumWebDriverTestCase doesn't close it's already open browser window. To prevent this leakage we catch errors where needed and manually call _tearDownClassInternal. _tearDownClassInternal should be written as defensively as possible since it is not allowed to make any assumptions on how far setUpClass got. This patch should fix the sporadic "Address already in use"-errors on jenkins and also the "This code isn't under transaction management"-error for sqlite (also just on jenkins). After discussion with koniiiik, jezdez, kmtracey, tos9, lifeless, nedbat and voidspace it was decided that this is the safest approach (thanks to everyone for their comments and help). Manually calling tearDownClass was shut down cause we don't know how our users override our classes. This is a private and very specialized API on purpose and should not be used without a strong reason! This patch partially reverts the earlier attempts to fix those issues, namely: 2fa0dd73b18f55d0fdd1c1d54b1d18031bfcf1ed and 3c5775d36f7e431d9691829a78580873111cb714 Final note: If this patch breaks in a later version of Django, please be very careful on how you fix it, you might not see test failures locally. That said, this patch hopefully doesn't produce even more failures. Backport of 73a610d2a81bc3bf2d3834786b2458bc85953ed0 from master.
2013-09-17 22:28:20 +08:00
super(AdminSeleniumWebDriverTestCase, cls)._tearDownClassInternal()
def wait_until(self, callback, timeout=10):
"""
Helper function that blocks the execution of the tests until the
specified callback returns a value that is not falsy. This function can
be called, for example, after clicking a link or submitting a form.
See the other public methods that call this function for more details.
"""
from selenium.webdriver.support.wait import WebDriverWait
WebDriverWait(self.selenium, timeout).until(callback)
def wait_loaded_tag(self, tag_name, timeout=10):
"""
Helper function that blocks until the element with the given tag name
is found on the page.
"""
self.wait_until(
lambda driver: driver.find_element_by_tag_name(tag_name),
timeout
)
def wait_page_loaded(self):
"""
Block until page has started to load.
"""
from selenium.common.exceptions import TimeoutException
try:
# Wait for the next page to be loaded
self.wait_loaded_tag('body')
except TimeoutException:
# IE7 occasionnally returns an error "Internet Explorer cannot
# display the webpage" and doesn't load the next page. We just
# ignore it.
pass
def admin_login(self, username, password, login_url='/admin/'):
"""
Helper function to log into the admin.
"""
self.selenium.get('%s%s' % (self.live_server_url, login_url))
username_input = self.selenium.find_element_by_name('username')
username_input.send_keys(username)
password_input = self.selenium.find_element_by_name('password')
password_input.send_keys(password)
login_text = _('Log in')
self.selenium.find_element_by_xpath(
'//input[@value="%s"]' % login_text).click()
self.wait_page_loaded()
def get_css_value(self, selector, attribute):
"""
Helper function that returns the value for the CSS attribute of an
DOM element specified by the given selector. Uses the jQuery that ships
with Django.
"""
return self.selenium.execute_script(
'return django.jQuery("%s").css("%s")' % (selector, attribute))
def get_select_option(self, selector, value):
"""
Returns the <OPTION> with the value `value` inside the <SELECT> widget
identified by the CSS selector `selector`.
"""
from selenium.common.exceptions import NoSuchElementException
options = self.selenium.find_elements_by_css_selector('%s > option' % selector)
for option in options:
if option.get_attribute('value') == value:
return option
raise NoSuchElementException('Option "%s" not found in "%s"' % (value, selector))
def assertSelectOptions(self, selector, values):
"""
Asserts that the <SELECT> widget identified by `selector` has the
options with the given `values`.
"""
options = self.selenium.find_elements_by_css_selector('%s > option' % selector)
actual_values = []
for option in options:
actual_values.append(option.get_attribute('value'))
self.assertEqual(values, actual_values)
def has_css_class(self, selector, klass):
"""
Returns True if the element identified by `selector` has the CSS class
`klass`.
"""
return (self.selenium.find_element_by_css_selector(selector)
.get_attribute('class').find(klass) != -1)