Rewrote selenium tests to thoroughly exercise Add Course form. Still WIP.
[oweals/karmaworld.git] / karmaworld / apps / courses / test / test_selenium.py
1 import itertools
2 from nose import with_setup
3 from selenium import webdriver
4 from selenium.webdriver.support.wait import WebDriverWait
5 from selenium.webdriver.common.by import By
6 from selenium.webdriver.common.keys import Keys
7 from selenium.webdriver.support import expected_conditions as EC
8 from django.test import LiveServerTestCase
9 from karmaworld.apps.courses.models import *
10 import uuid
11
12
13 class AddCourseTest(LiveServerTestCase):
14     """Tests the Add Course form."""
15
16     class FieldAction:
17         def __init__(self, field, value, autocomplete, error_expected):
18             self.field = field
19             self.value = value
20             self.autocomplete = autocomplete
21             self.error_expected = error_expected
22
23         def __str__(self):
24             return "{f}: {v}".format(f=self.field, v=self.value)
25
26     class CompositeFieldAction:
27         def __init__(self, subactions, error_expected):
28             self.subactions = subactions
29             self.error_expected = error_expected
30
31     SCHOOL_FIELD_NAME = 'DepartmentForm-school_text'
32     COURSE_FIELD_NAME = 'CourseForm-name'
33     DEPARTMENT_FIELD_NAME = 'DepartmentForm-name_text'
34     PROFESSOR_FIELD_NAME = 'ProfessorForm-name_text'
35     PROFESSOR_EMAIL_FIELD_NAME = 'ProfessorForm-email_text'
36     COURSE_URL_FIELD_NAME = 'CourseForm-url'
37     HONEYPOT_FIELD_NAME = 'CourseForm-instruction_url'
38
39     EXAMPLE_SCHOOL = 'Northeastern University'
40     EXAMPLE_NEW_DEPARTMENT = 'College of Arts and Sciences'
41     EXAMPLE_EXISTING_DEPARTMENT = 'College of Computer and Information Sciences'
42     EXAMPLE_NEW_PROFESSOR = 'Prof. X'
43     EXAMPLE_NEW_PROFESSOR_EMAIL = 'xavier@xmen.edu'
44     EXAMPLE_EXISTING_PROFESSOR = 'Prof. Einstein'
45     EXAMPLE_EXISTING_PROFESSOR_EMAIL = 'einstein@princeton.edu'
46     EXAMPLE_NEW_COURSE_NAME = 'Algorithms and Data'
47     EXAMPLE_EXISTING_COURSE_NAME = 'Theory of Computation'
48     EXAMPLE_NEW_COURSE_URL = 'http://neu.edu/stuff'
49     EXAMPLE_HONEYPOT_VALUE = 'Free drugs!'
50
51     SCHOOL_FIELD_OPTIONS = (
52         # Choose a school from autocomplete
53         FieldAction(SCHOOL_FIELD_NAME, EXAMPLE_SCHOOL, autocomplete=True, error_expected=False),
54         # Type in a school without choosing from autocomplete
55         FieldAction(SCHOOL_FIELD_NAME, EXAMPLE_SCHOOL, autocomplete=False, error_expected=True),
56         # Empty school name
57         FieldAction(SCHOOL_FIELD_NAME, '', autocomplete=False, error_expected=True)
58     )
59
60     DEPARTMENT_FIELD_OPTIONS = (
61         # Type in a new department
62         FieldAction(DEPARTMENT_FIELD_NAME, EXAMPLE_NEW_DEPARTMENT, autocomplete=False, error_expected=False),
63         # Choose an existing department
64         FieldAction(DEPARTMENT_FIELD_NAME, EXAMPLE_EXISTING_DEPARTMENT, autocomplete=True, error_expected=False)
65     )
66
67     PROFESSOR_FIELDS_OPTIONS = (
68         # New name and email typed in
69         CompositeFieldAction([
70             FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_NEW_PROFESSOR, autocomplete=False, error_expected=False),
71             FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_NEW_PROFESSOR_EMAIL, autocomplete=False, error_expected=False)],
72                              error_expected=False),
73         # existing Professor Name selected, matching Professor Email selected
74         CompositeFieldAction([
75             FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR, autocomplete=True, error_expected=False),
76             FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR_EMAIL, autocomplete=True, error_expected=False)],
77                              error_expected=False),
78         # existing Professor Name selected, no Email selected
79         CompositeFieldAction([
80             FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR, autocomplete=True, error_expected=False)],
81                              error_expected=False),
82         # existing Professor Email selected, no Name selected
83         CompositeFieldAction([
84             FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR_EMAIL, autocomplete=True, error_expected=False)],
85                              error_expected=False),
86         # existing Professor Name selected, different Professor Email selected
87         CompositeFieldAction([
88             FieldAction(PROFESSOR_FIELD_NAME, EXAMPLE_EXISTING_PROFESSOR, autocomplete=False, error_expected=False),
89             FieldAction(PROFESSOR_EMAIL_FIELD_NAME, EXAMPLE_NEW_PROFESSOR_EMAIL, autocomplete=False, error_expected=False)],
90                              error_expected=True),
91     )
92
93     COURSE_FIELD_OPTIONS = (
94         # new course name
95         FieldAction(COURSE_FIELD_NAME, EXAMPLE_NEW_COURSE_NAME, autocomplete=False, error_expected=False),
96         # empty course name
97         FieldAction(COURSE_FIELD_NAME, '', autocomplete=False, error_expected=True)
98     )
99
100     COURSE_URL_OPTIONS = (
101         # no URL
102         FieldAction(COURSE_URL_FIELD_NAME, '', autocomplete=False, error_expected=False),
103         # URL given
104         FieldAction(COURSE_URL_FIELD_NAME, EXAMPLE_NEW_COURSE_URL, autocomplete=False, error_expected=False)
105     )
106
107     HONEYPOT_FIELD_OPTIONS = (
108         # something in the honeypot
109         FieldAction(HONEYPOT_FIELD_NAME, EXAMPLE_HONEYPOT_VALUE, autocomplete=False, error_expected=True),
110         # nothing in the honeypot
111         FieldAction(HONEYPOT_FIELD_NAME, '', autocomplete=False, error_expected=False)
112     )
113
114     @classmethod
115     def setUpClass(cls):
116         cls.driver = webdriver.Firefox()
117         cls.driver.implicitly_wait(3)
118         cls.wait = WebDriverWait(cls.driver, 200)
119         super(AddCourseTest, cls).setUpClass()
120
121     @classmethod
122     def tearDownClass(cls):
123         cls.driver.quit()
124         super(AddCourseTest, cls).tearDownClass()
125
126     def set_up(self):
127         self.northeastern = School.objects.create(name=self.EXAMPLE_SCHOOL, usde_id=33333)
128         self.department = Department.objects.create(name=self.EXAMPLE_EXISTING_DEPARTMENT, school=self.northeastern)
129         self.professor = Professor.objects.create(name=self.EXAMPLE_EXISTING_PROFESSOR, email=self.EXAMPLE_EXISTING_PROFESSOR_EMAIL)
130         self.course = Course.objects.create(name=self.EXAMPLE_EXISTING_COURSE_NAME, department=self.department)
131
132     def tear_down(self):
133         School.objects.all().delete()
134         Department.objects.all().delete()
135         Professor.objects.all().delete()
136         Course.objects.all().delete()
137
138     def select_autocomplete(self, name, keys):
139         """Type in the characters into the given field, and then choose
140         the first item in the autocomplete menu that appears."""
141         input = self.driver.find_element_by_name(name)
142         input.send_keys(keys)
143         self.wait.until(EC.element_to_be_clickable(
144             (By.XPATH, "//ul[contains(@style,'display: block')]/li[contains(@class,'ui-menu-item')][1]")))
145         input.send_keys(Keys.DOWN)
146         autocomplete_menu_item = self.driver.find_element_by_id("ui-active-menuitem")
147         autocomplete_menu_item.click()
148
149     @staticmethod
150     def flatten_actions(actions):
151         results = []
152         for action in actions:
153             if isinstance(action, AddCourseTest.CompositeFieldAction):
154                 results.extend(AddCourseTest.flatten_actions(action.subactions))
155             else:
156                 results.append(action)
157
158         return results
159
160     def fill_out_form(self, field_actions):
161         """Fill out the Add Course form with the given actions."""
162         add_course_button = self.driver.find_element_by_id("add-course-btn")
163         add_course_button.click()
164         self.driver.execute_script("javascript:window.scrollBy(0,200)")
165
166         for action in AddCourseTest.flatten_actions(field_actions):
167             if action.autocomplete:
168                 self.select_autocomplete(action.field, action.value)
169             else:
170                 field = self.driver.find_element_by_name(action.field)
171                 field.send_keys(action.value)
172
173         # Click "Save"
174         save_button = self.driver.find_element_by_id("save-btn")
175         save_button.click()
176
177     def expect_error(self, actions):
178         result = False
179         for action in actions:
180             if isinstance(action, AddCourseTest.CompositeFieldAction) and \
181                     self.expect_error(action.subactions):
182                 result = True
183             elif action.error_expected:
184                 result = True
185
186         return result
187
188     def check_object_exists(self, value, field, exists=True):
189         desired_count = 1 if exists else 0
190
191         if field == self.COURSE_FIELD_NAME:
192             self.assertEqual(Course.objects.filter(name=value).count(), desired_count)
193         elif field == self.DEPARTMENT_FIELD_NAME:
194             self.assertEqual(Department.objects.filter(name=value).count(), desired_count)
195         elif field == self.PROFESSOR_FIELD_NAME:
196             self.assertEqual(Professor.objects.filter(name=value).count(), desired_count)
197         elif field == self.PROFESSOR_EMAIL_FIELD_NAME:
198             self.assertEqual(Professor.objects.filter(email=value).count(), desired_count)
199         elif field == self.COURSE_URL_FIELD_NAME:
200             self.assertEqual(Course.objects.filter(url=value).count(), desired_count)
201
202     def actions_test_and_check(self, actions):
203         self.set_up()
204         self.driver.get(self.live_server_url)
205         error_expected = self.expect_error(actions)
206         self.fill_out_form(actions)
207         if error_expected:
208             for action in AddCourseTest.flatten_actions(actions):
209                 self.check_object_exists(action.value, action.field, exists=False)
210         else:
211             for action in AddCourseTest.flatten_actions(actions):
212                 self.check_object_exists(action.value, action.field, exists=True)
213         self.tear_down()
214
215     def test_combinations(self):
216         field_options_combinations = itertools.product(self.SCHOOL_FIELD_OPTIONS,
217                                                        self.DEPARTMENT_FIELD_OPTIONS,
218                                                        self.PROFESSOR_FIELDS_OPTIONS,
219                                                        self.COURSE_FIELD_OPTIONS,
220                                                        self.COURSE_URL_OPTIONS,
221                                                        self.HONEYPOT_FIELD_OPTIONS)
222
223         for actions in field_options_combinations:
224             self.actions_test_and_check(actions)