diff --git a/esp/INSTALL b/esp/INSTALL index 189a9359cc..f09736a67a 100644 --- a/esp/INSTALL +++ b/esp/INSTALL @@ -38,8 +38,6 @@ a) Libraries obtained from the Internet If you use Ubuntu, a list of required system packages can be found in `packages_base.txt` in this directory; you can install them all at once with "./update_deps.sh". -Note that if you use this approach, you still must download -selenium-server-standalone separately. Packages useful for a production server are listed in a separate file. To install those, use "apt-get install -y $(< packages_prod.txt)". @@ -61,7 +59,6 @@ zlib System zlib1g-dev python System 2.5 python setuptools Python python-setuptools virtualenv Python python-virtualenv -selenium-server-standalone System http://selenium.googlecode.com/files/selenium-server-standalone-2.9.0.jar Node.js System nodejs LESS JS node-less diff --git a/esp/esp/django_settings.py b/esp/esp/django_settings.py index cfbda6a4dd..cec903bb4c 100644 --- a/esp/esp/django_settings.py +++ b/esp/esp/django_settings.py @@ -289,8 +289,6 @@ USE_MAILMAN = False MAILMAN_PATH = '/usr/lib/mailman/bin/' -SELENIUM_PATH = os.path.join(os.path.dirname(__file__), '../../../dependencies/selenium-server-standalone-2.9.0/selenium-server-standalone-2.9.0.jar') - AUTHENTICATION_BACKENDS = ( 'esp.utils.auth_backend.ESPAuthBackend', ) diff --git a/esp/esp/local_settings.py.travis b/esp/esp/local_settings.py.travis index 8c3aae3b82..ed8651a03d 100644 --- a/esp/esp/local_settings.py.travis +++ b/esp/esp/local_settings.py.travis @@ -76,11 +76,3 @@ MIDDLEWARE_LOCAL = [] DATABASE_NAME = 'test_django' DATABASE_USER = 'testuser' DATABASE_PASSWORD = 'testpassword' - -############ -# Selenium # -############ - -SELENIUM_TESTSERVER_HOST = 'localhost' -SELENIUM_TESTSERVER_PORT = 8000 -SELENIUM_DRIVER = 'Chrome' diff --git a/esp/esp/program/modules/tests/__init__.py b/esp/esp/program/modules/tests/__init__.py index 262f84bdec..2acffe2def 100644 --- a/esp/esp/program/modules/tests/__init__.py +++ b/esp/esp/program/modules/tests/__init__.py @@ -34,6 +34,7 @@ """ from esp.program.modules.tests.ajaxschedulingmodule import AJAXSchedulingModuleTest +from esp.program.modules.tests.ajaxstudentreg import AjaxStudentRegTest from esp.program.modules.tests.availabilitymodule import AvailabilityModuleTest from esp.program.modules.tests.regprofilemodule import RegProfileModuleTest from esp.program.modules.tests.studentreg import StudentRegTest diff --git a/esp/esp/program/modules/tests/ajaxstudentreg.py b/esp/esp/program/modules/tests/ajaxstudentreg.py index c6bfdef137..a33527780b 100644 --- a/esp/esp/program/modules/tests/ajaxstudentreg.py +++ b/esp/esp/program/modules/tests/ajaxstudentreg.py @@ -36,14 +36,12 @@ from esp.program.models.class_ import ClassSection from esp.program.tests import ProgramFrameworkTest -from django_selenium.testcases import SeleniumTestCase import random import json import logging logger = logging.getLogger(__name__) -# TODO(gkanwar): Remove non-selenium tests from this TestCase -class AjaxStudentRegTest(ProgramFrameworkTest, SeleniumTestCase): +class AjaxStudentRegTest(ProgramFrameworkTest): def setUp(self, *args, **kwargs): from esp.program.modules.base import ProgramModule, ProgramModuleObj @@ -54,7 +52,6 @@ def setUp(self, *args, **kwargs): 'num_rooms': 6, } ) ProgramFrameworkTest.setUp(self, *args, **kwargs) - SeleniumTestCase.setUp(self) self.add_student_profiles() self.schedule_randomly() @@ -100,7 +97,7 @@ def expect_ajaxerror(self, request_url, post_data, error_str): error_msg = response_dict['error'] self.assertTrue(error_received) - self.assertTrue(error_msg == error_str, 'Unexpected Ajax error: "%s", expected "%s"' % (error_msg, error_str)) + self.assertTrue(str(error_msg) == error_str, 'Unexpected Ajax error: "%s", expected "%s"' % (error_msg, error_str)) def test_ajax_schedule(self): program = self.program @@ -186,9 +183,3 @@ def test_ajax_clearslot(self): response = self.client.get('/learn/%s/ajax_clearslot/%d' % (program.getUrlBase(), sec2.meeting_times.all()[0].id), HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.expect_empty_schedule(response) - def test_lottery(self): - # TODO(gkanwar): Make this test actually do something, or remove it - program = self.program - - self.webdriver.get('/learn/%s/lotterystudentreg' % program.getUrlBase()) - diff --git a/esp/esp/qsd/seltests.py b/esp/esp/qsd/seltests.py index 43d7a549f1..f0a1a71607 100644 --- a/esp/esp/qsd/seltests.py +++ b/esp/esp/qsd/seltests.py @@ -33,19 +33,15 @@ """ from esp.qsd.models import QuasiStaticData from esp.seltests.util import try_normal_login, logout, noActiveAjaxJQuery -from esp.tagdict.models import Tag from esp.users.models import ESPUser from esp.web.models import NavBarCategory, default_navbarcategory -from django.conf import settings -from django.contrib.sites.models import Site -from django.utils.unittest.case import skipUnless -from django_selenium.testcases import SeleniumTestCase -from selenium import selenium +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.keys import Keys -class TestQsdCachePurging(SeleniumTestCase): +class TestQsdCachePurging(StaticLiveServerTestCase): """ This test requires Varnish (or some proxy caching server that accepts PURGE requests) to be set up on the port and host specified in @@ -56,17 +52,18 @@ class TestQsdCachePurging(SeleniumTestCase): TEST_STRING = 'Hello there from a django test!' def editQSD(self): - elem = self.find_element_by_class_name("qsd_header") + elem = self.selenium.find_element_by_class_name("qsd_header") elem.click() - elem = self.find_element_by_name("qsd_content") + elem = self.selenium.find_element_by_class_name("jodit_wysiwyg") for x in range(0, len(elem.text)): elem.send_keys(Keys.DELETE) elem.send_keys(self.TEST_STRING) - elem.send_keys(Keys.TAB) - WebDriverWait(self, 10).until(noActiveAjaxJQuery) + elem = self.selenium.find_element_by_class_name("btn-success") + elem.click() + WebDriverWait(self.selenium, 10).until(noActiveAjaxJQuery) def setUp(self): - SeleniumTestCase.setUp(self) + super(TestQsdCachePurging, self).setUp() # Make our users self.admin_user, created = ESPUser.objects.get_or_create(username='admin', first_name='Harry', last_name='Alborez') @@ -94,45 +91,33 @@ def setUp(self): qsd_rec_new.keywords = '' qsd_rec_new.save() - # Set the port that the webdriver will try to access - self._old_port = self.driver.testserver_port - self.driver.testserver_port = settings.VARNISH_PORT - - # Add the varnish_purge tag - Tag.objects.get_or_create(key='varnish_purge', value='true') - - # Set up the correct site - site = Site.objects.get_current() - site.domain = settings.VARNISH_HOST+":"+str(settings.VARNISH_PORT) - site.save() - - def check_page(self, page): - self.open_url("/") - try_normal_login(self, self.admin_user.username, self.PASSWORD_STRING) - self.open_url(page) - self.editQSD() - - self.delete_all_cookies() - self.open_url(page) - self.assertTrue(self.is_text_present(self.TEST_STRING)) - logout(self) - - try_normal_login(self, self.qsd_user.username, self.PASSWORD_STRING) - self.open_url(page) - self.editQSD() - - self.delete_all_cookies() - self.open_url(page) - self.assertTrue(self.is_text_present(self.TEST_STRING)) - - @skipUnless(hasattr(settings, 'VARNISH_HOST') and hasattr(settings, 'VARNISH_PORT'), "Varnish settings weren't set") - def test_inline(self): - self.check_page("/") - - @skipUnless(hasattr(settings, 'VARNISH_HOST') and hasattr(settings, 'VARNISH_PORT'), "Varnish settings weren't set") - def test_regular(self): - self.check_page("/test.html") + options = webdriver.FirefoxOptions() + options.add_argument("--headless") + self.selenium = webdriver.Firefox(firefox_options=options) + self.selenium.implicitly_wait(10) + + def test_qsd_editing(self): + for page in ["/", "/test.html"]: + self.selenium.get('%s%s' % (self.live_server_url, "/")) + try_normal_login(self.selenium, self.live_server_url, self.admin_user.username, self.PASSWORD_STRING) + self.selenium.get('%s%s' % (self.live_server_url, page)) + self.assertTrue(self.selenium.find_element_by_class_name("qsd_header").is_displayed(), "Admin should be able to see the QSD header on " + page) + self.editQSD() + + self.selenium.delete_all_cookies() + self.selenium.get('%s%s' % (self.live_server_url, page)) + self.assertTrue(self.TEST_STRING in self.selenium.page_source) + logout(self.selenium, self.live_server_url) + + self.selenium.get('%s%s' % (self.live_server_url, "/")) + try_normal_login(self.selenium, self.live_server_url, self.qsd_user.username, self.PASSWORD_STRING) + self.selenium.get('%s%s' % (self.live_server_url, page)) + self.assertFalse(self.selenium.find_element_by_class_name("qsd_header").is_displayed(), "Non-admin shouldn't be able to see the QSD header on " + page) + + self.selenium.delete_all_cookies() + self.selenium.get('%s%s' % (self.live_server_url, page)) + self.assertTrue(self.TEST_STRING in self.selenium.page_source) def tearDown(self): + self.selenium.quit() super(TestQsdCachePurging, self).tearDown() - self.driver.testserver_port = self._old_port diff --git a/esp/esp/qsd/tests.py b/esp/esp/qsd/tests.py index 834832a528..f06c452fc1 100644 --- a/esp/esp/qsd/tests.py +++ b/esp/esp/qsd/tests.py @@ -40,6 +40,8 @@ from django.template import Template, Context +from esp.qsd.seltests import TestQsdCachePurging # Run Selenium tests with regular tests + class QSDCorrectnessTest(TestCase): """ Tests to ensure that QSD-related caches are cleared appropriately. """ diff --git a/esp/esp/seltests/management/__init__.py b/esp/esp/seltests/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esp/esp/seltests/management/commands/__init__.py b/esp/esp/seltests/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/esp/esp/seltests/management/commands/seltest.py b/esp/esp/seltests/management/commands/seltest.py deleted file mode 100644 index 70222de6bb..0000000000 --- a/esp/esp/seltests/management/commands/seltest.py +++ /dev/null @@ -1,48 +0,0 @@ -from django_selenium.selenium_runner import SeleniumTestRunner -from optparse import make_option -from south.management.commands import patch_for_test_db_setup -from south.management.commands import test as test_south - -import logging -logger = logging.getLogger(__name__) -import sys - - -class Command(test_south.Command): - - option_list = test_south.Command.option_list + ( - make_option('--selenium-tests', action='store_true', dest='run_selenium_tests', default=False, help='Run Selenium tests only.'), - make_option('--normal-tests', action='store_true', dest='run_normal_tests', default=False, help='Run non-Selenium tests only.') - ) - - def handle(self, *test_labels, **options): - """Trigger both south and selenium tests.""" - - run_south_tests = bool(options.get('run_normal_tests', False)) - run_selenium_tests = bool(options.get('run_selenium_tests', False)) - - # Check test types - if (run_south_tests and run_selenium_tests or - not run_south_tests and not run_selenium_tests): - logger.error("You must specify exactly one of --selenium-tests and --normal-tests.") - sys.exit(1) - - # Apply the south patch for syncdb, migrate commands during tests - patch_for_test_db_setup() - - if run_south_tests: - test_south.Command.handle(self, *test_labels, **options) - elif run_selenium_tests: - # We don't want to call any super handle function, because it will - # try to run tests in addition to parsing command line options. - # Instead, we parse the relevant options ourselves. - verbosity = int(options.get('verbosity', 1)) - interactive = options.get('interactive', True) - failfast = options.get('failfast', False) - - test_runner = SeleniumTestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) - test_runner.selenium = True - test_runner.selenium_only = True - failures = test_runner.run_tests(test_labels) - if failures: - sys.exit(bool(failures)) diff --git a/esp/esp/seltests/seltests.py b/esp/esp/seltests/seltests.py index c4551d2ac0..c9a809dcbf 100644 --- a/esp/esp/seltests/seltests.py +++ b/esp/esp/seltests/seltests.py @@ -1,9 +1,10 @@ -from django_selenium.testcases import SeleniumTestCase +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver from esp.seltests.util import try_normal_login, logout from esp.users.models import ESPUser from esp.utils.models import TemplateOverride -class CsrfTestCase(SeleniumTestCase): +class CsrfTestCase(StaticLiveServerTestCase): def setUp(self): super(CsrfTestCase, self).setUp() user, created = ESPUser.objects.get_or_create(username='student', first_name='Student', last_name='Student') @@ -16,8 +17,13 @@ def setUp(self): else: to = TemplateOverride.objects.filter(name='index.html')[0] self.good_version = to.next_version() - 1 + options = webdriver.FirefoxOptions() + options.add_argument("--headless") + self.selenium = webdriver.Firefox(firefox_options=options) + self.selenium.implicitly_wait(10) def tearDown(self): + self.selenium.quit() super(CsrfTestCase, self).tearDown() if (self.good_version > 1): # Tear down the template override for consistent behavior @@ -44,16 +50,18 @@ def setUpNormalLogin(self): def test_csrf_delete(self): # Now set up and test normal login self.setUpNormalLogin() - self.open_url("/") # Load index + self.selenium.get('%s%s' % (self.live_server_url, '/')) # Load index - try_normal_login(self, "student", "student") - self.assertTrue(self.is_text_present('Student Student')) - logout(self) + try_normal_login(self.selenium, self.live_server_url, "student", "student") + self.assertTrue(self.selenium.find_element_by_class_name('logged_in').is_displayed()) + self.assertTrue(self.selenium.find_element_by_id('user_first_name').text == "Student") + self.assertTrue(self.selenium.find_element_by_id('user_last_name').text == "Student") + logout(self.selenium, self.live_server_url) - self.delete_cookie("esp_csrftoken") + self.selenium.delete_cookie("esp_csrftoken") - try_normal_login(self, "student", "student") - self.assertTrue(self.is_text_present('Student Student')) - logout(self) - - self.close() + try_normal_login(self.selenium, self.live_server_url, "student", "student") + self.assertTrue(self.selenium.find_element_by_class_name('logged_in').is_displayed()) + self.assertTrue(self.selenium.find_element_by_id('user_first_name').text == "Student") + self.assertTrue(self.selenium.find_element_by_id('user_last_name').text == "Student") + logout(self.selenium, self.live_server_url) diff --git a/esp/esp/seltests/tests.py b/esp/esp/seltests/tests.py index e69de29bb2..c4ea27682a 100644 --- a/esp/esp/seltests/tests.py +++ b/esp/esp/seltests/tests.py @@ -0,0 +1 @@ +from esp.seltests.seltests import CsrfTestCase # Run Selenium tests with regular tests diff --git a/esp/esp/seltests/util.py b/esp/esp/seltests/util.py index dc8072f0ae..df10b025cb 100644 --- a/esp/esp/seltests/util.py +++ b/esp/esp/seltests/util.py @@ -1,22 +1,17 @@ -from selenium.webdriver.support.ui import WebDriverWait -import time +def noActiveAjaxJQuery(selenium): + return selenium.execute_script("return $j.active == 0") -def noActiveAjaxJQuery(driver): - return driver.execute_script("return $j.active == 0") +def try_login(selenium, username, password): + username_input = selenium.find_element_by_name("username") + username_input.send_keys(username) + password_input = selenium.find_element_by_name("password") + password_input.send_keys(password) + selenium.find_element_by_id('gologin').click() -def try_login(driver, username, password): - elem = WebDriverWait(driver, 10).until( - lambda driver: driver.find_element_by_name("username")) - elem.send_keys(username) - elem = WebDriverWait(driver, 10).until( - lambda driver: driver.find_element_by_name("password")) - elem.send_keys(password) - elem.submit() +def try_normal_login(selenium, live_server_url, username, password): + try_login(selenium, username, password) + selenium.get('%s%s' % (live_server_url, "/")) -def try_normal_login(driver, username, password): - try_login(driver, username, password) - driver.open_url("/") - -def logout(driver): - driver.open_url("/myesp/signout/") - driver.open_url("/") +def logout(selenium, live_server_url): + selenium.get('%s%s' % (live_server_url, "/myesp/signout/")) + selenium.get('%s%s' % (live_server_url, "/")) diff --git a/esp/packages_base.txt b/esp/packages_base.txt index 80544bbf8b..f5a7142e06 100644 --- a/esp/packages_base.txt +++ b/esp/packages_base.txt @@ -23,3 +23,4 @@ libc-ares2 libev4 git-core libfreetype6-dev +firefox-geckodriver diff --git a/esp/requirements.txt b/esp/requirements.txt index dea6ee0645..9544a37732 100644 --- a/esp/requirements.txt +++ b/esp/requirements.txt @@ -10,7 +10,6 @@ django-form-utils==1.0.3 # Provides BetterForm (used in customforms) and BetterM django-formtools==2.1 # Used once in customforms django-localflavor==1.1 # Provides address and phone number fields django-reversion==1.10.0 # Handles versioning, currently for QSD and TemplateOverrides -django-selenium==0.9.8 # Runs selenium tests which probably don't work anymore django-sendgrid-v5==0.9.0 # Provides support for using SendGrid as the EmailBackend django-vanilla-views==1.0.4 # Provides simpler generic views, currently used only for grade change requests git+https://github.com/learning-unlimited/django-admin-tools.git@esp # Extends Django Admin @@ -31,7 +30,7 @@ pyinotify # Makes dev servers auto-reload pylibmc==1.5.0 # Talks to memcached pytz==2015.4 # Required for timezone support raven # Required for Sentry error reporting -selenium==2.44.0 # Runs selenium tests which probably don't work anymore +selenium<4 # Runs selenium tests (version 4 dropped Python 2.7 support) shortuuid==0.4.2 # django-extensions dependency stripe==1.19.1 # Required for credit card processing twilio==3.6.5 # Required for text messaging support