Commit b8be7f5f by Will Daly

Merge pull request #510 from edx/will/selenium

Selenium (part 1)
parents 20f0f066 96dd57b3
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
ddt==0.8.0 ddt==0.8.0
django-nose==1.2 django-nose==1.2
bok_choy==0.3.1
mock==1.0.1 mock==1.0.1
moto==0.2.22 moto==0.2.22
nose==1.3.0 nose==1.3.0
......
Selenium Tests
==============
These are UI-level acceptance tests designed to be executed on an edx-platform sandbox instance.
The tests use the ``bok-choy`` library. For a tutorial, see `here`__.
__ http://bok-choy.readthedocs.org/en/latest/tutorial.html
To use the tests:
1. Install the test requirements:
.. code:: bash
cd edx-ora2
make install-test
2. Run the tests
.. code:: bash
cd edx-ora2/test/selenium
export BASE_URL=https://{USER}:{PASSWORD}@example.com
python tests.py
\ No newline at end of file
"""
Auto-auth page (used to automatically log in during testing).
"""
import re
import urllib
from bok_choy.page_object import PageObject
import os
BASE_URL = os.environ.get('BASE_URL')
class AutoAuthPage(PageObject):
"""
The automatic authorization page.
When allowed via the django settings file, visiting
this url will create a user and log them in.
"""
def __init__(self, browser, username=None, email=None, password=None, staff=None, course_id=None, roles=None):
"""
Auto-auth is an end-point for HTTP GET requests.
By default, it will create accounts with random user credentials,
but you can also specify credentials using querystring parameters.
`username`, `email`, and `password` are the user's credentials (strings)
`staff` is a boolean indicating whether the user is global staff.
`course_id` is the ID of the course to enroll the student in.
Currently, this has the form "org/number/run"
Note that "global staff" is NOT the same as course staff.
"""
super(AutoAuthPage, self).__init__(browser)
# Create query string parameters if provided
self._params = {}
if username is not None:
self._params['username'] = username
if email is not None:
self._params['email'] = email
if password is not None:
self._params['password'] = password
if staff is not None:
self._params['staff'] = "true" if staff else "false"
if course_id is not None:
self._params['course_id'] = course_id
if roles is not None:
self._params['roles'] = roles
@property
def url(self):
"""
Construct the URL.
"""
url = BASE_URL + "/auto_auth"
query_str = urllib.urlencode(self._params)
if query_str:
url += "?" + query_str
return url
def is_browser_on_page(self):
message = self.q(css='BODY').text[0]
match = re.search(r'Logged in user ([^$]+) with password ([^$]+) and user_id ([^$]+)$', message)
return True if match else False
def get_user_id(self):
"""
Finds and returns the user_id
"""
message = self.q(css='BODY').text[0].strip()
match = re.search(r' user_id ([^$]+)$', message)
return match.groups()[0] if match else None
"""
Page objects for UI-level acceptance tests.
"""
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise
import os
BASE_URL = os.environ.get('BASE_URL')
assert BASE_URL is not None, 'No base URL specified - please set the `BASE_URL` environment variable'
class OpenAssessmentPage(PageObject):
"""
Base class for ORA page objects.
"""
def __init__(self, browser, problem_location):
"""
Configure a page object for a particular ORA problem.
Args:
browser (Selenium browser): The browser object used by the tests.
problem_location (unicode): URL path for the problem, appended to the base URL.
"""
super(OpenAssessmentPage, self).__init__(browser)
self._problem_location = problem_location
@property
def url(self):
return "{base}/{loc}".format(
base=BASE_URL,
loc=self._problem_location
)
def submit(self):
"""
Click the submit button on the page.
This relies on the fact that we use the same CSS styles for submit buttons
in all problem steps.
"""
EmptyPromise(
lambda: 'is--disabled' not in " ".join(self.q(css=".action--submit").attrs('class')),
"Submit button is enabled."
).fulfill()
with self.handle_alert():
self.q(css=".action--submit").first.click()
class SubmissionPage(OpenAssessmentPage):
"""
Page object representing the "submission" step in an ORA problem.
"""
def is_browser_on_page(self):
return self.q(css='#openassessment__response').is_present()
def submit_response(self, response_text):
"""
Submit a response for the problem.
Args:
response_text (unicode): The submission response text.
Raises:
BrokenPromise: The response was not submitted successfully.
"""
self.q(css="textarea#submission__answer__value").fill(response_text)
self.submit()
EmptyPromise(lambda: self.has_submitted, 'Response is completed').fulfill()
@property
def has_submitted(self):
"""
Check whether the response was submitted successfully.
Returns:
bool
"""
return self.q(css=".step--response.is--complete").is_present()
class SelfAssessmentPage(OpenAssessmentPage):
"""
Page object representing the "self assessment" step in an ORA problem.
"""
def is_browser_on_page(self):
return self.q(css="#openassessment__self-assessment").is_present()
def assess(self, options_selected):
"""
Create a self-assessment.
Args:
options_selected (list of int): list of the indices (starting from 0)
of each option to select in the rubric.
Example usage:
>>> self_page.assess([0, 2, 1])
"""
for criterion_num, option_num in enumerate(options_selected):
sel = "#assessment__rubric__question--{criterion_num}__{option_num}".format(
criterion_num=criterion_num,
option_num=option_num
)
self.q(css=sel).first.click()
self.submit()
EmptyPromise(lambda: self.has_submitted, 'Self assessment is complete').fulfill()
@property
def response_text(self):
"""
Retrieve the text of the response shown in the assessment.
Returns:
unicode
"""
return u" ".join(self.q(css=".self-assessment__display__response>p").text)
@property
def has_submitted(self):
"""
Check whether the assessment was submitted successfully.
Returns:
bool
"""
return self.q(css=".step--self-assessment.is--complete").is_present()
class GradePage(OpenAssessmentPage):
"""
Page object representing the "grade" step in an ORA problem.
"""
def is_browser_on_page(self):
return self.q(css="#openassessment__grade").is_present()
@property
def score(self):
"""
Retrieve the number of points received.
Returns:
int or None
Raises:
ValueError if the score is not an integer.
"""
score_candidates = [int(x) for x in self.q(css=".grade__value__earned").text]
return score_candidates[0] if len(score_candidates) > 0 else None
"""
UI-level acceptance tests for OpenAssessment.
"""
import unittest
from bok_choy.web_app_test import WebAppTest
from auto_auth import AutoAuthPage
from pages import (
SubmissionPage, SelfAssessmentPage, GradePage
)
class OpenAssessmentTest(WebAppTest):
"""
UI-level acceptance tests for Open Assessment.
"""
TEST_COURSE_ID = "ora2/1/1"
PROBLEM_LOCATIONS = {
'self_only': u'courses/ora2/1/1/courseware/a4dfec19cf9b4a6fb5b18be6ccd9cecc/338a4affb58a45459629e0566291381e/',
}
SUBMISSION = u"This is a test submission."
OPTIONS_SELECTED = [1, 2]
EXPECTED_SCORE = 6
def setUp(self):
"""
Create an account registered for the test course and log in.
"""
super(OpenAssessmentTest, self).setUp()
AutoAuthPage(self.browser, course_id=self.TEST_COURSE_ID).visit()
def test_self_assessment(self):
"""
Test the self-only flow.
"""
submission_page = SubmissionPage(
self.browser,
self.PROBLEM_LOCATIONS['self_only']
).visit()
submission_page.submit_response(self.SUBMISSION)
self.assertTrue(submission_page.has_submitted)
self_assessment_page = SelfAssessmentPage(
self.browser,
self.PROBLEM_LOCATIONS['self_only']
).wait_for_page()
self.assertIn(self.SUBMISSION, self_assessment_page.response_text)
self_assessment_page.assess(self.OPTIONS_SELECTED)
self.assertTrue(self_assessment_page.has_submitted)
grade_page = GradePage(
self.browser,
self.PROBLEM_LOCATIONS['self_only']
).wait_for_page()
self.assertEqual(grade_page.score, self.EXPECTED_SCORE)
if __name__ == "__main__":
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment