Rewrote selenium tests to thoroughly exercise Add Course form. Still WIP.
authorCharles Connell <charles@connells.org>
Sun, 16 Mar 2014 17:32:56 +0000 (13:32 -0400)
committerCharles Connell <charles@connells.org>
Sun, 16 Mar 2014 17:32:56 +0000 (13:32 -0400)
karmaworld/apps/courses/test/test_selenium.py

index 5a7720fca6147ef511a2ed23bc8787e2dc94be4f..577cbda8243572a6776eab4b0dfbcc7634a9b1b8 100644 (file)
@@ -1,4 +1,5 @@
-import time
+import itertools
+from nose import with_setup
 from selenium import webdriver
 from selenium.webdriver.support.wait import WebDriverWait
 from selenium.webdriver.common.by import By
@@ -12,17 +13,131 @@ import uuid
 class AddCourseTest(LiveServerTestCase):
     """Tests the Add Course form."""
 
-    def setUp(self):
-        self.driver = webdriver.Firefox()
-        self.driver.implicitly_wait(3)
-        self.wait = WebDriverWait(self.driver, 200)
-        self.harvard = School.objects.create(name="Harvard University", usde_id=12345)
-        self.northeastern = School.objects.create(name="Northeastern University", usde_id=33333)
-
-    def tearDown(self):
-        self.driver.close()
+    class FieldAction:
+        def __init__(self, field, value, autocomplete, error_expected):
+            self.field = field
+            self.value = value
+            self.autocomplete = autocomplete
+            self.error_expected = error_expected
+
+        def __str__(self):
+            return "{f}: {v}".format(f=self.field, v=self.value)
+
+    class CompositeFieldAction:
+        def __init__(self, subactions, error_expected):
+            self.subactions = subactions
+            self.error_expected = error_expected
+
+    SCHOOL_FIELD_NAME = 'DepartmentForm-school_text'
+    COURSE_FIELD_NAME = 'CourseForm-name'
+    DEPARTMENT_FIELD_NAME = 'DepartmentForm-name_text'
+    PROFESSOR_FIELD_NAME = 'ProfessorForm-name_text'
+    PROFESSOR_EMAIL_FIELD_NAME = 'ProfessorForm-email_text'
+    COURSE_URL_FIELD_NAME = 'CourseForm-url'
+    HONEYPOT_FIELD_NAME = 'CourseForm-instruction_url'
+
+    EXAMPLE_SCHOOL = 'Northeastern University'
+    EXAMPLE_NEW_DEPARTMENT = 'College of Arts and Sciences'
+    EXAMPLE_EXISTING_DEPARTMENT = 'College of Computer and Information Sciences'
+    EXAMPLE_NEW_PROFESSOR = 'Prof. X'
+    EXAMPLE_NEW_PROFESSOR_EMAIL = 'xavier@xmen.edu'
+    EXAMPLE_EXISTING_PROFESSOR = 'Prof. Einstein'
+    EXAMPLE_EXISTING_PROFESSOR_EMAIL = 'einstein@princeton.edu'
+    EXAMPLE_NEW_COURSE_NAME = 'Algorithms and Data'
+    EXAMPLE_EXISTING_COURSE_NAME = 'Theory of Computation'
+    EXAMPLE_NEW_COURSE_URL = 'http://neu.edu/stuff'
+    EXAMPLE_HONEYPOT_VALUE = 'Free drugs!'
+
+    SCHOOL_FIELD_OPTIONS = (
+        # Choose a school from autocomplete
+        FieldAction(SCHOOL_FIELD_NAME, EXAMPLE_SCHOOL, autocomplete=True, error_expected=False),
+        # Type in a school without choosing from autocomplete
+        FieldAction(SCHOOL_FIELD_NAME, EXAMPLE_SCHOOL, autocomplete=False, error_expected=True),
+        # Empty school name
+        FieldAction(SCHOOL_FIELD_NAME, '', autocomplete=False, error_expected=True)
+    )
+
+    DEPARTMENT_FIELD_OPTIONS = (
+        # Type in a new department
+        FieldAction(DEPARTMENT_FIELD_NAME, EXAMPLE_NEW_DEPARTMENT, autocomplete=False, error_expected=False),
+        # Choose an existing department
+        FieldAction(DEPARTMENT_FIELD_NAME, EXAMPLE_EXISTING_DEPARTMENT, autocomplete=True, error_expected=False)
+    )
+
+    PROFESSOR_FIELDS_OPTIONS = (
+        # New name and email typed in
+        CompositeFieldAction([
+            FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_NEW_PROFESSOR, autocomplete=False, error_expected=False),
+            FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_NEW_PROFESSOR_EMAIL, autocomplete=False, error_expected=False)],
+                             error_expected=False),
+        # existing Professor Name selected, matching Professor Email selected
+        CompositeFieldAction([
+            FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR, autocomplete=True, error_expected=False),
+            FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR_EMAIL, autocomplete=True, error_expected=False)],
+                             error_expected=False),
+        # existing Professor Name selected, no Email selected
+        CompositeFieldAction([
+            FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR, autocomplete=True, error_expected=False)],
+                             error_expected=False),
+        # existing Professor Email selected, no Name selected
+        CompositeFieldAction([
+            FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR_EMAIL, autocomplete=True, error_expected=False)],
+                             error_expected=False),
+        # existing Professor Name selected, different Professor Email selected
+        CompositeFieldAction([
+            FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR, autocomplete=False, error_expected=False),
+            FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_NEW_PROFESSOR_EMAIL, autocomplete=False, error_expected=False)],
+                             error_expected=True),
+    )
+
+    COURSE_FIELD_OPTIONS = (
+        # new course name
+        FieldAction(COURSE_FIELD_NAME, EXAMPLE_NEW_COURSE_NAME, autocomplete=False, error_expected=False),
+        # empty course name
+        FieldAction(COURSE_FIELD_NAME, '', autocomplete=False, error_expected=True)
+    )
+
+    COURSE_URL_OPTIONS = (
+        # no URL
+        FieldAction(COURSE_URL_FIELD_NAME, '', autocomplete=False, error_expected=False),
+        # URL given
+        FieldAction(COURSE_URL_FIELD_NAME, EXAMPLE_NEW_COURSE_URL, autocomplete=False, error_expected=False)
+    )
+
+    HONEYPOT_FIELD_OPTIONS = (
+        # something in the honeypot
+        FieldAction(HONEYPOT_FIELD_NAME, EXAMPLE_HONEYPOT_VALUE, autocomplete=False, error_expected=True),
+        # nothing in the honeypot
+        FieldAction(HONEYPOT_FIELD_NAME, '', autocomplete=False, error_expected=False)
+    )
+
+    @classmethod
+    def setUpClass(cls):
+        cls.driver = webdriver.Firefox()
+        cls.driver.implicitly_wait(3)
+        cls.wait = WebDriverWait(cls.driver, 200)
+        super(AddCourseTest, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.driver.quit()
+        super(AddCourseTest, cls).tearDownClass()
+
+    def set_up(self):
+        self.northeastern = School.objects.create(name=self.EXAMPLE_SCHOOL, usde_id=33333)
+        self.department = Department.objects.create(name=self.EXAMPLE_EXISTING_DEPARTMENT, school=self.northeastern)
+        self.professor = Professor.objects.create(name=self.EXAMPLE_EXISTING_PROFESSOR, email=self.EXAMPLE_EXISTING_PROFESSOR_EMAIL)
+        self.course = Course.objects.create(name=self.EXAMPLE_EXISTING_COURSE_NAME, department=self.department)
+
+    def tear_down(self):
+        School.objects.all().delete()
+        Department.objects.all().delete()
+        Professor.objects.all().delete()
+        Course.objects.all().delete()
 
     def select_autocomplete(self, name, keys):
+        """Type in the characters into the given field, and then choose
+        the first item in the autocomplete menu that appears."""
         input = self.driver.find_element_by_name(name)
         input.send_keys(keys)
         self.wait.until(EC.element_to_be_clickable(
@@ -31,123 +146,79 @@ class AddCourseTest(LiveServerTestCase):
         autocomplete_menu_item = self.driver.find_element_by_id("ui-active-menuitem")
         autocomplete_menu_item.click()
 
-    def test_school_name(self):
-        self.driver.get(self.live_server_url)
-
-        # Click "Add Course"
-        add_course_button = self.driver.find_element_by_id("add-course-btn")
-        add_course_button.click()
-
-        # Scroll down so the autocomplete menu is in view
-        # This works around some weird failures
-        self.driver.execute_script("javascript:window.scrollBy(0,200)")
-
-        # Type in part of a school name
-        school_input = self.driver.find_element_by_name("DepartmentForm-school_text")
-        school_input.send_keys("harvard u")
-
-        # Wait for autocomplete menu to appear
-        # li.ui-menu-item:nth-child(1)
-        self.wait.until(EC.element_to_be_clickable((By.XPATH, "//ul[contains(@class,'ui-autocomplete')]/li[1]")))
-
-        # Choose the first suggestion
-        school_input.send_keys(Keys.DOWN)
-        active_item = self.driver.find_element_by_id("ui-active-menuitem")
-        active_item.click()
-
-        school_name_on_deck = self.driver.find_element_by_xpath("//div[contains(@class,'results_on_deck')][1]")
-        self.assertIn("Harvard University", school_name_on_deck.text)
-
-        school_id = self.driver.find_element_by_name("DepartmentForm-school")
-        self.assertEqual(school_id.get_attribute("value"), str(self.harvard.id))
-
-    def test_create_course(self):
-        self.driver.get(self.live_server_url)
-
-        # Click "Add Course"
-        add_course_button = self.driver.find_element_by_id("add-course-btn")
-        add_course_button.click()
-        self.driver.execute_script("javascript:window.scrollBy(0,200)")
-
-        # Choose a school
-        self.select_autocomplete("DepartmentForm-school_text", "northeastern u")
-
-        # Course name
-        new_course_name = "SELENIUM TEST COURSE " + uuid.uuid4().hex
-        course_name_input = self.driver.find_element_by_name("CourseForm-name")
-        course_name_input.send_keys(new_course_name)
-
-        # Department name
-        new_department_name = "SELENIUM TEST DEPARTMENT " + uuid.uuid4().hex
-        department_name_input = self.driver.find_element_by_name("DepartmentForm-name_text")
-        department_name_input.send_keys(new_department_name)
-
-        # Instructor name
-        new_instructor_name = "SELENIUM TEST INSTRUCTOR " + uuid.uuid4().hex
-        instructor_name_input = self.driver.find_element_by_name("ProfessorForm-name_text")
-        instructor_name_input.send_keys(new_instructor_name)
+    @staticmethod
+    def flatten_actions(actions):
+        results = []
+        for action in actions:
+            if isinstance(action, AddCourseTest.CompositeFieldAction):
+                results.extend(AddCourseTest.flatten_actions(action.subactions))
+            else:
+                results.append(action)
 
-        # Click "Save"
-        save_button = self.driver.find_element_by_id("save-btn")
-        save_button.click()
-
-        # See if we are taken to the new course page
-        self.wait.until(EC.title_contains(new_course_name))
-
-    def test_create_existing_course(self):
-        self.driver.get(self.live_server_url)
+        return results
 
-        # Click "Add Course"
+    def fill_out_form(self, field_actions):
+        """Fill out the Add Course form with the given actions."""
         add_course_button = self.driver.find_element_by_id("add-course-btn")
         add_course_button.click()
         self.driver.execute_script("javascript:window.scrollBy(0,200)")
 
-        # Choose a school
-        self.select_autocomplete("DepartmentForm-school_text", "northeastern u")
-
-        # Course name
-        new_course_name = "SELENIUM TEST COURSE " + uuid.uuid4().hex
-        course_name_input = self.driver.find_element_by_name("CourseForm-name")
-        course_name_input.send_keys(new_course_name)
-
-        # Department name
-        new_department_name = "SELENIUM TEST DEPARTMENT " + uuid.uuid4().hex
-        department_name_input = self.driver.find_element_by_name("DepartmentForm-name_text")
-        department_name_input.send_keys(new_department_name)
-
-        # Instructor name
-        new_instructor_name = "SELENIUM TEST INSTRUCTOR " + uuid.uuid4().hex
-        instructor_name_input = self.driver.find_element_by_name("ProfessorForm-name_text")
-        instructor_name_input.send_keys(new_instructor_name)
+        for action in AddCourseTest.flatten_actions(field_actions):
+            if action.autocomplete:
+                self.select_autocomplete(action.field, action.value)
+            else:
+                field = self.driver.find_element_by_name(action.field)
+                field.send_keys(action.value)
 
         # Click "Save"
         save_button = self.driver.find_element_by_id("save-btn")
         save_button.click()
 
-        # See if we are taken to the new course page
-        self.wait.until(EC.title_contains(new_course_name))
-
-        # Now go back to the home page
+    def expect_error(self, actions):
+        result = False
+        for action in actions:
+            if isinstance(action, AddCourseTest.CompositeFieldAction) and \
+                    self.expect_error(action.subactions):
+                result = True
+            elif action.error_expected:
+                result = True
+
+        return result
+
+    def check_object_exists(self, value, field, exists=True):
+        desired_count = 1 if exists else 0
+
+        if field == self.COURSE_FIELD_NAME:
+            self.assertEqual(Course.objects.filter(name=value).count(), desired_count)
+        elif field == self.DEPARTMENT_FIELD_NAME:
+            self.assertEqual(Department.objects.filter(name=value).count(), desired_count)
+        elif field == self.PROFESSOR_FIELD_NAME:
+            self.assertEqual(Professor.objects.filter(name=value).count(), desired_count)
+        elif field == self.PROFESSOR_EMAIL_FIELD_NAME:
+            self.assertEqual(Professor.objects.filter(email=value).count(), desired_count)
+        elif field == self.COURSE_URL_FIELD_NAME:
+            self.assertEqual(Course.objects.filter(url=value).count(), desired_count)
+
+    def actions_test_and_check(self, actions):
+        self.set_up()
         self.driver.get(self.live_server_url)
-
-        # Click "Add Course"
-        add_course_button = self.driver.find_element_by_id("add-course-btn")
-        add_course_button.click()
-        self.driver.execute_script("javascript:window.scrollBy(0,200)")
-
-        # Choose the SAME school
-        self.select_autocomplete("DepartmentForm-school_text", "northeastern u")
-
-        # The SAME course name
-        self.select_autocomplete("CourseForm-name", new_course_name)
-
-        # The SAME instructor name
-        self.select_autocomplete("ProfessorForm-name_text", new_instructor_name)
-
-        # The SAME department name
-        self.select_autocomplete("DepartmentForm-name_text", new_department_name)
-
-        save_button = self.driver.find_element_by_id("save-btn")
-        save_button.click()
-        self.assertEqual(Course.objects.count(), 1, "Duplicated course not created")
-
+        error_expected = self.expect_error(actions)
+        self.fill_out_form(actions)
+        if error_expected:
+            for action in AddCourseTest.flatten_actions(actions):
+                self.check_object_exists(action.value, action.field, exists=False)
+        else:
+            for action in AddCourseTest.flatten_actions(actions):
+                self.check_object_exists(action.value, action.field, exists=True)
+        self.tear_down()
+
+    def test_combinations(self):
+        field_options_combinations = itertools.product(self.SCHOOL_FIELD_OPTIONS,
+                                                       self.DEPARTMENT_FIELD_OPTIONS,
+                                                       self.PROFESSOR_FIELDS_OPTIONS,
+                                                       self.COURSE_FIELD_OPTIONS,
+                                                       self.COURSE_URL_OPTIONS,
+                                                       self.HONEYPOT_FIELD_OPTIONS)
+
+        for actions in field_options_combinations:
+            self.actions_test_and_check(actions)