Merge pull request #5959 from edx/will/logistration-selenium
Logistration: Bok-choy tests for the combined login/registration page.
This commit is contained in:
@@ -3,7 +3,7 @@ Course about page (with registration button)
|
||||
"""
|
||||
|
||||
from .course_page import CoursePage
|
||||
from .register import RegisterPage
|
||||
from .login_and_register import RegisterPage
|
||||
|
||||
|
||||
class CourseAboutPage(CoursePage):
|
||||
|
||||
223
common/test/acceptance/pages/lms/login_and_register.py
Normal file
223
common/test/acceptance/pages/lms/login_and_register.py
Normal file
@@ -0,0 +1,223 @@
|
||||
"""Login and Registration pages """
|
||||
|
||||
from urllib import urlencode
|
||||
from bok_choy.page_object import PageObject, unguarded
|
||||
from bok_choy.promise import Promise, EmptyPromise
|
||||
from . import BASE_URL
|
||||
from .dashboard import DashboardPage
|
||||
|
||||
|
||||
class RegisterPage(PageObject):
|
||||
"""
|
||||
Registration page (create a new account)
|
||||
"""
|
||||
|
||||
def __init__(self, browser, course_id):
|
||||
"""
|
||||
Course ID is currently of the form "edx/999/2013_Spring"
|
||||
but this format could change.
|
||||
"""
|
||||
super(RegisterPage, self).__init__(browser)
|
||||
self._course_id = course_id
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
URL for the registration page of a course.
|
||||
"""
|
||||
return "{base}/register?course_id={course_id}&enrollment_action={action}".format(
|
||||
base=BASE_URL,
|
||||
course_id=self._course_id,
|
||||
action="enroll",
|
||||
)
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return any([
|
||||
'register' in title.lower()
|
||||
for title in self.q(css='span.title-sub').text
|
||||
])
|
||||
|
||||
def provide_info(self, email, password, username, full_name):
|
||||
"""
|
||||
Fill in registration info.
|
||||
`email`, `password`, `username`, and `full_name` are the user's credentials.
|
||||
"""
|
||||
self.q(css='input#email').fill(email)
|
||||
self.q(css='input#password').fill(password)
|
||||
self.q(css='input#username').fill(username)
|
||||
self.q(css='input#name').fill(full_name)
|
||||
self.q(css='input#tos-yes').first.click()
|
||||
self.q(css='input#honorcode-yes').first.click()
|
||||
self.q(css="#country option[value='US']").first.click()
|
||||
|
||||
def submit(self):
|
||||
"""
|
||||
Submit registration info to create an account.
|
||||
"""
|
||||
self.q(css='button#submit').first.click()
|
||||
|
||||
# The next page is the dashboard; make sure it loads
|
||||
dashboard = DashboardPage(self.browser)
|
||||
dashboard.wait_for_page()
|
||||
return dashboard
|
||||
|
||||
|
||||
class CombinedLoginAndRegisterPage(PageObject):
|
||||
"""Interact with combined login and registration page.
|
||||
|
||||
This page is currently hidden behind the feature flag
|
||||
`ENABLE_COMBINED_LOGIN_REGISTRATION`, which is enabled
|
||||
in the bok choy settings.
|
||||
|
||||
When enabled, the new page is available from either
|
||||
`/account/login` or `/account/register`.
|
||||
|
||||
Users can reach this page while attempting to enroll
|
||||
in a course, in which case users will be auto-enrolled
|
||||
when they successfully authenticate (unless the course
|
||||
has been paywalled).
|
||||
|
||||
"""
|
||||
def __init__(self, browser, start_page="register", course_id=None):
|
||||
"""Initialize the page.
|
||||
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
|
||||
Keyword Args:
|
||||
start_page (str): Whether to start on the login or register page.
|
||||
course_id (unicode): If provided, load the page as if the user
|
||||
is trying to enroll in a course.
|
||||
|
||||
"""
|
||||
super(CombinedLoginAndRegisterPage, self).__init__(browser)
|
||||
self._course_id = course_id
|
||||
|
||||
if start_page not in ["register", "login"]:
|
||||
raise ValueError("Start page must be either 'register' or 'login'")
|
||||
self._start_page = start_page
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Return the URL for the combined login/registration page. """
|
||||
url = "{base}/account/{login_or_register}".format(
|
||||
base=BASE_URL,
|
||||
login_or_register=self._start_page
|
||||
)
|
||||
|
||||
# These are the parameters that would be included if the user
|
||||
# were trying to enroll in a course.
|
||||
if self._course_id is not None:
|
||||
url += "?{params}".format(
|
||||
params=urlencode({
|
||||
"course_id": self._course_id,
|
||||
"enrollment_action": "enroll"
|
||||
})
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""Check whether the combined login/registration page has loaded. """
|
||||
return (
|
||||
self.q(css="#register-option").is_present() and
|
||||
self.q(css="#login-option").is_present() and
|
||||
self.current_form is not None
|
||||
)
|
||||
|
||||
def toggle_form(self):
|
||||
"""Toggle between the login and registration forms. """
|
||||
old_form = self.current_form
|
||||
|
||||
# Toggle the form
|
||||
self.q(css=".form-toggle:not(:checked)").click()
|
||||
|
||||
# Wait for the form to change before returning
|
||||
EmptyPromise(
|
||||
lambda: self.current_form != old_form,
|
||||
"Finish toggling to the other form"
|
||||
).fulfill()
|
||||
|
||||
def register(self, email="", password="", username="", full_name="", country="", terms_of_service=False):
|
||||
"""Fills in and submits the registration form.
|
||||
|
||||
Requires that the "register" form is visible.
|
||||
This does NOT wait for the next page to load,
|
||||
so the caller should wait for the next page
|
||||
(or errors if that's the expected behavior.)
|
||||
|
||||
Keyword Arguments:
|
||||
email (unicode): The user's email address.
|
||||
password (unicode): The user's password.
|
||||
username (unicode): The user's username.
|
||||
full_name (unicode): The user's full name.
|
||||
country (unicode): Two-character country code.
|
||||
terms_of_service (boolean): If True, agree to the terms of service and honor code.
|
||||
|
||||
"""
|
||||
# Fill in the form
|
||||
self.q(css="#register-email").fill(email)
|
||||
self.q(css="#register-password").fill(password)
|
||||
self.q(css="#register-username").fill(username)
|
||||
self.q(css="#register-name").fill(full_name)
|
||||
if country:
|
||||
self.q(css="#register-country option[value='{country}']".format(country=country)).click()
|
||||
if (terms_of_service):
|
||||
self.q(css="#register-honor_code").click()
|
||||
|
||||
# Submit it
|
||||
self.q(css=".register-button").click()
|
||||
|
||||
def login(self, email="", password="", remember_me=True):
|
||||
"""Fills in and submits the login form.
|
||||
|
||||
Requires that the "login" form is visible.
|
||||
This does NOT wait for the next page to load,
|
||||
so the caller should wait for the next page
|
||||
(or errors if that's the expected behavior).
|
||||
|
||||
Keyword Arguments:
|
||||
email (unicode): The user's email address.
|
||||
password (unicode): The user's password.
|
||||
remember_me (boolean): If True, check the "remember me" box.
|
||||
|
||||
"""
|
||||
# Fill in the form
|
||||
self.q(css="#login-email").fill(email)
|
||||
self.q(css="#login-password").fill(password)
|
||||
if remember_me:
|
||||
self.q(css="#login-remember").click()
|
||||
|
||||
# Submit it
|
||||
self.q(css=".login-button").click()
|
||||
|
||||
@property
|
||||
@unguarded
|
||||
def current_form(self):
|
||||
"""Return the form that is currently visible to the user.
|
||||
|
||||
Returns:
|
||||
Either "register", "login", or "password-reset" if a valid
|
||||
form is loaded.
|
||||
|
||||
If we can't find any of these forms on the page, return None.
|
||||
|
||||
"""
|
||||
if self.q(css=".register-button").visible:
|
||||
return "register"
|
||||
elif self.q(css=".login-button").visible:
|
||||
return "login"
|
||||
elif self.q(css=".js-reset").visible:
|
||||
return "password-reset"
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
"""Return a list of errors displayed to the user. """
|
||||
return self.q(css=".submission-error li").text
|
||||
|
||||
def wait_for_errors(self):
|
||||
"""Wait for errors to be visible, then return them. """
|
||||
def _check_func():
|
||||
errors = self.errors
|
||||
return (bool(errors), errors)
|
||||
return Promise(_check_func, "Errors are visible").fulfill()
|
||||
@@ -1,61 +0,0 @@
|
||||
"""
|
||||
Registration page (create a new account)
|
||||
"""
|
||||
|
||||
from bok_choy.page_object import PageObject
|
||||
from . import BASE_URL
|
||||
from .dashboard import DashboardPage
|
||||
|
||||
|
||||
class RegisterPage(PageObject):
|
||||
"""
|
||||
Registration page (create a new account)
|
||||
"""
|
||||
|
||||
def __init__(self, browser, course_id):
|
||||
"""
|
||||
Course ID is currently of the form "edx/999/2013_Spring"
|
||||
but this format could change.
|
||||
"""
|
||||
super(RegisterPage, self).__init__(browser)
|
||||
self._course_id = course_id
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
URL for the registration page of a course.
|
||||
"""
|
||||
return "{base}/register?course_id={course_id}&enrollment_action={action}".format(
|
||||
base=BASE_URL,
|
||||
course_id=self._course_id,
|
||||
action="enroll",
|
||||
)
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return any([
|
||||
'register' in title.lower()
|
||||
for title in self.q(css='span.title-sub').text
|
||||
])
|
||||
|
||||
def provide_info(self, email, password, username, full_name):
|
||||
"""
|
||||
Fill in registration info.
|
||||
`email`, `password`, `username`, and `full_name` are the user's credentials.
|
||||
"""
|
||||
self.q(css='input#email').fill(email)
|
||||
self.q(css='input#password').fill(password)
|
||||
self.q(css='input#username').fill(username)
|
||||
self.q(css='input#name').fill(full_name)
|
||||
self.q(css='input#tos-yes').first.click()
|
||||
self.q(css='input#honorcode-yes').first.click()
|
||||
|
||||
def submit(self):
|
||||
"""
|
||||
Submit registration info to create an account.
|
||||
"""
|
||||
self.q(css='button#submit').first.click()
|
||||
|
||||
# The next page is the dashboard; make sure it loads
|
||||
dashboard = DashboardPage(self.browser)
|
||||
dashboard.wait_for_page()
|
||||
return dashboard
|
||||
@@ -5,10 +5,12 @@ End-to-end tests for the LMS.
|
||||
|
||||
from textwrap import dedent
|
||||
from unittest import skip
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
from ..helpers import UniqueCourseTest, load_data_str
|
||||
from ...pages.lms.auto_auth import AutoAuthPage
|
||||
from ...pages.common.logout import LogoutPage
|
||||
from ...pages.lms.find_courses import FindCoursesPage
|
||||
from ...pages.lms.course_about import CourseAboutPage
|
||||
from ...pages.lms.course_info import CourseInfoPage
|
||||
@@ -19,6 +21,7 @@ from ...pages.lms.dashboard import DashboardPage
|
||||
from ...pages.lms.problem import ProblemPage
|
||||
from ...pages.lms.video.video import VideoPage
|
||||
from ...pages.lms.courseware import CoursewarePage
|
||||
from ...pages.lms.login_and_register import CombinedLoginAndRegisterPage
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc, CourseUpdateDesc
|
||||
|
||||
|
||||
@@ -64,6 +67,138 @@ class RegistrationTest(UniqueCourseTest):
|
||||
self.assertIn(self.course_info['display_name'], course_names)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class LoginFromCombinedPageTest(UniqueCourseTest):
|
||||
"""Test that we can log in using the combined login/registration page. """
|
||||
|
||||
def setUp(self):
|
||||
"""Initialize the page objects and create a test course. """
|
||||
super(LoginFromCombinedPageTest, self).setUp()
|
||||
self.login_page = CombinedLoginAndRegisterPage(
|
||||
self.browser,
|
||||
start_page="login",
|
||||
course_id=self.course_id
|
||||
)
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
|
||||
# Create a course to enroll in
|
||||
CourseFixture(
|
||||
self.course_info['org'], self.course_info['number'],
|
||||
self.course_info['run'], self.course_info['display_name']
|
||||
).install()
|
||||
|
||||
def test_login_success(self):
|
||||
# Create a user account
|
||||
email, password = self._create_unique_user()
|
||||
|
||||
# Navigate to the login page and try to log in
|
||||
self.login_page.visit().login(email=email, password=password)
|
||||
|
||||
# Expect that we reach the dashboard and we're auto-enrolled in the course
|
||||
course_names = self.dashboard_page.wait_for_page().available_courses
|
||||
self.assertIn(self.course_info["display_name"], course_names)
|
||||
|
||||
def test_login_failure(self):
|
||||
# Navigate to the login page
|
||||
self.login_page.visit()
|
||||
|
||||
# User account does not exist
|
||||
self.login_page.login(email="nobody@nowhere.com", password="password")
|
||||
|
||||
# Verify that an error is displayed
|
||||
self.assertIn("Email or password is incorrect.", self.login_page.wait_for_errors())
|
||||
|
||||
def test_toggle_to_register_form(self):
|
||||
self.login_page.visit().toggle_form()
|
||||
self.assertEqual(self.login_page.current_form, "register")
|
||||
|
||||
def _create_unique_user(self):
|
||||
username = "test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = "{user}@example.com".format(user=username)
|
||||
password = "password"
|
||||
|
||||
# Create the user (automatically logs us in)
|
||||
AutoAuthPage(
|
||||
self.browser,
|
||||
username=username,
|
||||
email=email,
|
||||
password=password
|
||||
).visit()
|
||||
|
||||
# Log out
|
||||
LogoutPage(self.browser).visit()
|
||||
|
||||
return (email, password)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class RegisterFromCombinedPageTest(UniqueCourseTest):
|
||||
"""Test that we can register a new user from the combined login/registration page. """
|
||||
|
||||
def setUp(self):
|
||||
"""Initialize the page objects and create a test course. """
|
||||
super(RegisterFromCombinedPageTest, self).setUp()
|
||||
self.register_page = CombinedLoginAndRegisterPage(
|
||||
self.browser,
|
||||
start_page="register",
|
||||
course_id=self.course_id
|
||||
)
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
|
||||
# Create a course to enroll in
|
||||
CourseFixture(
|
||||
self.course_info['org'], self.course_info['number'],
|
||||
self.course_info['run'], self.course_info['display_name']
|
||||
).install()
|
||||
|
||||
def test_register_success(self):
|
||||
# Navigate to the registration page
|
||||
self.register_page.visit()
|
||||
|
||||
# Fill in the form and submit it
|
||||
username = "test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = "{user}@example.com".format(user=username)
|
||||
self.register_page.register(
|
||||
email=email,
|
||||
password="password",
|
||||
username=username,
|
||||
full_name="Test User",
|
||||
country="US",
|
||||
terms_of_service=True
|
||||
)
|
||||
|
||||
# Expect that we reach the dashboard and we're auto-enrolled in the course
|
||||
course_names = self.dashboard_page.wait_for_page().available_courses
|
||||
self.assertIn(self.course_info["display_name"], course_names)
|
||||
|
||||
def test_register_failure(self):
|
||||
# Navigate to the registration page
|
||||
self.register_page.visit()
|
||||
|
||||
# Enter a blank for the username field, which is required
|
||||
# Don't agree to the terms of service / honor code.
|
||||
# Don't specify a country code, which is required.
|
||||
username = "test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = "{user}@example.com".format(user=username)
|
||||
self.register_page.register(
|
||||
email=email,
|
||||
password="password",
|
||||
username="",
|
||||
full_name="Test User",
|
||||
terms_of_service=False
|
||||
)
|
||||
|
||||
# Verify that the expected errors are displayed.
|
||||
errors = self.register_page.wait_for_errors()
|
||||
self.assertIn(u'The Username field cannot be empty.', errors)
|
||||
self.assertIn(u'You must agree to the edX Terms of Service and Honor Code.', errors)
|
||||
self.assertIn(u'The Country field cannot be empty.', errors)
|
||||
|
||||
def test_toggle_to_login_form(self):
|
||||
self.register_page.visit().toggle_form()
|
||||
self.assertEqual(self.register_page.current_form, "login")
|
||||
|
||||
|
||||
class LanguageTest(WebAppTest):
|
||||
"""
|
||||
Tests that the change language functionality on the dashboard works
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"ENABLE_INSTRUCTOR_ANALYTICS": true,
|
||||
"ENABLE_S3_GRADE_DOWNLOADS": true,
|
||||
"ENABLE_THIRD_PARTY_AUTH": true,
|
||||
"ENABLE_COMBINED_LOGIN_REGISTRATION": true,
|
||||
"PREVIEW_LMS_BASE": "localhost:8003",
|
||||
"SUBDOMAIN_BRANDING": false,
|
||||
"SUBDOMAIN_COURSE_LISTINGS": false
|
||||
@@ -82,6 +83,17 @@
|
||||
"MEDIA_URL": "",
|
||||
"MKTG_URL_LINK_MAP": {},
|
||||
"PLATFORM_NAME": "edX",
|
||||
"REGISTRATION_EXTRA_FIELDS": {
|
||||
"level_of_education": "optional",
|
||||
"gender": "optional",
|
||||
"year_of_birth": "optional",
|
||||
"mailing_address": "optional",
|
||||
"goals": "optional",
|
||||
"honor_code": "required",
|
||||
"terms_of_service": "hidden",
|
||||
"city": "hidden",
|
||||
"country": "required"
|
||||
},
|
||||
"SEGMENT_IO_LMS": true,
|
||||
"SERVER_EMAIL": "devops@example.com",
|
||||
"SESSION_COOKIE_DOMAIN": null,
|
||||
|
||||
Reference in New Issue
Block a user