Bok Choy tests for the split payment and verification flow
Adds mode creation endpoint to course_modes app and ability to bypass media device access prompt when proceeding through verification flow in a testing environment.
This commit is contained in:
71
common/test/acceptance/pages/lms/create_mode.py
Normal file
71
common/test/acceptance/pages/lms/create_mode.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Mode creation page (used to add modes to courses during testing)."""
|
||||
|
||||
import re
|
||||
import urllib
|
||||
|
||||
from bok_choy.page_object import PageObject
|
||||
from . import BASE_URL
|
||||
|
||||
|
||||
class ModeCreationPage(PageObject):
|
||||
"""The mode creation page.
|
||||
|
||||
When allowed by the Django settings file, visiting this page allows modes to be
|
||||
created for an existing course.
|
||||
"""
|
||||
|
||||
def __init__(self, browser, course_id, mode_slug=None, mode_display_name=None, min_price=None, suggested_prices=None, currency=None):
|
||||
"""The mode creation page is an endpoint for HTTP GET requests.
|
||||
|
||||
By default, it will create an 'honor' mode for the given course with display name
|
||||
'Honor Code', a minimum price of 0, no suggested prices, and using USD as the currency.
|
||||
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
course_id (unicode): The ID of the course for which modes are to be created.
|
||||
|
||||
Keyword Arguments:
|
||||
mode_slug (str): The mode to add, either 'honor', 'verified', or 'professional'
|
||||
mode_display_name (str): Describes the new course mode
|
||||
min_price (int): The minimum price a user must pay to enroll in the new course mode
|
||||
suggested_prices (str): Comma-separated prices to suggest to the user.
|
||||
currency (str): The currency in which to list prices.
|
||||
"""
|
||||
super(ModeCreationPage, self).__init__(browser)
|
||||
|
||||
self.course_id = course_id
|
||||
self._parameters = {}
|
||||
|
||||
if mode_slug is not None:
|
||||
self._parameters['mode_slug'] = mode_slug
|
||||
|
||||
if mode_display_name is not None:
|
||||
self._parameters['mode_display_name'] = mode_display_name
|
||||
|
||||
if min_price is not None:
|
||||
self._parameters['min_price'] = min_price
|
||||
|
||||
if suggested_prices is not None:
|
||||
self._parameters['suggested_prices'] = suggested_prices
|
||||
|
||||
if currency is not None:
|
||||
self._parameters['currency'] = currency
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Construct the mode creation URL."""
|
||||
url = '{base}/course_modes/create_mode/{course_id}'.format(
|
||||
base=BASE_URL,
|
||||
course_id=self.course_id
|
||||
)
|
||||
|
||||
query_string = urllib.urlencode(self._parameters)
|
||||
if query_string:
|
||||
url += '?' + query_string
|
||||
|
||||
return url
|
||||
|
||||
def is_browser_on_page(self):
|
||||
message = self.q(css='BODY').text[0]
|
||||
match = re.search(r'Mode ([^$]+) created for course with ID ([^$]+).$', message)
|
||||
return True if match else False
|
||||
@@ -13,8 +13,32 @@ class DashboardPage(PageObject):
|
||||
Student dashboard, where the student can view
|
||||
courses she/he has registered for.
|
||||
"""
|
||||
def __init__(self, browser, separate_verified=False):
|
||||
"""Initialize the page.
|
||||
|
||||
url = BASE_URL + "/dashboard"
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
|
||||
Keyword Arguments:
|
||||
separate_verified (Boolean): Whether to use the split payment and
|
||||
verification flow.
|
||||
"""
|
||||
super(DashboardPage, self).__init__(browser)
|
||||
|
||||
if separate_verified:
|
||||
self._querystring = "?separate-verified=1"
|
||||
else:
|
||||
self._querystring = "?disable-separate-verified=1"
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Return the URL corresponding to the dashboard."""
|
||||
url = "{base}/dashboard{querystring}".format(
|
||||
base=BASE_URL,
|
||||
querystring=self._querystring
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='section.my-courses').present
|
||||
@@ -44,6 +68,60 @@ class DashboardPage(PageObject):
|
||||
|
||||
return self.q(css='section.info > hgroup > h3 > a').map(_get_course_name).results
|
||||
|
||||
def get_enrollment_mode(self, course_name):
|
||||
"""Get the enrollment mode for a given course on the dashboard.
|
||||
|
||||
Arguments:
|
||||
course_name (str): The name of the course whose mode should be retrieved.
|
||||
|
||||
Returns:
|
||||
String, indicating the enrollment mode for the course corresponding to
|
||||
the provided course name.
|
||||
|
||||
Raises:
|
||||
Exception, if no course with the provided name is found on the dashboard.
|
||||
"""
|
||||
# Filter elements by course name, only returning the relevant course item
|
||||
course_listing = self.q(css=".course").filter(lambda el: course_name in el.text).results
|
||||
|
||||
if course_listing:
|
||||
# There should only be one course listing for the provided course name.
|
||||
# Since 'ENABLE_VERIFIED_CERTIFICATES' is true in the Bok Choy settings, we
|
||||
# can expect two classes to be present on <article> elements, one being 'course'
|
||||
# and the other being the enrollment mode.
|
||||
enrollment_mode = course_listing[0].get_attribute('class').split('course ')[1]
|
||||
else:
|
||||
raise Exception("No course named {} was found on the dashboard".format(course_name))
|
||||
|
||||
return enrollment_mode
|
||||
|
||||
def upgrade_enrollment(self, course_name, upgrade_page):
|
||||
"""Interact with the upgrade button for the course with the provided name.
|
||||
|
||||
Arguments:
|
||||
course_name (str): The name of the course whose mode should be checked.
|
||||
upgrade_page (PageObject): The page to wait on after clicking the upgrade button. Importing
|
||||
the definition of PaymentAndVerificationFlow results in a circular dependency.
|
||||
|
||||
Raises:
|
||||
Exception, if no enrollment corresponding to the provided course name appears
|
||||
on the dashboard.
|
||||
"""
|
||||
# Filter elements by course name, only returning the relevant course item
|
||||
course_listing = self.q(css=".course").filter(lambda el: course_name in el.text).results
|
||||
|
||||
if course_listing:
|
||||
# There should only be one course listing corresponding to the provided course name.
|
||||
el = course_listing[0]
|
||||
|
||||
# Expand the upsell copy and click the upgrade button
|
||||
el.find_element_by_css_selector('.message-upsell').click()
|
||||
el.find_element_by_css_selector('#upgrade-to-verified').click()
|
||||
|
||||
upgrade_page.wait_for_page()
|
||||
else:
|
||||
raise Exception("No enrollment for {} is visible on the dashboard.".format(course_name))
|
||||
|
||||
def view_course(self, course_id):
|
||||
"""
|
||||
Go to the course with `course_id` (e.g. edx/Open_DemoX/edx_demo_course)
|
||||
|
||||
168
common/test/acceptance/pages/lms/pay_and_verify.py
Normal file
168
common/test/acceptance/pages/lms/pay_and_verify.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""Payment and verification pages"""
|
||||
|
||||
import re
|
||||
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 PaymentAndVerificationFlow(PageObject):
|
||||
"""Interact with the split payment and verification flow.
|
||||
|
||||
These pages are currently hidden behind the feature flag
|
||||
`SEPARATE_VERIFICATION_FROM_PAYMENT`, which is enabled in
|
||||
the Bok Choy settings.
|
||||
|
||||
When enabled, the flow can be accessed at the following URLs:
|
||||
`/verify_student/start-flow/{course}/`
|
||||
`/verify_student/upgrade/{course}/`
|
||||
`/verify_student/verify-now/{course}/`
|
||||
`/verify_student/verify-later/{course}/`
|
||||
`/verify_student/payment-confirmation/{course}/`
|
||||
|
||||
Users can reach the flow when attempting to enroll in a course's verified
|
||||
mode, either directly from the track selection page, or by upgrading from
|
||||
the honor mode. Users can also reach the flow when attempting to complete
|
||||
a deferred verification, or when attempting to view a receipt corresponding
|
||||
to an earlier payment.
|
||||
"""
|
||||
def __init__(self, browser, course_id, entry_point='start-flow'):
|
||||
"""Initialize the page.
|
||||
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
course_id (unicode): The course in which the user is enrolling.
|
||||
|
||||
Keyword Arguments:
|
||||
entry_point (str): Where to begin the flow; must be one of 'start-flow',
|
||||
'upgrade', 'verify-now', verify-later', or 'payment-confirmation'.
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
"""
|
||||
super(PaymentAndVerificationFlow, self).__init__(browser)
|
||||
self._course_id = course_id
|
||||
|
||||
if entry_point not in ['start-flow', 'upgrade', 'verify-now', 'verify-later', 'payment-confirmation']:
|
||||
raise ValueError(
|
||||
"Entry point must be either 'start-flow', 'upgrade', 'verify-now', 'verify-later', or 'payment-confirmation'."
|
||||
)
|
||||
self._entry_point = entry_point
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Return the URL corresponding to the initial position in the flow."""
|
||||
url = "{base}/verify_student/{entry_point}/{course}".format(
|
||||
base=BASE_URL,
|
||||
entry_point=self._entry_point,
|
||||
course=self._course_id
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""Check if a step in the payment and verification flow has loaded."""
|
||||
return (
|
||||
self.q(css="div .make-payment-step").is_present() or
|
||||
self.q(css="div .payment-confirmation-step").is_present() or
|
||||
self.q(css="div .face-photo-step").is_present() or
|
||||
self.q(css="div .id-photo-step").is_present() or
|
||||
self.q(css="div .review-photos-step").is_present() or
|
||||
self.q(css="div .enrollment-confirmation-step").is_present()
|
||||
)
|
||||
|
||||
def indicate_contribution(self):
|
||||
"""Interact with the radio buttons appearing on the first page of the upgrade flow."""
|
||||
self.q(css=".contribution-option > input").first.click()
|
||||
|
||||
def proceed_to_payment(self):
|
||||
"""Interact with the payment button."""
|
||||
self.q(css="#pay_button").click()
|
||||
|
||||
FakePaymentPage(self.browser, self._course_id).wait_for_page()
|
||||
|
||||
def immediate_verification(self):
|
||||
"""Interact with the immediate verification button."""
|
||||
self.q(css="#verify_now_button").click()
|
||||
|
||||
PaymentAndVerificationFlow(self.browser, self._course_id, entry_point='verify-now').wait_for_page()
|
||||
|
||||
def defer_verification(self):
|
||||
"""Interact with the link allowing the user to defer their verification."""
|
||||
self.q(css="#verify_later_button").click()
|
||||
|
||||
DashboardPage(self.browser).wait_for_page()
|
||||
|
||||
def webcam_capture(self):
|
||||
"""Interact with a webcam capture button."""
|
||||
self.q(css="#webcam_capture_button").click()
|
||||
|
||||
def _check_func():
|
||||
next_step_button_classes = self.q(css="#next_step_button").attrs('class')
|
||||
next_step_button_enabled = 'is-disabled' not in next_step_button_classes
|
||||
return (next_step_button_enabled, next_step_button_classes)
|
||||
|
||||
# Check that the #next_step_button is enabled before returning control to the caller
|
||||
Promise(_check_func, "The 'Next Step' button is enabled.").fulfill()
|
||||
|
||||
def next_verification_step(self, next_page_object):
|
||||
"""Interact with the 'Next' step button found in the verification flow."""
|
||||
self.q(css="#next_step_button").click()
|
||||
|
||||
next_page_object.wait_for_page()
|
||||
|
||||
def go_to_dashboard(self):
|
||||
"""Interact with the link to the dashboard appearing on the enrollment confirmation page."""
|
||||
if self.q(css="div .enrollment-confirmation-step").is_present():
|
||||
self.q(css=".action-primary").click()
|
||||
else:
|
||||
raise Exception("The dashboard can only be accessed from the enrollment confirmation.")
|
||||
|
||||
DashboardPage(self.browser, separate_verified=True).wait_for_page()
|
||||
|
||||
|
||||
class FakePaymentPage(PageObject):
|
||||
"""Interact with the fake payment endpoint.
|
||||
|
||||
This page is hidden behind the feature flag `ENABLE_PAYMENT_FAKE`,
|
||||
which is enabled in the Bok Choy env settings.
|
||||
|
||||
Configuring this payment endpoint also requires configuring the Bok Choy
|
||||
auth settings with the following:
|
||||
|
||||
"CC_PROCESSOR_NAME": "CyberSource2",
|
||||
"CC_PROCESSOR": {
|
||||
"CyberSource2": {
|
||||
"SECRET_KEY": <string>,
|
||||
"ACCESS_KEY": <string>,
|
||||
"PROFILE_ID": "edx",
|
||||
"PURCHASE_ENDPOINT": "/shoppingcart/payment_fake"
|
||||
}
|
||||
}
|
||||
"""
|
||||
def __init__(self, browser, course_id):
|
||||
"""Initialize the page.
|
||||
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
course_id (unicode): The course in which the user is enrolling.
|
||||
"""
|
||||
super(FakePaymentPage, self).__init__(browser)
|
||||
self._course_id = course_id
|
||||
|
||||
url = BASE_URL + "/shoppingcart/payment_fake/"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""Check if a step in the payment and verification flow has loaded."""
|
||||
message = self.q(css='BODY').text[0]
|
||||
match = re.search('Payment page', message)
|
||||
return True if match else False
|
||||
|
||||
def submit_payment(self):
|
||||
"""Interact with the payment submission button."""
|
||||
self.q(css="input[value='Submit']").click()
|
||||
|
||||
return PaymentAndVerificationFlow(self.browser, self._course_id, entry_point='payment-confirmation').wait_for_page()
|
||||
72
common/test/acceptance/pages/lms/track_selection.py
Normal file
72
common/test/acceptance/pages/lms/track_selection.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Track selection page"""
|
||||
|
||||
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
|
||||
from .pay_and_verify import PaymentAndVerificationFlow
|
||||
|
||||
|
||||
class TrackSelectionPage(PageObject):
|
||||
"""Interact with the track selection page.
|
||||
|
||||
This page can be accessed at `/course_modes/choose/{course_id}/`.
|
||||
"""
|
||||
def __init__(self, browser, course_id, separate_verified=False):
|
||||
"""Initialize the page.
|
||||
|
||||
Arguments:
|
||||
browser (Browser): The browser instance.
|
||||
course_id (unicode): The course in which the user is enrolling.
|
||||
|
||||
Keyword Arguments:
|
||||
separate_verified (Boolean): Whether to use the split payment and
|
||||
verification flow when enrolling as verified.
|
||||
"""
|
||||
super(TrackSelectionPage, self).__init__(browser)
|
||||
self._course_id = course_id
|
||||
self._separate_verified = separate_verified
|
||||
|
||||
if self._separate_verified:
|
||||
self._querystring = "?separate-verified=1"
|
||||
else:
|
||||
self._querystring = "?disable-separate-verified=1"
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""Return the URL corresponding to the track selection page."""
|
||||
url = "{base}/course_modes/choose/{course_id}{querystring}".format(
|
||||
base=BASE_URL,
|
||||
course_id=self._course_id,
|
||||
querystring=self._querystring
|
||||
)
|
||||
|
||||
return url
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""Check if the track selection page has loaded."""
|
||||
return self.q(css=".wrapper-register-choose").is_present()
|
||||
|
||||
def enroll(self, mode="honor"):
|
||||
"""Interact with one of the enrollment buttons on the page.
|
||||
|
||||
Keyword Arguments:
|
||||
mode (str): Can be "honor" or "verified"
|
||||
|
||||
Raises:
|
||||
ValueError
|
||||
"""
|
||||
if mode == "honor":
|
||||
self.q(css="input[name='honor_mode']").click()
|
||||
|
||||
return DashboardPage(self.browser, separate_verified=self._separate_verified).wait_for_page()
|
||||
elif mode == "verified":
|
||||
# Check the first contribution option, then click the enroll button
|
||||
self.q(css=".contribution-option > input").first.click()
|
||||
self.q(css="input[name='verified_mode']").click()
|
||||
|
||||
return PaymentAndVerificationFlow(self.browser, self._course_id).wait_for_page()
|
||||
else:
|
||||
raise ValueError("Mode must be either 'honor' or 'verified'.")
|
||||
@@ -16,6 +16,7 @@ from ..helpers import (
|
||||
select_option_by_value,
|
||||
)
|
||||
from ...pages.lms.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.create_mode import ModeCreationPage
|
||||
from ...pages.common.logout import LogoutPage
|
||||
from ...pages.lms.find_courses import FindCoursesPage
|
||||
from ...pages.lms.course_about import CourseAboutPage
|
||||
@@ -28,6 +29,8 @@ 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 ...pages.lms.track_selection import TrackSelectionPage
|
||||
from ...pages.lms.pay_and_verify import PaymentAndVerificationFlow, FakePaymentPage
|
||||
from ...pages.studio.settings import SettingsPage
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc, CourseUpdateDesc
|
||||
|
||||
@@ -237,6 +240,128 @@ class RegisterFromCombinedPageTest(UniqueCourseTest):
|
||||
self.assertEqual(self.register_page.current_form, "login")
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class PayAndVerifyTest(UniqueCourseTest):
|
||||
"""Test that we can proceed through the payment and verification flow."""
|
||||
def setUp(self):
|
||||
"""Initialize the test.
|
||||
|
||||
Create the necessary page objects, create a test course and configure its modes,
|
||||
create a user and log them in.
|
||||
"""
|
||||
super(PayAndVerifyTest, self).setUp()
|
||||
self.track_selection_page = TrackSelectionPage(self.browser, self.course_id, separate_verified=True)
|
||||
self.payment_and_verification_flow = PaymentAndVerificationFlow(self.browser, self.course_id)
|
||||
self.immediate_verification_page = PaymentAndVerificationFlow(self.browser, self.course_id, entry_point='verify-now')
|
||||
self.upgrade_page = PaymentAndVerificationFlow(self.browser, self.course_id, entry_point='upgrade')
|
||||
self.fake_payment_page = FakePaymentPage(self.browser, self.course_id)
|
||||
self.dashboard_page = DashboardPage(self.browser, separate_verified=True)
|
||||
|
||||
# Create a course
|
||||
CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
).install()
|
||||
|
||||
# Add an honor mode to the course
|
||||
ModeCreationPage(self.browser, self.course_id).visit()
|
||||
|
||||
# Add a verified mode to the course
|
||||
ModeCreationPage(self.browser, self.course_id, mode_slug=u'verified', mode_display_name=u'Verified Certificate', min_price=10, suggested_prices='10,20').visit()
|
||||
|
||||
def test_immediate_verification_enrollment(self):
|
||||
# Create a user and log them in
|
||||
AutoAuthPage(self.browser).visit()
|
||||
|
||||
# Navigate to the track selection page with the appropriate GET parameter in the URL
|
||||
self.track_selection_page.visit()
|
||||
|
||||
# Enter the payment and verification flow by choosing to enroll as verified
|
||||
self.track_selection_page.enroll('verified')
|
||||
|
||||
# Proceed to the fake payment page
|
||||
self.payment_and_verification_flow.proceed_to_payment()
|
||||
|
||||
# Submit payment
|
||||
self.fake_payment_page.submit_payment()
|
||||
|
||||
# Proceed to verification
|
||||
self.payment_and_verification_flow.immediate_verification()
|
||||
|
||||
# Take face photo and proceed to the ID photo step
|
||||
self.payment_and_verification_flow.webcam_capture()
|
||||
self.payment_and_verification_flow.next_verification_step(self.immediate_verification_page)
|
||||
|
||||
# Take ID photo and proceed to the review photos step
|
||||
self.payment_and_verification_flow.webcam_capture()
|
||||
self.payment_and_verification_flow.next_verification_step(self.immediate_verification_page)
|
||||
|
||||
# Submit photos and proceed to the enrollment confirmation step
|
||||
self.payment_and_verification_flow.next_verification_step(self.immediate_verification_page)
|
||||
|
||||
# Navigate to the dashboard with the appropriate GET parameter in the URL
|
||||
self.dashboard_page.visit()
|
||||
|
||||
# Expect that we're enrolled as verified in the course
|
||||
enrollment_mode = self.dashboard_page.get_enrollment_mode(self.course_info["display_name"])
|
||||
self.assertEqual(enrollment_mode, 'verified')
|
||||
|
||||
def test_deferred_verification_enrollment(self):
|
||||
# Create a user and log them in
|
||||
AutoAuthPage(self.browser).visit()
|
||||
|
||||
# Navigate to the track selection page with the appropriate GET parameter in the URL
|
||||
self.track_selection_page.visit()
|
||||
|
||||
# Enter the payment and verification flow by choosing to enroll as verified
|
||||
self.track_selection_page.enroll('verified')
|
||||
|
||||
# Proceed to the fake payment page
|
||||
self.payment_and_verification_flow.proceed_to_payment()
|
||||
|
||||
# Submit payment
|
||||
self.fake_payment_page.submit_payment()
|
||||
|
||||
# Navigate to the dashboard with the appropriate GET parameter in the URL
|
||||
self.dashboard_page.visit()
|
||||
|
||||
# Expect that we're enrolled as verified in the course
|
||||
enrollment_mode = self.dashboard_page.get_enrollment_mode(self.course_info["display_name"])
|
||||
self.assertEqual(enrollment_mode, 'verified')
|
||||
|
||||
def test_enrollment_upgrade(self):
|
||||
# Create a user, log them in, and enroll them in the honor mode
|
||||
AutoAuthPage(self.browser, course_id=self.course_id).visit()
|
||||
|
||||
# Navigate to the dashboard with the appropriate GET parameter in the URL
|
||||
self.dashboard_page.visit()
|
||||
|
||||
# Expect that we're enrolled as honor in the course
|
||||
enrollment_mode = self.dashboard_page.get_enrollment_mode(self.course_info["display_name"])
|
||||
self.assertEqual(enrollment_mode, 'honor')
|
||||
|
||||
# Click the upsell button on the dashboard
|
||||
self.dashboard_page.upgrade_enrollment(self.course_info["display_name"], self.upgrade_page)
|
||||
|
||||
# Select the first contribution option appearing on the page
|
||||
self.upgrade_page.indicate_contribution()
|
||||
|
||||
# Proceed to the fake payment page
|
||||
self.upgrade_page.proceed_to_payment()
|
||||
|
||||
# Submit payment
|
||||
self.fake_payment_page.submit_payment()
|
||||
|
||||
# Navigate to the dashboard with the appropriate GET parameter in the URL
|
||||
self.dashboard_page.visit()
|
||||
|
||||
# Expect that we're enrolled as verified in the course
|
||||
enrollment_mode = self.dashboard_page.get_enrollment_mode(self.course_info["display_name"])
|
||||
self.assertEqual(enrollment_mode, 'verified')
|
||||
|
||||
|
||||
class LanguageTest(WebAppTest):
|
||||
"""
|
||||
Tests that the change language functionality on the dashboard works
|
||||
|
||||
Reference in New Issue
Block a user