fix unicode strings in common/
This commit is contained in:
@@ -386,7 +386,7 @@ class CourseEntitlement(TimeStampedModel):
|
||||
mode=entitlement.mode
|
||||
)
|
||||
except CourseEnrollmentException:
|
||||
log.exception('Login for Course Entitlement {uuid} failed'.format(uuid=entitlement.uuid))
|
||||
log.exception(u'Login for Course Entitlement {uuid} failed'.format(uuid=entitlement.uuid))
|
||||
return False
|
||||
|
||||
entitlement.set_enrollment(enrollment)
|
||||
@@ -432,7 +432,7 @@ class CourseEntitlement(TimeStampedModel):
|
||||
if not refund_successful:
|
||||
# This state is achieved in most cases by a failure in the ecommerce service to process the refund.
|
||||
log.warn(
|
||||
'Entitlement Refund failed for Course Entitlement [%s], alert User',
|
||||
u'Entitlement Refund failed for Course Entitlement [%s], alert User',
|
||||
self.uuid
|
||||
)
|
||||
# Force Transaction reset with an Integrity error exception, this will revert all previous transactions
|
||||
|
||||
@@ -36,8 +36,8 @@ def is_course_run_entitlement_fulfillable(
|
||||
try:
|
||||
course_overview = CourseOverview.get_from_id(course_run_key)
|
||||
except CourseOverview.DoesNotExist:
|
||||
log.error(('There is no CourseOverview entry available for {course_run_id}, '
|
||||
'course run cannot be applied to entitlement').format(
|
||||
log.error((u'There is no CourseOverview entry available for {course_run_id}, '
|
||||
u'course run cannot be applied to entitlement').format(
|
||||
course_run_id=str(course_run_key)
|
||||
))
|
||||
return False
|
||||
|
||||
@@ -104,7 +104,7 @@ class ActivationEmailTests(CacheIsolationTestCase):
|
||||
u"high-quality {platform} courses".format(platform=settings.PLATFORM_NAME),
|
||||
"http://edx.org/activate/",
|
||||
(
|
||||
"please use our web form at "
|
||||
u"please use our web form at "
|
||||
u"{support_url} ".format(support_url=settings.SUPPORT_SITE_LINK)
|
||||
)
|
||||
]
|
||||
|
||||
@@ -151,10 +151,10 @@ class SAMLConfigurationAdmin(KeyedConfigurationModelAdmin):
|
||||
public_key = inst.get_setting('SP_PUBLIC_CERT')
|
||||
private_key = inst.get_setting('SP_PRIVATE_KEY')
|
||||
if not public_key or not private_key:
|
||||
return u'<em>Key pair incomplete/missing</em>'
|
||||
return HTML(u'<em>Key pair incomplete/missing</em>')
|
||||
pub1, pub2 = public_key[0:10], public_key[-10:]
|
||||
priv1, priv2 = private_key[0:10], private_key[-10:]
|
||||
return u'Public: {}…{}<br>Private: {}…{}'.format(pub1, pub2, priv1, priv2)
|
||||
return HTML(u'Public: {}…{}<br>Private: {}…{}').format(pub1, pub2, priv1, priv2)
|
||||
key_summary.allow_tags = True
|
||||
|
||||
admin.site.register(SAMLConfiguration, SAMLConfigurationAdmin)
|
||||
@@ -210,7 +210,7 @@ class ApiPermissionsAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ApiPermissionsAdminForm, self).__init__(*args, **kwargs)
|
||||
self.fields['provider_id'].choices = (
|
||||
(provider.provider_id, "{} ({})".format(provider.name, provider.provider_id))
|
||||
(provider.provider_id, u"{} ({})".format(provider.name, provider.provider_id))
|
||||
for provider in Registry.enabled()
|
||||
)
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ class UserMappingViewAPITests(TpaAPITestCase):
|
||||
if access_token == 'valid-token':
|
||||
access_token = token.token
|
||||
|
||||
response = self.client.get(url, HTTP_AUTHORIZATION='Bearer {}'.format(access_token))
|
||||
response = self.client.get(url, HTTP_AUTHORIZATION=u'Bearer {}'.format(access_token))
|
||||
self._verify_response(response, expect_code, expect_data)
|
||||
|
||||
@ddt.data(
|
||||
|
||||
@@ -117,7 +117,7 @@ class BaseUserView(APIView):
|
||||
if identifier.kind not in self.identifier_kinds:
|
||||
# This is already checked before we get here, so raise a 500 error
|
||||
# if the check fails.
|
||||
raise ValueError("Identifier kind {} not in {}".format(identifier.kind, self.identifier_kinds))
|
||||
raise ValueError(u"Identifier kind {} not in {}".format(identifier.kind, self.identifier_kinds))
|
||||
|
||||
self_request = False
|
||||
if identifier == self.identifier('username', request.user.username):
|
||||
|
||||
@@ -186,7 +186,7 @@ class LTIAuthBackend(BaseAuth):
|
||||
if valid:
|
||||
return data
|
||||
except AttributeError as error:
|
||||
log.error("'{}' not found.".format(text_type(error)))
|
||||
log.error(u"'{}' not found.".format(text_type(error)))
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -27,10 +27,10 @@ class Command(BaseCommand):
|
||||
log.addHandler(log_handler)
|
||||
total, skipped, attempted, updated, failed, failure_messages = fetch_saml_metadata()
|
||||
self.stdout.write(
|
||||
"\nDone."
|
||||
"\n{total} provider(s) found in database."
|
||||
"\n{skipped} skipped and {attempted} attempted."
|
||||
"\n{updated} updated and {failed} failed.\n".format(
|
||||
u"\nDone."
|
||||
u"\n{total} provider(s) found in database."
|
||||
u"\n{skipped} skipped and {attempted} attempted."
|
||||
u"\n{updated} updated and {failed} failed.\n".format(
|
||||
total=total,
|
||||
skipped=skipped, attempted=attempted,
|
||||
updated=updated, failed=failed,
|
||||
@@ -39,7 +39,7 @@ class Command(BaseCommand):
|
||||
|
||||
if failed > 0:
|
||||
raise CommandError(
|
||||
"Command finished with the following exceptions:\n\n{failures}".format(
|
||||
u"Command finished with the following exceptions:\n\n{failures}".format(
|
||||
failures="\n\n".join(failure_messages)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@ from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from provider.oauth2.models import Client
|
||||
from provider.utils import long_token
|
||||
from six import text_type
|
||||
from social_core.backends.base import BaseAuth
|
||||
from social_core.backends.oauth import OAuthAuth
|
||||
from social_core.backends.saml import SAMLAuth
|
||||
@@ -61,9 +60,9 @@ def clean_json(value, of_type):
|
||||
try:
|
||||
value_python = json.loads(value)
|
||||
except ValueError as err:
|
||||
raise ValidationError("Invalid JSON: {}".format(text_type(err)))
|
||||
raise ValidationError(u"Invalid JSON: {}".format(err))
|
||||
if not isinstance(value_python, of_type):
|
||||
raise ValidationError("Expected a JSON {}".format(of_type))
|
||||
raise ValidationError(u"Expected a JSON {}".format(of_type))
|
||||
return json.dumps(value_python, indent=4)
|
||||
|
||||
|
||||
@@ -345,7 +344,7 @@ class OAuth2ProviderConfig(ProviderConfig):
|
||||
help_text=(
|
||||
'For increased security, you can avoid storing this in your database by leaving '
|
||||
' this field blank and setting '
|
||||
'SOCIAL_AUTH_OAUTH_SECRETS = {"(backend name)": "secret", ...} '
|
||||
'SOCIAL_AUTH_OAUTH_SECRETS = {"(backend name)": "secret", ...} ' # pylint: disable=unicode-format-string
|
||||
'in your instance\'s Django settings (or lms.auth.json)'
|
||||
)
|
||||
)
|
||||
@@ -444,7 +443,7 @@ class SAMLConfiguration(ConfigurationModel):
|
||||
"""
|
||||
Return human-readable string representation.
|
||||
"""
|
||||
return "SAMLConfiguration {site}: {slug} on {date:%Y-%m-%d %H:%M:%S}".format(
|
||||
return u"SAMLConfiguration {site}: {slug} on {date:%Y-%m-%d %H:%M:%S}".format(
|
||||
site=self.site.name,
|
||||
slug=self.slug,
|
||||
date=self.change_date,
|
||||
@@ -475,7 +474,7 @@ class SAMLConfiguration(ConfigurationModel):
|
||||
""" Get the value of a setting, or raise KeyError """
|
||||
default_saml_contact = {
|
||||
# Default contact information to put into the SAML metadata that gets generated by python-saml.
|
||||
"givenName": _("{platform_name} Support").format(
|
||||
"givenName": _(u"{platform_name} Support").format(
|
||||
platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
|
||||
),
|
||||
"emailAddress": configuration_helpers.get_value('TECH_SUPPORT_EMAIL', settings.TECH_SUPPORT_EMAIL),
|
||||
@@ -594,9 +593,9 @@ class SAMLProviderConfig(ProviderConfig):
|
||||
verbose_name="Advanced settings", blank=True,
|
||||
help_text=(
|
||||
'For advanced use cases, enter a JSON object with addtional configuration. '
|
||||
'The tpa-saml backend supports {"requiredEntitlements": ["urn:..."]}, '
|
||||
'The tpa-saml backend supports {"requiredEntitlements": ["urn:..."]}, ' # pylint: disable=unicode-format-string
|
||||
'which can be used to require the presence of a specific eduPersonEntitlement, '
|
||||
'and {"extra_field_definitions": [{"name": "...", "urn": "..."},...]}, which can be '
|
||||
'and {"extra_field_definitions": [{"name": "...", "urn": "..."},...]}, which can be ' # pylint: disable=unicode-format-string,line-too-long
|
||||
'used to define registration form fields and the URNs that can be used to retrieve '
|
||||
'the relevant values from the SAML response. Custom provider types, as selected '
|
||||
'in the "Identity Provider Type" field, may make use of the information stored '
|
||||
@@ -683,7 +682,7 @@ class SAMLProviderConfig(ProviderConfig):
|
||||
data = SAMLProviderData.current(self.entity_id)
|
||||
if not data or not data.is_valid():
|
||||
log.error(
|
||||
'No SAMLProviderData found for provider "%s" with entity id "%s" and IdP slug "%s". '
|
||||
'No SAMLProviderData found for provider "%s" with entity id "%s" and IdP slug "%s". ' # pylint: disable=unicode-format-string,line-too-long
|
||||
'Run "manage.py saml pull" to fix or debug.',
|
||||
self.name, self.entity_id, self.slug
|
||||
)
|
||||
@@ -794,7 +793,7 @@ class LTIProviderConfig(ProviderConfig):
|
||||
'tool consumer instance should know this value. '
|
||||
'For increased security, you can avoid storing this in '
|
||||
'your database by leaving this field blank and setting '
|
||||
'SOCIAL_AUTH_LTI_CONSUMER_SECRETS = {"consumer key": "secret", ...} '
|
||||
'SOCIAL_AUTH_LTI_CONSUMER_SECRETS = {"consumer key": "secret", ...} ' # pylint: disable=unicode-format-string,line-too-long
|
||||
'in your instance\'s Django setttigs (or lms.auth.json)'
|
||||
),
|
||||
blank=True,
|
||||
|
||||
@@ -275,7 +275,7 @@ def _get_enabled_provider(provider_id):
|
||||
enabled_provider = provider.Registry.get(provider_id)
|
||||
|
||||
if not enabled_provider:
|
||||
raise ValueError('Provider %s not enabled' % provider_id)
|
||||
raise ValueError(u'Provider %s not enabled' % provider_id)
|
||||
|
||||
return enabled_provider
|
||||
|
||||
@@ -317,7 +317,7 @@ def get_complete_url(backend_name):
|
||||
ValueError: if no provider is enabled with the given backend_name.
|
||||
"""
|
||||
if not any(provider.Registry.get_enabled_by_backend_name(backend_name)):
|
||||
raise ValueError('Provider with backend %s not enabled' % backend_name)
|
||||
raise ValueError(u'Provider with backend %s not enabled' % backend_name)
|
||||
|
||||
return _get_url('social:complete', backend_name)
|
||||
|
||||
@@ -593,7 +593,7 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
|
||||
# register anew via SSO. See SOL-1324 in JIRA.
|
||||
# However, we will log a warning for this case:
|
||||
logger.warning(
|
||||
'User "%s" is using third_party_auth to login but has not yet activated their account. ',
|
||||
u'User "%s" is using third_party_auth to login but has not yet activated their account. ',
|
||||
user.username
|
||||
)
|
||||
|
||||
@@ -727,8 +727,8 @@ def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **k
|
||||
current_value = getattr(model, field)
|
||||
if provider_value is not None and current_value != provider_value:
|
||||
if field in integrity_conflict_fields and User.objects.filter(**{field: provider_value}).exists():
|
||||
logger.warning('User with ID [%s] tried to synchronize profile data through [%s] '
|
||||
'but there was a conflict with an existing [%s]: [%s].',
|
||||
logger.warning(u'User with ID [%s] tried to synchronize profile data through [%s] '
|
||||
u'but there was a conflict with an existing [%s]: [%s].',
|
||||
user.id, current_provider.name, field, provider_value)
|
||||
continue
|
||||
changed[provider_field] = current_value
|
||||
@@ -736,8 +736,8 @@ def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **k
|
||||
|
||||
if changed:
|
||||
logger.info(
|
||||
"User [%s] performed SSO through [%s] who synchronizes profile data, and the "
|
||||
"following fields were changed: %s", user.username, current_provider.name, changed.keys(),
|
||||
u"User [%s] performed SSO through [%s] who synchronizes profile data, and the "
|
||||
u"following fields were changed: %s", user.username, current_provider.name, changed.keys(),
|
||||
)
|
||||
|
||||
# Save changes to user and user.profile models.
|
||||
@@ -763,7 +763,7 @@ def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **k
|
||||
email.send()
|
||||
except SMTPException:
|
||||
logger.exception('Error sending IdP learner data sync-initiated email change '
|
||||
'notification email for user [%s].', user.username)
|
||||
u'notification email for user [%s].', user.username)
|
||||
|
||||
|
||||
def set_id_verification_status(auth_entry, strategy, details, user=None, *args, **kwargs):
|
||||
|
||||
@@ -155,7 +155,7 @@ class SAMLAuthBackend(SAMLAuth): # pylint: disable=abstract-method
|
||||
for expected in idp.conf['requiredEntitlements']:
|
||||
if expected not in entitlements:
|
||||
log.warning(
|
||||
"SAML user from IdP %s rejected due to missing eduPersonEntitlement %s", idp.name, expected)
|
||||
u"SAML user from IdP %s rejected due to missing eduPersonEntitlement %s", idp.name, expected)
|
||||
raise AuthForbidden(self)
|
||||
|
||||
def _create_saml_auth(self, idp):
|
||||
@@ -177,7 +177,7 @@ class SAMLAuthBackend(SAMLAuth): # pylint: disable=abstract-method
|
||||
def wrapped_method(*args, **kwargs):
|
||||
""" Wrapped login or process_response method """
|
||||
result = method(*args, **kwargs)
|
||||
log.info("SAML login %s for IdP %s. XML is:\n%s", action_description, idp.name, xml_getter())
|
||||
log.info(u"SAML login %s for IdP %s. XML is:\n%s", action_description, idp.name, xml_getter())
|
||||
return result
|
||||
setattr(auth_inst, method_name, wrapped_method)
|
||||
|
||||
@@ -363,8 +363,8 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
if not all(var in self.conf for var in self.required_variables):
|
||||
missing = [var for var in self.required_variables if var not in self.conf]
|
||||
log.warning(
|
||||
"To retrieve rich user data for an SAP SuccessFactors identity provider, the following keys in "
|
||||
"'other_settings' are required, but were missing: %s",
|
||||
u"To retrieve rich user data for an SAP SuccessFactors identity provider, the following keys in "
|
||||
u"'other_settings' are required, but were missing: %s",
|
||||
missing
|
||||
)
|
||||
return missing
|
||||
@@ -381,14 +381,14 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
token_data = transaction_data.get('token_data')
|
||||
token_data = token_data if token_data else 'Not available'
|
||||
log_msg_template = (
|
||||
'SAPSuccessFactors exception received for {operation_name} request. ' +
|
||||
'URL: {url} ' +
|
||||
'Company ID: {company_id}. ' +
|
||||
'User ID: {user_id}. ' +
|
||||
'Error message: {err_msg}. ' +
|
||||
'System message: {sys_msg}. ' +
|
||||
'Headers: {headers}. ' +
|
||||
'Token Data: {token_data}.'
|
||||
u'SAPSuccessFactors exception received for {operation_name} request. ' +
|
||||
u'URL: {url} ' +
|
||||
u'Company ID: {company_id}. ' +
|
||||
u'User ID: {user_id}. ' +
|
||||
u'Error message: {err_msg}. ' +
|
||||
u'System message: {sys_msg}. ' +
|
||||
u'Headers: {headers}. ' +
|
||||
u'Token Data: {token_data}.'
|
||||
)
|
||||
log_msg = log_msg_template.format(
|
||||
operation_name=transaction_data['operation_name'],
|
||||
@@ -469,7 +469,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
if not access_token_data:
|
||||
return None
|
||||
token_string = access_token_data['access_token']
|
||||
session.headers.update({'Authorization': 'Bearer {}'.format(token_string), 'Accept': 'application/json'})
|
||||
session.headers.update({'Authorization': u'Bearer {}'.format(token_string), 'Accept': 'application/json'})
|
||||
session.token_data = access_token_data
|
||||
return session
|
||||
|
||||
@@ -537,7 +537,7 @@ def get_saml_idp_class(idp_identifier_string):
|
||||
}
|
||||
if idp_identifier_string not in choices:
|
||||
log.error(
|
||||
'%s is not a valid EdXSAMLIdentityProvider subclass; using EdXSAMLIdentityProvider base class.',
|
||||
u'%s is not a valid EdXSAMLIdentityProvider subclass; using EdXSAMLIdentityProvider base class.',
|
||||
idp_identifier_string
|
||||
)
|
||||
return choices.get(idp_identifier_string, EdXSAMLIdentityProvider)
|
||||
|
||||
@@ -17,6 +17,7 @@ from requests import exceptions
|
||||
from six import text_type
|
||||
|
||||
from third_party_auth.models import SAMLConfiguration, SAMLProviderConfig, SAMLProviderData
|
||||
from openedx.core.djangolib.markup import Text
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -76,9 +77,9 @@ def fetch_saml_metadata():
|
||||
failure_messages = [] # We return the length of this array for num_failed
|
||||
for url, entity_ids in url_map.items():
|
||||
try:
|
||||
log.info("Fetching %s", url)
|
||||
log.info(u"Fetching %s", url)
|
||||
if not url.lower().startswith('https'):
|
||||
log.warning("This SAML metadata URL is not secure! It should use HTTPS. (%s)", url)
|
||||
log.warning(u"This SAML metadata URL is not secure! It should use HTTPS. (%s)", url)
|
||||
response = requests.get(url, verify=True) # May raise HTTPError or SSLError or ConnectionError
|
||||
response.raise_for_status() # May raise an HTTPError
|
||||
|
||||
@@ -108,23 +109,23 @@ def fetch_saml_metadata():
|
||||
|
||||
log.exception(text_type(error))
|
||||
failure_messages.append(
|
||||
"{error_type}: {error_message}\nMetadata Source: {url}\nEntity IDs: \n{entity_ids}.".format(
|
||||
u"{error_type}: {error_message}\nMetadata Source: {url}\nEntity IDs: \n{entity_ids}.".format(
|
||||
error_type=type(error).__name__,
|
||||
error_message=text_type(error),
|
||||
url=url,
|
||||
entity_ids="\n".join(
|
||||
["\t{}: {}".format(count, item) for count, item in enumerate(entity_ids, start=1)],
|
||||
[u"\t{}: {}".format(count, item) for count, item in enumerate(entity_ids, start=1)],
|
||||
)
|
||||
)
|
||||
)
|
||||
except etree.XMLSyntaxError as error:
|
||||
log.exception(text_type(error))
|
||||
failure_messages.append(
|
||||
"XMLSyntaxError: {error_message}\nMetadata Source: {url}\nEntity IDs: \n{entity_ids}.".format(
|
||||
u"XMLSyntaxError: {error_message}\nMetadata Source: {url}\nEntity IDs: \n{entity_ids}.".format(
|
||||
error_message=str(error.error_log),
|
||||
url=url,
|
||||
entity_ids="\n".join(
|
||||
["\t{}: {}".format(count, item) for count, item in enumerate(entity_ids, start=1)],
|
||||
[u"\t{}: {}".format(count, item) for count, item in enumerate(entity_ids, start=1)],
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -144,12 +145,12 @@ def _parse_metadata_xml(xml, entity_id):
|
||||
entity_desc = xml
|
||||
else:
|
||||
if xml.tag != etree.QName(SAML_XML_NS, 'EntitiesDescriptor'):
|
||||
raise MetadataParseError("Expected root element to be <EntitiesDescriptor>, not {}".format(xml.tag))
|
||||
raise MetadataParseError(Text(u"Expected root element to be <EntitiesDescriptor>, not {}").format(xml.tag))
|
||||
entity_desc = xml.find(
|
||||
".//{}[@entityID='{}']".format(etree.QName(SAML_XML_NS, 'EntityDescriptor'), entity_id)
|
||||
)
|
||||
if not entity_desc:
|
||||
raise MetadataParseError("Can't find EntityDescriptor for entityID {}".format(entity_id))
|
||||
raise MetadataParseError(u"Can't find EntityDescriptor for entityID {}".format(entity_id))
|
||||
|
||||
expires_at = None
|
||||
if "validUntil" in xml.attrib:
|
||||
|
||||
@@ -63,7 +63,10 @@ class HelperMixin(object):
|
||||
"""
|
||||
self.assertEqual(200, response.status_code)
|
||||
# Check that the correct provider was selected.
|
||||
self.assertIn('successfully signed in with <strong>%s</strong>' % self.provider.name, response.content)
|
||||
self.assertIn(
|
||||
u'successfully signed in with <strong>%s</strong>' % self.provider.name,
|
||||
response.content.decode(response.charset)
|
||||
)
|
||||
# Expect that each truthy value we've prepopulated the register form
|
||||
# with is actually present.
|
||||
form_field_data = self.provider.get_register_form_data(pipeline_kwargs)
|
||||
@@ -120,8 +123,8 @@ class HelperMixin(object):
|
||||
"""Asserts failure on /login for missing social auth looks right."""
|
||||
self.assertEqual(403, response.status_code)
|
||||
self.assertIn(
|
||||
"successfully logged into your %s account, but this account isn't linked" % self.provider.name,
|
||||
response.content
|
||||
u"successfully logged into your %s account, but this account isn't linked" % self.provider.name,
|
||||
response.content.decode(response.charset)
|
||||
)
|
||||
|
||||
def assert_json_failure_response_is_username_collision(self, response):
|
||||
|
||||
@@ -290,13 +290,13 @@ class TestShibIntegrationTest(SamlIntegrationTestUtilities, IntegrationTestMixin
|
||||
self.assertEqual(mock_log.call_count, 4)
|
||||
|
||||
(msg, action_type, idp_name, xml), _kwargs = mock_log.call_args_list[0]
|
||||
self.assertTrue(msg.startswith("SAML login %s"))
|
||||
self.assertTrue(msg.startswith(u"SAML login %s"))
|
||||
self.assertEqual(action_type, "request")
|
||||
self.assertEqual(idp_name, self.PROVIDER_IDP_SLUG)
|
||||
self.assertIn('<samlp:AuthnRequest', xml)
|
||||
|
||||
(msg, action_type, idp_name, xml), _kwargs = mock_log.call_args_list[1]
|
||||
self.assertTrue(msg.startswith("SAML login %s"))
|
||||
self.assertTrue(msg.startswith(u"SAML login %s"))
|
||||
self.assertEqual(action_type, "response")
|
||||
self.assertEqual(idp_name, self.PROVIDER_IDP_SLUG)
|
||||
self.assertIn('<saml2p:Response', xml)
|
||||
|
||||
@@ -350,9 +350,9 @@ class UserDetailsForceSyncTestCase(testutil.TestCase, test.TestCase):
|
||||
self.old_username = self.user.username
|
||||
self.old_fullname = self.user.profile.name
|
||||
self.details = {
|
||||
'email': 'new+{}'.format(self.user.email),
|
||||
'username': 'new_{}'.format(self.user.username),
|
||||
'fullname': 'Grown Up {}'.format(self.user.profile.name),
|
||||
'email': u'new+{}'.format(self.user.email),
|
||||
'username': u'new_{}'.format(self.user.username),
|
||||
'fullname': u'Grown Up {}'.format(self.user.profile.name),
|
||||
'country': 'PK',
|
||||
'non_existing_field': 'value',
|
||||
}
|
||||
@@ -381,7 +381,7 @@ class UserDetailsForceSyncTestCase(testutil.TestCase, test.TestCase):
|
||||
# User now has updated information in the DB.
|
||||
user = User.objects.get()
|
||||
assert user.email == 'new+{}'.format(self.old_email)
|
||||
assert user.profile.name == 'Grown Up {}'.format(self.old_fullname)
|
||||
assert user.profile.name == u'Grown Up {}'.format(self.old_fullname)
|
||||
assert user.profile.country == 'PK'
|
||||
|
||||
# Now verify that username field is not updated
|
||||
@@ -407,7 +407,7 @@ class UserDetailsForceSyncTestCase(testutil.TestCase, test.TestCase):
|
||||
# The email is not changed, but everything else is.
|
||||
user = User.objects.get(pk=self.user.pk)
|
||||
assert user.email == self.old_email
|
||||
assert user.profile.name == 'Grown Up {}'.format(self.old_fullname)
|
||||
assert user.profile.name == u'Grown Up {}'.format(self.old_fullname)
|
||||
assert user.profile.country == 'PK'
|
||||
|
||||
# Now verify that username field is not updated
|
||||
@@ -437,7 +437,7 @@ class UserDetailsForceSyncTestCase(testutil.TestCase, test.TestCase):
|
||||
user = User.objects.get(pk=self.user.pk)
|
||||
assert user.email == 'new+{}'.format(self.old_email)
|
||||
assert user.username == self.old_username
|
||||
assert user.profile.name == 'Grown Up {}'.format(self.old_fullname)
|
||||
assert user.profile.name == u'Grown Up {}'.format(self.old_fullname)
|
||||
assert user.profile.country == 'PK'
|
||||
|
||||
# An email should still be sent because the email changed.
|
||||
|
||||
@@ -53,8 +53,8 @@ class SAMLMetadataTest(SAMLTestCase):
|
||||
self.enable_saml(
|
||||
other_config_str=(
|
||||
'{'
|
||||
'"TECHNICAL_CONTACT": {"givenName": "Jane Tech", "emailAddress": "jane@example.com"},'
|
||||
'"SUPPORT_CONTACT": {"givenName": "Joe Support", "emailAddress": "joe@example.com"}'
|
||||
'"TECHNICAL_CONTACT": {"givenName": "Jane Tech", "emailAddress": "jane@example.com"},' # pylint: disable=unicode-format-string,line-too-long
|
||||
'"SUPPORT_CONTACT": {"givenName": "Joe Support", "emailAddress": "joe@example.com"}' # pylint: disable=unicode-format-string,line-too-long
|
||||
'}'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -220,7 +220,7 @@ class SAMLTestCase(TestCase):
|
||||
error_mock = log_mock.error
|
||||
idp_class = get_saml_idp_class('fake_idp_class_option')
|
||||
error_mock.assert_called_once_with(
|
||||
'%s is not a valid EdXSAMLIdentityProvider subclass; using EdXSAMLIdentityProvider base class.',
|
||||
u'%s is not a valid EdXSAMLIdentityProvider subclass; using EdXSAMLIdentityProvider base class.',
|
||||
'fake_idp_class_option'
|
||||
)
|
||||
self.assertIs(idp_class, EdXSAMLIdentityProvider)
|
||||
|
||||
@@ -861,7 +861,6 @@ class MatlabTest(unittest.TestCase):
|
||||
'response_data': {},
|
||||
'describedby_html': 'aria-describedby="status_{id}"'.format(id=prob_id)
|
||||
}
|
||||
|
||||
self.assertEqual(context, expected)
|
||||
self.the_input.capa_system.render_template = DemoSystem().render_template
|
||||
self.the_input.get_html() # Should not raise an exception
|
||||
|
||||
@@ -40,12 +40,12 @@ class StudioApiFixture(object):
|
||||
self.user = response.json()
|
||||
|
||||
if not self.user:
|
||||
raise StudioApiLoginError('Auto-auth failed. Response was: {}'.format(self.user))
|
||||
raise StudioApiLoginError(u'Auto-auth failed. Response was: {}'.format(self.user))
|
||||
|
||||
return session
|
||||
|
||||
else:
|
||||
msg = 'Could not log in to use Studio restful API. Status code: {0}'.format(response.status_code)
|
||||
msg = u'Could not log in to use Studio restful API. Status code: {0}'.format(response.status_code)
|
||||
raise StudioApiLoginError(msg)
|
||||
|
||||
@lazy
|
||||
@@ -123,14 +123,14 @@ class XBlockContainerFixture(StudioApiFixture):
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
msg = "Could not create {0}. Status was {1}".format(xblock_desc, response.status_code)
|
||||
msg = u"Could not create {0}. Status was {1}".format(xblock_desc, response.status_code)
|
||||
raise FixtureError(msg)
|
||||
|
||||
try:
|
||||
loc = response.json().get('locator')
|
||||
xblock_desc.locator = loc
|
||||
except ValueError:
|
||||
raise FixtureError("Could not decode JSON from '{0}'".format(response.content))
|
||||
raise FixtureError(u"Could not decode JSON from '{0}'".format(response.content))
|
||||
|
||||
# Configure the XBlock
|
||||
response = self.session.post(
|
||||
@@ -142,7 +142,7 @@ class XBlockContainerFixture(StudioApiFixture):
|
||||
if response.ok:
|
||||
return loc
|
||||
else:
|
||||
raise FixtureError("Could not update {0}. Status code: {1}".format(xblock_desc, response.status_code))
|
||||
raise FixtureError(u"Could not update {0}. Status code: {1}".format(xblock_desc, response.status_code))
|
||||
|
||||
def _update_xblock(self, locator, data):
|
||||
"""
|
||||
@@ -156,7 +156,7 @@ class XBlockContainerFixture(StudioApiFixture):
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
msg = "Could not update {} with data {}. Status was {}".format(locator, data, response.status_code)
|
||||
msg = u"Could not update {} with data {}. Status was {}".format(locator, data, response.status_code)
|
||||
raise FixtureError(msg)
|
||||
|
||||
def _encode_post_dict(self, post_dict):
|
||||
|
||||
@@ -45,7 +45,7 @@ class CertificateConfigFixture(StudioApiFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise CertificateConfigFixtureError(
|
||||
"Could not create certificate {0}. Status was {1}".format(
|
||||
u"Could not create certificate {0}. Status was {1}".format(
|
||||
json.dumps(self.certificates), response.status_code
|
||||
)
|
||||
)
|
||||
@@ -64,7 +64,7 @@ class CertificateConfigFixture(StudioApiFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise CertificateConfigUpdateFixtureError(
|
||||
"Could not update certificate {0}. Status was {1}".format(
|
||||
u"Could not update certificate {0}. Status was {1}".format(
|
||||
json.dumps(self.certificates), response.status_code
|
||||
)
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ class ConfigModelFixture(object):
|
||||
|
||||
if not response.ok:
|
||||
raise ConfigModelFixtureError(
|
||||
"Could not configure url '{}'. response: {} - {}".format(
|
||||
u"Could not configure url '{}'. response: {} - {}".format(
|
||||
self._api_base,
|
||||
response,
|
||||
response.content,
|
||||
@@ -86,7 +86,7 @@ class ConfigModelFixture(object):
|
||||
if response.ok:
|
||||
# auto_auth returns information about the newly created user
|
||||
# capture this so it can be used by by the testcases.
|
||||
user_pattern = re.compile(r'Logged in user {0} \({1}\) with password {2} and user_id {3}'.format(
|
||||
user_pattern = re.compile(ur'Logged in user {0} \({1}\) with password {2} and user_id {3}'.format(
|
||||
r'(?P<username>\S+)', r'(?P<email>[^\)]+)', r'(?P<password>\S+)', r'(?P<user_id>\d+)'))
|
||||
user_matches = re.match(user_pattern, response.text)
|
||||
if user_matches:
|
||||
@@ -95,5 +95,5 @@ class ConfigModelFixture(object):
|
||||
return session
|
||||
|
||||
else:
|
||||
msg = "Could not log in to use ConfigModel restful API. Status code: {0}".format(response.status_code)
|
||||
msg = u"Could not log in to use ConfigModel restful API. Status code: {0}".format(response.status_code)
|
||||
raise ConfigModelFixtureError(msg)
|
||||
|
||||
@@ -74,7 +74,7 @@ class XBlockFixtureDesc(object):
|
||||
Return a string representation of the description.
|
||||
Useful for error messages.
|
||||
"""
|
||||
return dedent("""
|
||||
return dedent(u"""
|
||||
<XBlockFixtureDescriptor:
|
||||
category={0},
|
||||
data={1},
|
||||
@@ -152,7 +152,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
"""
|
||||
String representation of the course fixture, useful for debugging.
|
||||
"""
|
||||
return "<CourseFixture: org='{org}', number='{number}', run='{run}'>".format(**self._course_dict)
|
||||
return u"<CourseFixture: org='{org}', number='{number}', run='{run}'>".format(**self._course_dict)
|
||||
|
||||
def add_course_details(self, course_details):
|
||||
"""
|
||||
@@ -234,14 +234,14 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not retrieve course outline json. Status was {0}".format(
|
||||
u"Could not retrieve course outline json. Status was {0}".format(
|
||||
response.status_code))
|
||||
|
||||
try:
|
||||
course_outline_json = response.json()
|
||||
except ValueError:
|
||||
raise FixtureError(
|
||||
"Could not decode course outline as JSON: '{0}'".format(response)
|
||||
u"Could not decode course outline as JSON: '{0}'".format(response)
|
||||
)
|
||||
return course_outline_json
|
||||
|
||||
@@ -289,18 +289,18 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
except ValueError:
|
||||
raise FixtureError(
|
||||
"Could not parse response from course request as JSON: '{0}'".format(
|
||||
u"Could not parse response from course request as JSON: '{0}'".format(
|
||||
response.content))
|
||||
|
||||
# This will occur if the course identifier is not unique
|
||||
if err is not None:
|
||||
raise FixtureError("Could not create course {0}. Error message: '{1}'".format(self, err))
|
||||
raise FixtureError(u"Could not create course {0}. Error message: '{1}'".format(self, err))
|
||||
|
||||
if response.ok:
|
||||
self._course_key = response.json()['course_key']
|
||||
else:
|
||||
raise FixtureError(
|
||||
"Could not create course {0}. Status was {1}\nResponse content was: {2}".format(
|
||||
u"Could not create course {0}. Status was {1}\nResponse content was: {2}".format(
|
||||
self._course_dict, response.status_code, response.content))
|
||||
|
||||
def _configure_course(self):
|
||||
@@ -314,14 +314,14 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not retrieve course details. Status was {0}".format(
|
||||
u"Could not retrieve course details. Status was {0}".format(
|
||||
response.status_code))
|
||||
|
||||
try:
|
||||
details = response.json()
|
||||
except ValueError:
|
||||
raise FixtureError(
|
||||
"Could not decode course details as JSON: '{0}'".format(details)
|
||||
u"Could not decode course details as JSON: '{0}'".format(details)
|
||||
)
|
||||
|
||||
# Update the old details with our overrides
|
||||
@@ -335,7 +335,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not update course details to '{0}' with {1}: Status was {2}.".format(
|
||||
u"Could not update course details to '{0}' with {1}: Status was {2}.".format(
|
||||
self._course_details, url, response.status_code))
|
||||
|
||||
def _install_course_handouts(self):
|
||||
@@ -346,10 +346,10 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
# Construct HTML with each of the handout links
|
||||
handouts_li = [
|
||||
'<li><a href="/static/{handout}">Example Handout</a></li>'.format(handout=handout)
|
||||
u'<li><a href="/static/{handout}">Example Handout</a></li>'.format(handout=handout)
|
||||
for handout in self._handouts
|
||||
]
|
||||
handouts_html = '<ol class="treeview-handoutsnav">{}</ol>'.format("".join(handouts_li))
|
||||
handouts_html = u'<ol class="treeview-handoutsnav">{}</ol>'.format("".join(handouts_li))
|
||||
|
||||
# Update the course's handouts HTML
|
||||
payload = json.dumps({
|
||||
@@ -363,7 +363,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not update course handouts with {0}. Status was {1}".format(url, response.status_code))
|
||||
u"Could not update course handouts with {0}. Status was {1}".format(url, response.status_code))
|
||||
|
||||
def _install_course_updates(self):
|
||||
"""
|
||||
@@ -380,7 +380,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not add update to course: {0} with {1}. Status was {2}".format(
|
||||
u"Could not add update to course: {0} with {1}. Status was {2}".format(
|
||||
update, url, response.status_code))
|
||||
|
||||
def _upload_assets(self):
|
||||
@@ -406,7 +406,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
upload_response = self.session.post(url, files=files, headers=headers)
|
||||
|
||||
if not upload_response.ok:
|
||||
raise FixtureError('Could not upload {asset_name} with {url}. Status code: {code}'.format(
|
||||
raise FixtureError(u'Could not upload {asset_name} with {url}. Status code: {code}'.format(
|
||||
asset_name=asset_name, url=url, code=upload_response.status_code))
|
||||
|
||||
def _install_course_textbooks(self):
|
||||
@@ -421,7 +421,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not add book to course: {0} with {1}. Status was {2}".format(
|
||||
u"Could not add book to course: {0} with {1}. Status was {2}".format(
|
||||
book, url, response.status_code))
|
||||
|
||||
def _add_advanced_settings(self):
|
||||
@@ -438,7 +438,7 @@ class CourseFixture(XBlockContainerFixture):
|
||||
|
||||
if not response.ok:
|
||||
raise FixtureError(
|
||||
"Could not update advanced details to '{0}' with {1}: Status was {2}.".format(
|
||||
u"Could not update advanced details to '{0}' with {1}: Status was {2}.".format(
|
||||
self._advanced_settings, url, response.status_code))
|
||||
|
||||
def _create_xblock_children(self, parent_loc, xblock_descriptions):
|
||||
|
||||
@@ -57,7 +57,7 @@ class EdxNotesFixture(object):
|
||||
|
||||
if not response.ok:
|
||||
raise EdxNotesFixtureError(
|
||||
"Could not create notes {0}. Status was {1}".format(
|
||||
u"Could not create notes {0}. Status was {1}".format(
|
||||
json.dumps(self.notes), response.status_code
|
||||
)
|
||||
)
|
||||
@@ -73,7 +73,7 @@ class EdxNotesFixture(object):
|
||||
|
||||
if not response.ok:
|
||||
raise EdxNotesFixtureError(
|
||||
"Could not cleanup EdxNotes service {0}. Status was {1}".format(
|
||||
u"Could not cleanup EdxNotes service {0}. Status was {1}".format(
|
||||
json.dumps(self.notes), response.status_code
|
||||
)
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ class LibraryFixture(XBlockContainerFixture):
|
||||
"""
|
||||
String representation of the library fixture, useful for debugging.
|
||||
"""
|
||||
return "<LibraryFixture: org='{org}', number='{number}'>".format(**self.library_info)
|
||||
return u"<LibraryFixture: org='{org}', number='{number}'>".format(**self.library_info)
|
||||
|
||||
def install(self):
|
||||
"""
|
||||
@@ -82,7 +82,7 @@ class LibraryFixture(XBlockContainerFixture):
|
||||
err_msg = response.json().get('ErrMsg')
|
||||
except ValueError:
|
||||
err_msg = "Unknown Error"
|
||||
raise FixtureError("Could not create library {}. Status was {}, error was: {}".format(
|
||||
raise FixtureError(u"Could not create library {}. Status was {}, error was: {}".format(
|
||||
self.library_info, response.status_code, err_msg
|
||||
))
|
||||
|
||||
|
||||
@@ -46,5 +46,5 @@ class XQueueResponseFixture(object):
|
||||
|
||||
if not response.ok:
|
||||
raise XQueueResponseFixtureError(
|
||||
"Could not configure XQueue stub for queue '{1}'. Status code: {2}".format(
|
||||
u"Could not configure XQueue stub for queue '{1}'. Status code: {2}".format(
|
||||
self._pattern, self._response_dict))
|
||||
|
||||
@@ -70,14 +70,14 @@ class AccountSettingsPage(FieldsMixin, PageObject):
|
||||
|
||||
def get_value_of_order_history_row_item(self, field_id, field_name):
|
||||
""" Return the text value of the provided order field name."""
|
||||
query = self.q(css='.u-field-{} .u-field-order-{}'.format(field_id, field_name))
|
||||
query = self.q(css=u'.u-field-{} .u-field-order-{}'.format(field_id, field_name))
|
||||
return query.text if query.present else None
|
||||
|
||||
def order_button_is_visible(self, field_id):
|
||||
""" Check that if hovering over the order history row shows the
|
||||
order detail link or not.
|
||||
"""
|
||||
return self.q(css='.u-field-{} .u-field-{}'.format(field_id, 'link')).visible
|
||||
return self.q(css=u'.u-field-{} .u-field-{}'.format(field_id, 'link')).visible
|
||||
|
||||
@property
|
||||
def is_delete_button_visible(self):
|
||||
|
||||
@@ -40,7 +40,7 @@ class AnnotationComponentPage(PageObject):
|
||||
"""
|
||||
Return css selector for current active problem with sub_selector.
|
||||
"""
|
||||
return 'div[data-problem-id="{}"] {}'.format(
|
||||
return u'div[data-problem-id="{}"] {}'.format(
|
||||
self.q(css='.vert-{}'.format(self.active_problem + 1)).map(
|
||||
lambda el: el.get_attribute('data-id')).results[0],
|
||||
sub_selector,
|
||||
|
||||
@@ -161,7 +161,7 @@ class CourseOutlinePage(PageObject):
|
||||
try:
|
||||
subsection_index = subsection_titles.index(text_type(subsection_title))
|
||||
except ValueError:
|
||||
raise ValueError("Could not find subsection '{0}' in section '{1}'".format(
|
||||
raise ValueError(u"Could not find subsection '{0}' in section '{1}'".format(
|
||||
subsection_title, section_title
|
||||
))
|
||||
|
||||
@@ -189,11 +189,11 @@ class CourseOutlinePage(PageObject):
|
||||
try:
|
||||
section_title = self._section_titles()[section_index]
|
||||
except IndexError:
|
||||
raise ValueError("Section index '{0}' is out of range.".format(section_index))
|
||||
raise ValueError(u"Section index '{0}' is out of range.".format(section_index))
|
||||
try:
|
||||
subsection_title = self._subsection_titles(section_index)[subsection_index]
|
||||
except IndexError:
|
||||
raise ValueError("Subsection index '{0}' in section index '{1}' is out of range.".format(
|
||||
raise ValueError(u"Subsection index '{0}' in section index '{1}' is out of range.".format(
|
||||
subsection_index, section_index
|
||||
))
|
||||
|
||||
@@ -206,7 +206,7 @@ class CourseOutlinePage(PageObject):
|
||||
try:
|
||||
section_index = self._section_titles().index(section_title)
|
||||
except ValueError:
|
||||
raise ValueError("Could not find section '{0}'".format(section_title))
|
||||
raise ValueError(u"Could not find section '{0}'".format(section_title))
|
||||
|
||||
return section_index
|
||||
|
||||
@@ -253,7 +253,7 @@ class CourseOutlinePage(PageObject):
|
||||
self.wait_for(
|
||||
promise_check_func=lambda: courseware_page.nav.is_on_section(
|
||||
section_title, subsection_title),
|
||||
description="Waiting for course page with section '{0}' and subsection '{1}'".format(
|
||||
description=u"Waiting for course page with section '{0}' and subsection '{1}'".format(
|
||||
section_title, subsection_title)
|
||||
)
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class CoursewarePage(CoursePage, CompletionOnViewMixin):
|
||||
# When Student Notes feature is enabled, it looks for the content inside
|
||||
# `.edx-notes-wrapper-content` element (Otherwise, you will get an
|
||||
# additional html related to Student Notes).
|
||||
element = self.q(css='{} .edx-notes-wrapper-content'.format(self.xblock_component_selector))
|
||||
element = self.q(css=u'{} .edx-notes-wrapper-content'.format(self.xblock_component_selector))
|
||||
if element.first:
|
||||
return element.attrs('innerHTML')[index].strip()
|
||||
else:
|
||||
@@ -110,8 +110,8 @@ class CoursewarePage(CoursePage, CompletionOnViewMixin):
|
||||
for index, tab in enumerate(self.q(css='#sequence-list > li')):
|
||||
ActionChains(self.browser).move_to_element(tab).perform()
|
||||
self.wait_for_element_visibility(
|
||||
'#tab_{index} > .sequence-tooltip'.format(index=index),
|
||||
'Tab {index} should appear'.format(index=index)
|
||||
u'#tab_{index} > .sequence-tooltip'.format(index=index),
|
||||
u'Tab {index} should appear'.format(index=index)
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -142,7 +142,7 @@ class CoursewarePage(CoursePage, CompletionOnViewMixin):
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
sequential_position_css = '#sequence-list #tab_{0}'.format(sequential_position - 1)
|
||||
sequential_position_css = u'#sequence-list #tab_{0}'.format(sequential_position - 1)
|
||||
self.q(css=sequential_position_css).first.click()
|
||||
EmptyPromise(is_at_new_position, "Position navigation fulfilled").fulfill()
|
||||
|
||||
@@ -196,7 +196,7 @@ class CoursewarePage(CoursePage, CompletionOnViewMixin):
|
||||
return False
|
||||
|
||||
self.q(
|
||||
css='.{} > .sequence-nav-button.{}'.format(top_or_bottom_class, next_or_previous_class)
|
||||
css=u'.{} > .sequence-nav-button.{}'.format(top_or_bottom_class, next_or_previous_class)
|
||||
).first.click()
|
||||
EmptyPromise(is_at_new_tab_id, "Button navigation fulfilled").fulfill()
|
||||
|
||||
@@ -348,7 +348,7 @@ class CoursewarePage(CoursePage, CompletionOnViewMixin):
|
||||
answer_word(str): An answer words to be filled in the field
|
||||
"""
|
||||
self.wait_for_element_visibility('.input-cloud', "Word cloud fields are visible")
|
||||
css = '.input_cloud_section label:nth-child({}) .input-cloud'
|
||||
css = u'.input_cloud_section label:nth-child({}) .input-cloud'
|
||||
for index in range(1, len(self.q(css='.input-cloud')) + 1):
|
||||
self.q(css=css.format(index)).fill(answer_word + str(index))
|
||||
|
||||
@@ -461,7 +461,7 @@ class CourseNavPage(PageObject):
|
||||
for sec_index, sec_title in enumerate(section_titles):
|
||||
|
||||
if len(section_titles) < 1:
|
||||
self.warning("Could not find subsections for '{0}'".format(sec_title))
|
||||
self.warning(u"Could not find subsections for '{0}'".format(sec_title))
|
||||
else:
|
||||
# Add one to convert list index (starts at 0) to CSS index (starts at 1)
|
||||
nav_dict[sec_title] = self._subsection_titles(sec_index + 1)
|
||||
@@ -498,25 +498,25 @@ class CourseNavPage(PageObject):
|
||||
try:
|
||||
sec_index = self._section_titles().index(section_title)
|
||||
except ValueError:
|
||||
self.warning("Could not find section '{0}'".format(section_title))
|
||||
self.warning(u"Could not find section '{0}'".format(section_title))
|
||||
return
|
||||
|
||||
# Click the section to ensure it's open (no harm in clicking twice if it's already open)
|
||||
# Add one to convert from list index to CSS index
|
||||
section_css = '.course-navigation .chapter:nth-of-type({0})'.format(sec_index + 1)
|
||||
section_css = u'.course-navigation .chapter:nth-of-type({0})'.format(sec_index + 1)
|
||||
self.q(css=section_css).first.click()
|
||||
|
||||
# Get the subsection by index
|
||||
try:
|
||||
subsec_index = self._subsection_titles(sec_index + 1).index(subsection_title)
|
||||
except ValueError:
|
||||
msg = "Could not find subsection '{0}' in section '{1}'".format(subsection_title, section_title)
|
||||
msg = u"Could not find subsection '{0}' in section '{1}'".format(subsection_title, section_title)
|
||||
self.warning(msg)
|
||||
return
|
||||
|
||||
# Convert list indices (start at zero) to CSS indices (start at 1)
|
||||
subsection_css = (
|
||||
".course-navigation .chapter-content-container:nth-of-type({0}) "
|
||||
u".course-navigation .chapter-content-container:nth-of-type({0}) "
|
||||
".menu-item:nth-of-type({1})"
|
||||
).format(sec_index + 1, subsec_index + 1)
|
||||
|
||||
@@ -536,7 +536,7 @@ class CourseNavPage(PageObject):
|
||||
seq_index = all_items.index(vertical_title)
|
||||
|
||||
except ValueError:
|
||||
msg = "Could not find sequential '{0}'. Available sequentials: [{1}]".format(
|
||||
msg = u"Could not find sequential '{0}'. Available sequentials: [{1}]".format(
|
||||
vertical_title, ", ".join(all_items)
|
||||
)
|
||||
self.warning(msg)
|
||||
@@ -567,7 +567,7 @@ class CourseNavPage(PageObject):
|
||||
# Retrieve the subsection title for the section
|
||||
# Add one to the list index to get the CSS index, which starts at one
|
||||
subsection_css = (
|
||||
".course-navigation .chapter-content-container:nth-of-type({0}) "
|
||||
u".course-navigation .chapter-content-container:nth-of-type({0}) "
|
||||
".menu-item a p:nth-of-type(1)"
|
||||
).format(section_index)
|
||||
|
||||
@@ -587,7 +587,7 @@ class CourseNavPage(PageObject):
|
||||
Return a `Promise` that is fulfilled when the user is on
|
||||
the correct section and subsection.
|
||||
"""
|
||||
desc = "currently at section '{0}' and subsection '{1}'".format(section_title, subsection_title)
|
||||
desc = u"currently at section '{0}' and subsection '{1}'".format(section_title, subsection_title)
|
||||
return EmptyPromise(
|
||||
lambda: self.is_on_section(section_title, subsection_title), desc
|
||||
)
|
||||
|
||||
@@ -75,7 +75,7 @@ class DashboardPage(PageObject):
|
||||
# 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))
|
||||
raise Exception(u"No course named {} was found on the dashboard".format(course_name))
|
||||
|
||||
return enrollment_mode
|
||||
|
||||
@@ -103,7 +103,7 @@ class DashboardPage(PageObject):
|
||||
|
||||
upgrade_page.wait_for_page()
|
||||
else:
|
||||
raise Exception("No enrollment for {} is visible on the dashboard.".format(course_name))
|
||||
raise Exception(u"No enrollment for {} is visible on the dashboard.".format(course_name))
|
||||
|
||||
def view_course(self, course_id):
|
||||
"""
|
||||
@@ -114,7 +114,7 @@ class DashboardPage(PageObject):
|
||||
if link_css is not None:
|
||||
self.q(css=link_css).first.click()
|
||||
else:
|
||||
msg = "No links found for course {0}".format(course_id)
|
||||
msg = u"No links found for course {0}".format(course_id)
|
||||
self.warning(msg)
|
||||
|
||||
def _link_css(self, course_id):
|
||||
@@ -156,7 +156,7 @@ class DashboardPage(PageObject):
|
||||
}
|
||||
|
||||
else:
|
||||
msg = "No links found for course {0}".format(course_id)
|
||||
msg = u"No links found for course {0}".format(course_id)
|
||||
self.warning(msg)
|
||||
|
||||
def get_course_actions_link_css(self, course_id):
|
||||
|
||||
@@ -178,7 +178,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
@wait_for_js
|
||||
def is_response_editor_visible(self, response_id):
|
||||
"""Returns true if the response editor is present, false otherwise"""
|
||||
return self.is_element_visible(".response_{} .edit-post-body".format(response_id))
|
||||
return self.is_element_visible(u".response_{} .edit-post-body".format(response_id))
|
||||
|
||||
@wait_for_js
|
||||
def is_discussion_body_visible(self):
|
||||
@@ -201,27 +201,27 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
def is_response_visible(self, comment_id):
|
||||
"""Returns true if the response is viewable onscreen"""
|
||||
self.wait_for_ajax()
|
||||
return self.is_element_visible(".response_{} .response-body".format(comment_id))
|
||||
return self.is_element_visible(u".response_{} .response-body".format(comment_id))
|
||||
|
||||
def is_response_editable(self, response_id):
|
||||
"""Returns true if the edit response button is present, false otherwise"""
|
||||
with self.secondary_action_menu_open(".response_{} .discussion-response".format(response_id)):
|
||||
return self.is_element_visible(".response_{} .discussion-response .action-edit".format(response_id))
|
||||
with self.secondary_action_menu_open(u".response_{} .discussion-response".format(response_id)):
|
||||
return self.is_element_visible(u".response_{} .discussion-response .action-edit".format(response_id))
|
||||
|
||||
def is_response_deletable(self, response_id):
|
||||
"""
|
||||
Returns true if the delete response button is present, false otherwise
|
||||
"""
|
||||
with self.secondary_action_menu_open(".response_{} .discussion-response".format(response_id)):
|
||||
return self.is_element_visible(".response_{} .discussion-response .action-delete".format(response_id))
|
||||
with self.secondary_action_menu_open(u".response_{} .discussion-response".format(response_id)):
|
||||
return self.is_element_visible(u".response_{} .discussion-response .action-delete".format(response_id))
|
||||
|
||||
def get_response_body(self, response_id):
|
||||
return self._get_element_text(".response_{} .response-body".format(response_id))
|
||||
|
||||
def start_response_edit(self, response_id):
|
||||
"""Click the edit button for the response, loading the editing view"""
|
||||
with self.secondary_action_menu_open(".response_{} .discussion-response".format(response_id)):
|
||||
self._find_within(".response_{} .discussion-response .action-edit".format(response_id)).first.click()
|
||||
with self.secondary_action_menu_open(u".response_{} .discussion-response".format(response_id)):
|
||||
self._find_within(u".response_{} .discussion-response .action-edit".format(response_id)).first.click()
|
||||
EmptyPromise(
|
||||
lambda: self.is_response_editor_visible(response_id),
|
||||
"Response edit started"
|
||||
@@ -237,26 +237,26 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
vote_count_element = self.browser.find_element_by_css_selector(vote_count_css)
|
||||
# To get the vote count, one must hover over the element first.
|
||||
hover(self.browser, vote_count_element)
|
||||
return self._get_element_text(".response_{} .discussion-response .action-vote .vote-count".format(response_id))
|
||||
return self._get_element_text(u".response_{} .discussion-response .action-vote .vote-count".format(response_id))
|
||||
|
||||
def vote_response(self, response_id):
|
||||
current_count = self.get_response_vote_count(response_id)
|
||||
self._find_within(".response_{} .discussion-response .action-vote".format(response_id)).first.click()
|
||||
self._find_within(u".response_{} .discussion-response .action-vote".format(response_id)).first.click()
|
||||
self.wait_for(
|
||||
lambda: current_count != self.get_response_vote_count(response_id),
|
||||
description="Vote updated for {response_id}".format(response_id=response_id)
|
||||
description=u"Vote updated for {response_id}".format(response_id=response_id)
|
||||
)
|
||||
|
||||
def cannot_vote_response(self, response_id):
|
||||
"""Assert that the voting button is not visible on this response"""
|
||||
return not self.is_element_visible(".response_{} .discussion-response .action-vote".format(response_id))
|
||||
return not self.is_element_visible(u".response_{} .discussion-response .action-vote".format(response_id))
|
||||
|
||||
def is_response_reported(self, response_id):
|
||||
return self.is_element_visible(".response_{} .discussion-response .post-label-reported".format(response_id))
|
||||
|
||||
def report_response(self, response_id):
|
||||
with self.secondary_action_menu_open(".response_{} .discussion-response".format(response_id)):
|
||||
self._find_within(".response_{} .discussion-response .action-report".format(response_id)).first.click()
|
||||
self._find_within(u".response_{} .discussion-response .action-report".format(response_id)).first.click()
|
||||
self.wait_for_ajax()
|
||||
EmptyPromise(
|
||||
lambda: self.is_response_reported(response_id),
|
||||
@@ -265,7 +265,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def cannot_report_response(self, response_id):
|
||||
"""Assert that the reporting button is not visible on this response"""
|
||||
return not self.is_element_visible(".response_{} .discussion-response .action-report".format(response_id))
|
||||
return not self.is_element_visible(u".response_{} .discussion-response .action-report".format(response_id))
|
||||
|
||||
def is_response_endorsed(self, response_id):
|
||||
return "endorsed" in self._get_element_text(".response_{} .discussion-response .posted-details".format(response_id))
|
||||
@@ -280,7 +280,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def set_response_editor_value(self, response_id, new_body):
|
||||
"""Replace the contents of the response editor"""
|
||||
self._find_within(".response_{} .discussion-response .wmd-input".format(response_id)).fill(new_body)
|
||||
self._find_within(u".response_{} .discussion-response .wmd-input".format(response_id)).fill(new_body)
|
||||
|
||||
def verify_link_editor_error_messages_shown(self):
|
||||
"""
|
||||
@@ -322,7 +322,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
was successfully updated, False otherwise.
|
||||
"""
|
||||
self._find_within(
|
||||
".response_{} .discussion-response .post-update".format(
|
||||
u".response_{} .discussion-response .post-update".format(
|
||||
response_id
|
||||
)
|
||||
).first.click()
|
||||
@@ -337,13 +337,13 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def is_show_comments_visible(self, response_id):
|
||||
"""Returns true if the "show comments" link is visible for a response"""
|
||||
return self.is_element_visible(".response_{} .action-show-comments".format(response_id))
|
||||
return self.is_element_visible(u".response_{} .action-show-comments".format(response_id))
|
||||
|
||||
def show_comments(self, response_id):
|
||||
"""Click the "show comments" link for a response"""
|
||||
self._find_within(".response_{} .action-show-comments".format(response_id)).first.click()
|
||||
self._find_within(u".response_{} .action-show-comments".format(response_id)).first.click()
|
||||
EmptyPromise(
|
||||
lambda: self.is_element_visible(".response_{} .comments".format(response_id)),
|
||||
lambda: self.is_element_visible(u".response_{} .comments".format(response_id)),
|
||||
"Comments shown"
|
||||
).fulfill()
|
||||
|
||||
@@ -353,7 +353,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def is_comment_visible(self, comment_id):
|
||||
"""Returns true if the comment is viewable onscreen"""
|
||||
return self.is_element_visible("#comment_{} .response-body".format(comment_id))
|
||||
return self.is_element_visible(u"#comment_{} .response-body".format(comment_id))
|
||||
|
||||
def get_comment_body(self, comment_id):
|
||||
return self._get_element_text("#comment_{} .response-body".format(comment_id))
|
||||
@@ -361,12 +361,12 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
def is_comment_deletable(self, comment_id):
|
||||
"""Returns true if the delete comment button is present, false otherwise"""
|
||||
with self.secondary_action_menu_open("#comment_{}".format(comment_id)):
|
||||
return self.is_element_visible("#comment_{} .action-delete".format(comment_id))
|
||||
return self.is_element_visible(u"#comment_{} .action-delete".format(comment_id))
|
||||
|
||||
def delete_comment(self, comment_id):
|
||||
with self.handle_alert():
|
||||
with self.secondary_action_menu_open("#comment_{}".format(comment_id)):
|
||||
self._find_within("#comment_{} .action-delete".format(comment_id)).first.click()
|
||||
self._find_within(u"#comment_{} .action-delete".format(comment_id)).first.click()
|
||||
EmptyPromise(
|
||||
lambda: not self.is_comment_visible(comment_id),
|
||||
"Deleted comment was removed"
|
||||
@@ -375,7 +375,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
def is_comment_editable(self, comment_id):
|
||||
"""Returns true if the edit comment button is present, false otherwise"""
|
||||
with self.secondary_action_menu_open("#comment_{}".format(comment_id)):
|
||||
return self.is_element_visible("#comment_{} .action-edit".format(comment_id))
|
||||
return self.is_element_visible(u"#comment_{} .action-edit".format(comment_id))
|
||||
|
||||
def is_comment_editor_visible(self, comment_id):
|
||||
"""Returns true if the comment editor is present, false otherwise"""
|
||||
@@ -388,7 +388,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
"""Click the edit button for the comment, loading the editing view"""
|
||||
old_body = self.get_comment_body(comment_id)
|
||||
with self.secondary_action_menu_open("#comment_{}".format(comment_id)):
|
||||
self._find_within("#comment_{} .action-edit".format(comment_id)).first.click()
|
||||
self._find_within(u"#comment_{} .action-edit".format(comment_id)).first.click()
|
||||
EmptyPromise(
|
||||
lambda: (
|
||||
self.is_comment_editor_visible(comment_id) and
|
||||
@@ -400,11 +400,11 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def set_comment_editor_value(self, comment_id, new_body):
|
||||
"""Replace the contents of the comment editor"""
|
||||
self._find_within("#comment_{} .wmd-input".format(comment_id)).fill(new_body)
|
||||
self._find_within(u"#comment_{} .wmd-input".format(comment_id)).fill(new_body)
|
||||
|
||||
def submit_comment_edit(self, comment_id, new_comment_body):
|
||||
"""Click the submit button on the comment editor"""
|
||||
self._find_within("#comment_{} .post-update".format(comment_id)).first.click()
|
||||
self._find_within(u"#comment_{} .post-update".format(comment_id)).first.click()
|
||||
self.wait_for_ajax()
|
||||
EmptyPromise(
|
||||
lambda: (
|
||||
@@ -417,7 +417,7 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def cancel_comment_edit(self, comment_id, original_body):
|
||||
"""Click the cancel button on the comment editor"""
|
||||
self._find_within("#comment_{} .post-cancel".format(comment_id)).first.click()
|
||||
self._find_within(u"#comment_{} .post-cancel".format(comment_id)).first.click()
|
||||
EmptyPromise(
|
||||
lambda: (
|
||||
not self.is_comment_editor_visible(comment_id) and
|
||||
@@ -459,7 +459,7 @@ class DiscussionSortPreferencePage(CoursePage):
|
||||
"""
|
||||
Change the option of sorting by clicking on new option.
|
||||
"""
|
||||
self.q(css=".forum-nav-sort-control option[value='{0}']".format(sort_by)).click()
|
||||
self.q(css=u".forum-nav-sort-control option[value='{0}']".format(sort_by)).click()
|
||||
# Click initiates an ajax call, waiting for it to complete
|
||||
self.wait_for_ajax()
|
||||
|
||||
@@ -475,7 +475,7 @@ class DiscussionTabSingleThreadPage(CoursePage):
|
||||
super(DiscussionTabSingleThreadPage, self).__init__(browser, course_id)
|
||||
self.thread_page = DiscussionThreadPage(
|
||||
browser,
|
||||
"body.discussion .discussion-article[data-id='{thread_id}']".format(thread_id=thread_id)
|
||||
u"body.discussion .discussion-article[data-id='{thread_id}']".format(thread_id=thread_id)
|
||||
)
|
||||
self.url_path = "discussion/forum/{discussion_id}/threads/{thread_id}".format(
|
||||
discussion_id=discussion_id, thread_id=thread_id
|
||||
@@ -526,7 +526,7 @@ class InlineDiscussionPage(PageObject, DiscussionPageMixin):
|
||||
def __init__(self, browser, discussion_id):
|
||||
super(InlineDiscussionPage, self).__init__(browser)
|
||||
self.root_selector = (
|
||||
".discussion-module[data-discussion-id='{discussion_id}'] ".format(
|
||||
u".discussion-module[data-discussion-id='{discussion_id}'] ".format(
|
||||
discussion_id=discussion_id
|
||||
)
|
||||
)
|
||||
@@ -561,8 +561,8 @@ class InlineDiscussionPage(PageObject, DiscussionPageMixin):
|
||||
|
||||
def click_element(self, selector):
|
||||
self.wait_for_element_presence(
|
||||
"{discussion} {selector}".format(discussion=self.root_selector, selector=selector),
|
||||
"{selector} is visible".format(selector=selector)
|
||||
u"{discussion} {selector}".format(discussion=self.root_selector, selector=selector),
|
||||
u"{selector} is visible".format(selector=selector)
|
||||
)
|
||||
self._find_within(selector).click()
|
||||
|
||||
@@ -582,7 +582,7 @@ class InlineDiscussionPage(PageObject, DiscussionPageMixin):
|
||||
Clicks the link for the specified thread to show the detailed view.
|
||||
"""
|
||||
self.wait_for_element_presence('.forum-nav-thread-link', 'Thread list has loaded')
|
||||
thread_selector = ".forum-nav-thread[data-id='{thread_id}'] .forum-nav-thread-link".format(thread_id=thread_id)
|
||||
thread_selector = u".forum-nav-thread[data-id='{thread_id}'] .forum-nav-thread-link".format(thread_id=thread_id)
|
||||
self._find_within(thread_selector).first.click()
|
||||
self.thread_page = InlineDiscussionThreadPage(self.browser, thread_id) # pylint: disable=attribute-defined-outside-init
|
||||
self.thread_page.wait_for_page()
|
||||
@@ -595,7 +595,7 @@ class InlineDiscussionThreadPage(DiscussionThreadPage):
|
||||
def __init__(self, browser, thread_id):
|
||||
super(InlineDiscussionThreadPage, self).__init__(
|
||||
browser,
|
||||
".discussion-module .discussion-article[data-id='{thread_id}']".format(thread_id=thread_id)
|
||||
u".discussion-module .discussion-article[data-id='{thread_id}']".format(thread_id=thread_id)
|
||||
)
|
||||
|
||||
def is_thread_anonymous(self):
|
||||
@@ -699,7 +699,7 @@ class DiscussionTabHomePage(CoursePage, DiscussionPageMixin):
|
||||
return self.q(css=".search-alert").filter(lambda elem: text in elem.text)
|
||||
|
||||
for alert_id in _match_messages(text).attrs("id"):
|
||||
self.q(css="{}#{} .dismiss".format(self.ALERT_SELECTOR, alert_id)).click()
|
||||
self.q(css=u"{}#{} .dismiss".format(self.ALERT_SELECTOR, alert_id)).click()
|
||||
EmptyPromise(
|
||||
lambda: _match_messages(text).results == [],
|
||||
"waiting for dismissed alerts to disappear"
|
||||
|
||||
@@ -22,7 +22,7 @@ class NoteChild(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to this particular `NoteChild` context
|
||||
"""
|
||||
return "{}#{} {}".format(
|
||||
return u"{}#{} {}".format(
|
||||
self.BODY_SELECTOR,
|
||||
self.item_id,
|
||||
selector,
|
||||
@@ -101,7 +101,7 @@ class EdxNotesTagsGroup(NoteChild, EdxNotesGroupMixin):
|
||||
top_script = "return " + title_selector + ".getBoundingClientRect().top;"
|
||||
EmptyPromise(
|
||||
lambda: 8 < self.browser.execute_script(top_script) < 12,
|
||||
"Expected tag title '{}' to scroll to top, but was at location {}".format(
|
||||
u"Expected tag title '{}' to scroll to top, but was at location {}".format(
|
||||
self.title, self.browser.execute_script(top_script)
|
||||
)
|
||||
).fulfill()
|
||||
@@ -177,7 +177,7 @@ class EdxNotesPageView(PageObject):
|
||||
try:
|
||||
return self.wait_for_page()
|
||||
except BrokenPromise:
|
||||
raise PageLoadError("Timed out waiting to load page '{!r}'".format(self))
|
||||
raise PageLoadError(u"Timed out waiting to load page '{!r}'".format(self))
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return all([
|
||||
@@ -191,13 +191,13 @@ class EdxNotesPageView(PageObject):
|
||||
"""
|
||||
Indicates if tab is closable or not.
|
||||
"""
|
||||
return self.q(css="{} .action-close".format(self.TAB_SELECTOR)).present
|
||||
return self.q(css=u"{} .action-close".format(self.TAB_SELECTOR)).present
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the tab.
|
||||
"""
|
||||
self.q(css="{} .action-close".format(self.TAB_SELECTOR)).first.click()
|
||||
self.q(css=u"{} .action-close".format(self.TAB_SELECTOR)).first.click()
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
@@ -652,7 +652,7 @@ class EdxNoteHighlight(NoteChild):
|
||||
label_exists = False
|
||||
EmptyPromise(
|
||||
lambda: len(self.q(css=self._bounded_selector("li.annotator-item > label.sr"))) > sr_index,
|
||||
"Expected more than '{}' sr labels".format(sr_index)
|
||||
u"Expected more than '{}' sr labels".format(sr_index)
|
||||
).fulfill()
|
||||
annotator_field_label = self.q(css=self._bounded_selector("li.annotator-item > label.sr"))[sr_index]
|
||||
for_attrib_correct = annotator_field_label.get_attribute("for") == "annotator-field-" + str(field_index)
|
||||
|
||||
@@ -16,7 +16,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
Return field with field_id.
|
||||
"""
|
||||
query = self.q(css='.u-field-{}'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{}'.format(field_id))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def wait_for_field(self, field_id):
|
||||
@@ -25,7 +25,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
EmptyPromise(
|
||||
lambda: self.field(field_id) is not None,
|
||||
"Field with id \"{0}\" is in DOM.".format(field_id)
|
||||
u"Field with id \"{0}\" is in DOM.".format(field_id)
|
||||
).fulfill()
|
||||
|
||||
def mode_for_field(self, field_id):
|
||||
@@ -37,7 +37,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{}'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{}'.format(field_id))
|
||||
|
||||
if not query.present:
|
||||
return None
|
||||
@@ -59,7 +59,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-icon'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} .u-field-icon'.format(field_id))
|
||||
return query.present and icon_id in query.attrs('class')[0].split()
|
||||
|
||||
def title_for_field(self, field_id):
|
||||
@@ -68,7 +68,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-title'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} .u-field-title'.format(field_id))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def message_for_field(self, field_id):
|
||||
@@ -77,7 +77,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-message'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} .u-field-message'.format(field_id))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def message_for_textarea_field(self, field_id):
|
||||
@@ -86,7 +86,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-message-help'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} .u-field-message-help'.format(field_id))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def wait_for_message(self, field_id, message):
|
||||
@@ -95,7 +95,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
EmptyPromise(
|
||||
lambda: message in (self.message_for_field(field_id) or ''),
|
||||
"Messsage \"{0}\" is visible.".format(message)
|
||||
u"Messsage \"{0}\" is visible.".format(message)
|
||||
).fulfill()
|
||||
|
||||
def indicator_for_field(self, field_id):
|
||||
@@ -104,7 +104,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-message .fa'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} .u-field-message .fa'.format(field_id))
|
||||
return [
|
||||
class_name for class_name
|
||||
in query.attrs('class')[0].split(' ')
|
||||
@@ -117,14 +117,14 @@ class FieldsMixin(object):
|
||||
"""
|
||||
EmptyPromise(
|
||||
lambda: indicator == self.indicator_for_field(field_id),
|
||||
"Indicator \"{0}\" is visible.".format(self.indicator_for_field(field_id))
|
||||
u"Indicator \"{0}\" is visible.".format(self.indicator_for_field(field_id))
|
||||
).fulfill()
|
||||
|
||||
def make_field_editable(self, field_id):
|
||||
"""
|
||||
Make a field editable.
|
||||
"""
|
||||
query = self.q(css='.u-field-{}'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{}'.format(field_id))
|
||||
|
||||
if not query.present:
|
||||
return None
|
||||
@@ -137,7 +137,7 @@ class FieldsMixin(object):
|
||||
self.wait_for_element_visibility(bio_field_selector, 'Bio field is visible')
|
||||
self.browser.execute_script("$('" + bio_field_selector + "').click();")
|
||||
else:
|
||||
self.q(css='.u-field-{}'.format(field_id)).first.click()
|
||||
self.q(css=u'.u-field-{}'.format(field_id)).first.click()
|
||||
|
||||
def value_for_readonly_field(self, field_id):
|
||||
"""
|
||||
@@ -145,7 +145,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} .u-field-value'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} .u-field-value'.format(field_id))
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
@@ -157,7 +157,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} input'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} input'.format(field_id))
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
@@ -176,7 +176,7 @@ class FieldsMixin(object):
|
||||
self.wait_for_field(field_id)
|
||||
self.make_field_editable(field_id)
|
||||
|
||||
field_selector = '.u-field-{} textarea'.format(field_id)
|
||||
field_selector = u'.u-field-{} textarea'.format(field_id)
|
||||
self.wait_for_element_presence(field_selector, 'Editable textarea is present.')
|
||||
|
||||
query = self.q(css=field_selector)
|
||||
@@ -190,7 +190,7 @@ class FieldsMixin(object):
|
||||
self.wait_for_field(field_id)
|
||||
self.wait_for_ajax()
|
||||
|
||||
return self.q(css='.u-field-{} .u-field-value .u-field-value-readonly'.format(field_id)).text[0]
|
||||
return self.q(css=u'.u-field-{} .u-field-value .u-field-value-readonly'.format(field_id)).text[0]
|
||||
|
||||
def value_for_dropdown_field(self, field_id, value=None, focus_out=False):
|
||||
"""
|
||||
@@ -200,7 +200,7 @@ class FieldsMixin(object):
|
||||
|
||||
self.make_field_editable(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} select'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{} select'.format(field_id))
|
||||
if not query.present:
|
||||
return None
|
||||
|
||||
@@ -218,7 +218,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-link-title-{}'.format(field_id))
|
||||
query = self.q(css=u'.u-field-link-title-{}'.format(field_id))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def wait_for_link_title_for_link_field(self, field_id, expected_title):
|
||||
@@ -227,7 +227,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
return EmptyPromise(
|
||||
lambda: self.link_title_for_link_field(field_id) == expected_title,
|
||||
"Link field with link title \"{0}\" is visible.".format(expected_title)
|
||||
u"Link field with link title \"{0}\" is visible.".format(expected_title)
|
||||
).fulfill()
|
||||
|
||||
def click_on_link_in_link_field(self, field_id, field_type='a'):
|
||||
@@ -236,7 +236,7 @@ class FieldsMixin(object):
|
||||
"""
|
||||
self.wait_for_field(field_id)
|
||||
|
||||
query = self.q(css='.u-field-{} {}'.format(field_id, field_type))
|
||||
query = self.q(css=u'.u-field-{} {}'.format(field_id, field_type))
|
||||
if query.present:
|
||||
query.first.click()
|
||||
|
||||
@@ -244,5 +244,5 @@ class FieldsMixin(object):
|
||||
"""
|
||||
Returns bool based on the highlighted border for field.
|
||||
"""
|
||||
query = self.q(css='.u-field-{}.error'.format(field_id))
|
||||
query = self.q(css=u'.u-field-{}.error'.format(field_id))
|
||||
return True if query.present else False
|
||||
|
||||
@@ -162,7 +162,7 @@ class BulkEmailPage(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to the bulk-email context.
|
||||
"""
|
||||
return '.send-email {}'.format(selector)
|
||||
return u'.send-email {}'.format(selector)
|
||||
|
||||
def _select_recipient(self, recipient):
|
||||
"""
|
||||
@@ -302,7 +302,7 @@ class CohortManagementSection(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to the cohort management context.
|
||||
"""
|
||||
return '.cohort-management {}'.format(selector)
|
||||
return u'.cohort-management {}'.format(selector)
|
||||
|
||||
def _get_cohort_options(self):
|
||||
"""
|
||||
@@ -708,13 +708,13 @@ class DiscussionManagementSection(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to the divided discussion management context.
|
||||
"""
|
||||
return '.discussions-management {}'.format(selector)
|
||||
return u'.discussions-management {}'.format(selector)
|
||||
|
||||
def is_save_button_disabled(self, key):
|
||||
"""
|
||||
Returns the status for form's save button, enabled or disabled.
|
||||
"""
|
||||
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
|
||||
save_button_css = u'%s %s' % (self.discussion_form_selectors[key], '.action-save')
|
||||
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
|
||||
return disabled[0] == 'true'
|
||||
|
||||
@@ -729,7 +729,7 @@ class DiscussionManagementSection(PageObject):
|
||||
"""
|
||||
Returns the text of discussion topic headings if it exists, otherwise return False.
|
||||
"""
|
||||
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
|
||||
form_heading_css = u'%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
|
||||
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
|
||||
|
||||
if len(discussion_heading) == 0:
|
||||
@@ -753,7 +753,7 @@ class DiscussionManagementSection(PageObject):
|
||||
"""
|
||||
Saves the discussion topics.
|
||||
"""
|
||||
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
|
||||
save_button_css = u'%s %s' % (self.discussion_form_selectors[key], '.action-save')
|
||||
self.q(css=self._bounded_selector(save_button_css)).first.click()
|
||||
|
||||
def always_inline_discussion_selected(self):
|
||||
@@ -791,7 +791,7 @@ class DiscussionManagementSection(PageObject):
|
||||
"""
|
||||
Returns the message related to modifying discussion topics.
|
||||
"""
|
||||
title_css = "%s .message-%s .message-title" % (self.discussion_form_selectors[key], msg_type)
|
||||
title_css = u"%s .message-%s .message-title" % (self.discussion_form_selectors[key], msg_type)
|
||||
|
||||
EmptyPromise(
|
||||
lambda: self.q(css=self._bounded_selector(title_css)),
|
||||
@@ -871,8 +871,8 @@ class MembershipPageAutoEnrollSection(PageObject):
|
||||
MembershipPageAutoEnrollSection.NOTIFICATION_ERROR
|
||||
Returns True if a {section_type} notification is displayed.
|
||||
"""
|
||||
notification_selector = '.auto_enroll_csv .results .message-%s' % section_type
|
||||
self.wait_for_element_presence(notification_selector, "%s Notification" % section_type.title())
|
||||
notification_selector = u'.auto_enroll_csv .results .message-%s' % section_type
|
||||
self.wait_for_element_presence(notification_selector, u"%s Notification" % section_type.title())
|
||||
return self.q(css=notification_selector).is_present()
|
||||
|
||||
def first_notification_message(self, section_type):
|
||||
@@ -881,8 +881,8 @@ class MembershipPageAutoEnrollSection(PageObject):
|
||||
MembershipPageAutoEnrollSection.NOTIFICATION_ERROR
|
||||
Returns the first message from the list of messages in the {section_type} section.
|
||||
"""
|
||||
error_message_selector = '.auto_enroll_csv .results .message-%s li.summary-item' % section_type
|
||||
self.wait_for_element_presence(error_message_selector, "%s message" % section_type.title())
|
||||
error_message_selector = u'.auto_enroll_csv .results .message-%s li.summary-item' % section_type
|
||||
self.wait_for_element_presence(error_message_selector, u"%s message" % section_type.title())
|
||||
return self.q(css=error_message_selector).text[0]
|
||||
|
||||
def upload_correct_csv_file(self):
|
||||
@@ -915,8 +915,8 @@ class MembershipPageAutoEnrollSection(PageObject):
|
||||
"""
|
||||
Fill in the form with the provided email and submit it.
|
||||
"""
|
||||
email_selector = "{} textarea".format(self.batch_enrollment_selector)
|
||||
enrollment_button = "{} .enrollment-button[data-action='enroll']".format(self.batch_enrollment_selector)
|
||||
email_selector = u"{} textarea".format(self.batch_enrollment_selector)
|
||||
enrollment_button = u"{} .enrollment-button[data-action='enroll']".format(self.batch_enrollment_selector)
|
||||
|
||||
# Fill the email addresses after the email selector is visible.
|
||||
self.wait_for_element_visibility(email_selector, 'Email field is visible')
|
||||
@@ -932,9 +932,9 @@ class MembershipPageAutoEnrollSection(PageObject):
|
||||
"""
|
||||
Check notification div is visible and have message.
|
||||
"""
|
||||
notification_selector = '{} .request-response'.format(self.batch_enrollment_selector)
|
||||
notification_selector = u'{} .request-response'.format(self.batch_enrollment_selector)
|
||||
self.wait_for_element_visibility(notification_selector, 'Notification div is visible')
|
||||
return self.q(css="{} h3".format(notification_selector)).text
|
||||
return self.q(css=u"{} h3".format(notification_selector)).text
|
||||
|
||||
|
||||
class MembershipPageBetaTesterSection(PageObject):
|
||||
@@ -953,8 +953,8 @@ class MembershipPageBetaTesterSection(PageObject):
|
||||
"""
|
||||
Fill in the form with the provided username and submit it.
|
||||
"""
|
||||
username_selector = "{} textarea".format(self.batch_beta_tester_selector)
|
||||
enrollment_button = "{} .enrollment-button[data-action='add']".format(self.batch_beta_tester_selector)
|
||||
username_selector = u"{} textarea".format(self.batch_beta_tester_selector)
|
||||
enrollment_button = u"{} .enrollment-button[data-action='add']".format(self.batch_beta_tester_selector)
|
||||
|
||||
# Fill the username after the username selector is visible.
|
||||
self.wait_for_element_visibility(username_selector, 'username field is visible')
|
||||
@@ -968,10 +968,10 @@ class MembershipPageBetaTesterSection(PageObject):
|
||||
"""
|
||||
Check notification div is visible and have message.
|
||||
"""
|
||||
notification_selector = '{} .request-response'.format(self.batch_beta_tester_selector)
|
||||
notification_selector = u'{} .request-response'.format(self.batch_beta_tester_selector)
|
||||
self.wait_for_element_visibility(notification_selector, 'Notification div is visible')
|
||||
notification_header_text = self.q(css="{} h3".format(notification_selector)).text
|
||||
notification_username = self.q(css="{} li".format(notification_selector)).text
|
||||
notification_header_text = self.q(css=u"{} h3".format(notification_selector)).text
|
||||
notification_username = self.q(css=u"{} li".format(notification_selector)).text
|
||||
return notification_header_text, notification_username
|
||||
|
||||
|
||||
@@ -1198,7 +1198,7 @@ class StudentAdminPage(PageObject):
|
||||
Returns the input box with the given name
|
||||
for this object's container.
|
||||
"""
|
||||
return self.q(css='{} input[name={}]'.format(self.CONTAINER, input_name))
|
||||
return self.q(css=u'{} input[name={}]'.format(self.CONTAINER, input_name))
|
||||
|
||||
@property
|
||||
def problem_location_input(self):
|
||||
@@ -1285,7 +1285,7 @@ class StudentAdminPage(PageObject):
|
||||
"""
|
||||
Promise Check Function
|
||||
"""
|
||||
query = self.q(css="{} .{}".format(self.CONTAINER, self.TASK_HISTORY_TABLE_NAME))
|
||||
query = self.q(css=u"{} .{}".format(self.CONTAINER, self.TASK_HISTORY_TABLE_NAME))
|
||||
return query.visible, query
|
||||
|
||||
return Promise(check_func, "Waiting for student admin task history table to be visible.").fulfill()
|
||||
@@ -1363,14 +1363,14 @@ class EntranceExamAdmin(StudentAdminPage):
|
||||
"""
|
||||
Return Let Student Skip Entrance Exam button.
|
||||
"""
|
||||
return self.q(css='{} input[name=skip-entrance-exam]'.format(self.CONTAINER))
|
||||
return self.q(css=u'{} input[name=skip-entrance-exam]'.format(self.CONTAINER))
|
||||
|
||||
@property
|
||||
def top_notification(self):
|
||||
"""
|
||||
Returns show background task history for student button.
|
||||
"""
|
||||
return self.q(css='{} .request-response-error'.format(self.CONTAINER)).first
|
||||
return self.q(css=u'{} .request-response-error'.format(self.CONTAINER)).first
|
||||
|
||||
def are_all_buttons_visible(self):
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,7 @@ from common.test.acceptance.pages.lms.fields import FieldsMixin
|
||||
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
|
||||
from common.test.acceptance.tests.helpers import select_option_by_value
|
||||
|
||||
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
|
||||
PROFILE_VISIBILITY_SELECTOR = u'#u-field-select-account_privacy option[value="{}"]'
|
||||
PROFILE_VISIBILITY_INPUT = '#u-field-select-account_privacy'
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ class Badge(PageObject):
|
||||
"""
|
||||
Execute javascript to bring the popup(.badges-model) inside the window.
|
||||
"""
|
||||
script_to_execute = ("var popup = document.querySelectorAll('.badges-modal')[0];;"
|
||||
"popup.style.left = '20%';")
|
||||
script_to_execute = (u"var popup = document.querySelectorAll('.badges-modal')[0];;"
|
||||
u"popup.style.left = '20%';")
|
||||
self.browser.execute_script(script_to_execute)
|
||||
|
||||
def close_modal(self):
|
||||
@@ -145,7 +145,7 @@ class LearnerProfilePage(FieldsMixin, PageObject):
|
||||
if privacy != self.privacy:
|
||||
query = self.q(css=PROFILE_VISIBILITY_INPUT)
|
||||
select_option_by_value(query, privacy)
|
||||
EmptyPromise(lambda: privacy == self.privacy, 'Privacy is set to {}'.format(privacy)).fulfill()
|
||||
EmptyPromise(lambda: privacy == self.privacy, u'Privacy is set to {}'.format(privacy)).fulfill()
|
||||
self.q(css='.btn-change-privacy').first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class LibraryContentXBlockWrapper(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to this particular block's context
|
||||
"""
|
||||
return '{}[data-id="{}"] {}'.format(
|
||||
return u'{}[data-id="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
|
||||
@@ -311,7 +311,7 @@ class ProblemPage(PageObject):
|
||||
status_selector(str): status selector string.
|
||||
message(str): description of promise, to be logged.
|
||||
"""
|
||||
msg = "Wait for status to be {}".format(message)
|
||||
msg = u"Wait for status to be {}".format(message)
|
||||
self.wait_for_element_visibility(status_selector, msg)
|
||||
|
||||
def is_expected_status_visible(self, status_selector):
|
||||
@@ -375,7 +375,7 @@ class ProblemPage(PageObject):
|
||||
Arguments:
|
||||
hint_index (int): Index of a displayed hint
|
||||
"""
|
||||
css = '.notification-hint .notification-message > ol > li.hint-index-{hint_index}'.format(
|
||||
css = u'.notification-hint .notification-message > ol > li.hint-index-{hint_index}'.format(
|
||||
hint_index=hint_index
|
||||
)
|
||||
self.wait_for(
|
||||
@@ -387,7 +387,7 @@ class ProblemPage(PageObject):
|
||||
"""
|
||||
Click on the "Review" button within the visible notification.
|
||||
"""
|
||||
css_string = '.notification.notification-{notification_type} .review-btn'.format(
|
||||
css_string = u'.notification.notification-{notification_type} .review-btn'.format(
|
||||
notification_type=notification_type
|
||||
)
|
||||
|
||||
@@ -468,7 +468,7 @@ class ProblemPage(PageObject):
|
||||
|
||||
Problem <clarification>clarification text hidden by an icon in rendering</clarification> Text
|
||||
"""
|
||||
self.q(css='div.problem .clarification:nth-child({index}) span[data-tooltip]'.format(index=index + 1)).click()
|
||||
self.q(css=u'div.problem .clarification:nth-child({index}) span[data-tooltip]'.format(index=index + 1)).click()
|
||||
|
||||
@property
|
||||
def visible_tooltip_text(self):
|
||||
@@ -489,10 +489,10 @@ class ProblemPage(PageObject):
|
||||
"""
|
||||
Check if the given answer/choice is highlighted for choice group.
|
||||
"""
|
||||
choice_status_xpath = ('//fieldset/div[contains(@class, "field")][{{0}}]'
|
||||
'/label[contains(@class, "choicegroup_{choice}")]'
|
||||
'/span[contains(@class, "status {choice}")]'.format(choice=choice))
|
||||
any_status_xpath = '//fieldset/div[contains(@class, "field")][{0}]/label/span'
|
||||
choice_status_xpath = (u'//fieldset/div[contains(@class, "field")][{{0}}]'
|
||||
u'/label[contains(@class, "choicegroup_{choice}")]'
|
||||
u'/span[contains(@class, "status {choice}")]'.format(choice=choice))
|
||||
any_status_xpath = u'//fieldset/div[contains(@class, "field")][{0}]/label/span'
|
||||
for choice in choices_list:
|
||||
if not self.q(xpath=choice_status_xpath.format(choice)).is_present():
|
||||
return False
|
||||
|
||||
@@ -122,7 +122,7 @@ class ProgressPage(CoursePage):
|
||||
# CSS indices are 1-indexed, so add one to the list index
|
||||
return chapter_titles.index(title.lower()) + 1
|
||||
except ValueError:
|
||||
self.warning("Could not find chapter '{0}'".format(title))
|
||||
self.warning(u"Could not find chapter '{0}'".format(title))
|
||||
return None
|
||||
|
||||
def _section_index(self, chapter_index, title):
|
||||
@@ -134,7 +134,7 @@ class ProgressPage(CoursePage):
|
||||
# This is a hideous CSS selector that means:
|
||||
# Get the links containing the section titles in `chapter_index`.
|
||||
# The link text is the section title.
|
||||
section_css = '.chapters>section:nth-of-type({0}) .sections div .hd a'.format(chapter_index)
|
||||
section_css = u'.chapters>section:nth-of-type({0}) .sections div .hd a'.format(chapter_index)
|
||||
section_titles = self.q(css=section_css).map(lambda el: el.text.lower().strip()).results
|
||||
|
||||
# The section titles also contain "n of m possible points" on the second line
|
||||
@@ -148,7 +148,7 @@ class ProgressPage(CoursePage):
|
||||
# CSS indices are 1-indexed, so add one to the list index
|
||||
return section_titles.index(title.lower()) + 1
|
||||
except ValueError:
|
||||
self.warning("Could not find section '{0}'".format(title))
|
||||
self.warning(u"Could not find section '{0}'".format(title))
|
||||
return None
|
||||
|
||||
def _aggregate_section_score(self, chapter_index, section_index):
|
||||
@@ -156,7 +156,7 @@ class ProgressPage(CoursePage):
|
||||
Return a tuple of the form `(points, max_points)` representing
|
||||
the aggregate score for the specified chapter and section.
|
||||
"""
|
||||
score_css = ".chapters>section:nth-of-type({0}) .sections>div:nth-of-type({1}) .hd>span".format(
|
||||
score_css = u".chapters>section:nth-of-type({0}) .sections>div:nth-of-type({1}) .hd>span".format(
|
||||
chapter_index, section_index
|
||||
|
||||
)
|
||||
@@ -183,7 +183,7 @@ class ProgressPage(CoursePage):
|
||||
# This is CSS selector means:
|
||||
# Get the scores for the chapter at `chapter_index` and the section at `section_index`
|
||||
# Example text of the retrieved elements: "0/1"
|
||||
score_css = ".chapters>section:nth-of-type({0}) .sections>div:nth-of-type({1}) .scores>dd".format(
|
||||
score_css = u".chapters>section:nth-of-type({0}) .sections>div:nth-of-type({1}) .scores>dd".format(
|
||||
chapter_index, section_index
|
||||
)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class TabNavPage(PageObject):
|
||||
"""
|
||||
|
||||
if tab_name not in ['Course', 'Home', 'Discussion', 'Wiki', 'Progress']:
|
||||
self.warning("'{0}' is not a valid tab name".format(tab_name))
|
||||
self.warning(u"'{0}' is not a valid tab name".format(tab_name))
|
||||
|
||||
# The only identifier for individual tabs is the link href
|
||||
# so we find the tab with `tab_name` in its text.
|
||||
@@ -38,7 +38,7 @@ class TabNavPage(PageObject):
|
||||
if tab_css is not None:
|
||||
self.q(css=tab_css).first.click()
|
||||
else:
|
||||
self.warning("No tabs found for '{0}'".format(tab_name))
|
||||
self.warning(u"No tabs found for '{0}'".format(tab_name))
|
||||
|
||||
self.wait_for_page()
|
||||
self._is_on_tab_promise(tab_name).fulfill()
|
||||
@@ -74,9 +74,9 @@ class TabNavPage(PageObject):
|
||||
return None
|
||||
else:
|
||||
if self.is_using_boostrap_style_tabs():
|
||||
return 'ul.navbar-nav li:nth-of-type({0}) a'.format(tab_index + 1)
|
||||
return u'ul.navbar-nav li:nth-of-type({0}) a'.format(tab_index + 1)
|
||||
else:
|
||||
return 'ol.course-tabs li:nth-of-type({0}) a'.format(tab_index + 1)
|
||||
return u'ol.course-tabs li:nth-of-type({0}) a'.format(tab_index + 1)
|
||||
|
||||
@property
|
||||
def tab_names(self):
|
||||
@@ -122,7 +122,7 @@ class TabNavPage(PageObject):
|
||||
# Use the private version of _is_on_tab to skip the page check
|
||||
return EmptyPromise(
|
||||
lambda: self._is_on_tab(tab_name),
|
||||
"{0} is the current tab".format(tab_name)
|
||||
u"{0} is the current tab".format(tab_name)
|
||||
)
|
||||
|
||||
def has_new_post_button_visible_on_tab(self):
|
||||
|
||||
@@ -23,7 +23,7 @@ class TeamCardsMixin(object):
|
||||
|
||||
def _bounded_selector(self, css):
|
||||
"""Bind the CSS to a particular tabpanel (e.g. My Teams or Browse)."""
|
||||
return '{tabpanel_id} {css}'.format(tabpanel_id=getattr(self, 'tabpanel_id', ''), css=css)
|
||||
return u'{tabpanel_id} {css}'.format(tabpanel_id=getattr(self, 'tabpanel_id', ''), css=css)
|
||||
|
||||
def view_first_team(self):
|
||||
"""Click the 'view' button of the first team card on the page."""
|
||||
@@ -189,14 +189,14 @@ class BrowseTopicsPage(CoursePage, PaginatedUIMixin):
|
||||
Show the teams list for `topic_name`.
|
||||
"""
|
||||
self.q(css=TEAMS_LINK_CSS).filter(
|
||||
text='View Teams in the {topic_name} Topic'.format(topic_name=topic_name)
|
||||
text=u'View Teams in the {topic_name} Topic'.format(topic_name=topic_name)
|
||||
)[0].click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def sort_topics_by(self, sort_order):
|
||||
"""Sort the list of topics by the given `sort_order`."""
|
||||
self.q(
|
||||
css='#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
|
||||
css=u'#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
|
||||
).click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
@@ -273,7 +273,7 @@ class BaseTeamsPage(CoursePage, PaginatedUIMixin, TeamCardsMixin, BreadcrumbsMix
|
||||
def sort_teams_by(self, sort_order):
|
||||
"""Sort the list of teams by the given `sort_order`."""
|
||||
self.q(
|
||||
css='#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
|
||||
css=u'#paging-header-select option[value={sort_order}]'.format(sort_order=sort_order)
|
||||
).click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
@@ -564,7 +564,7 @@ class TeamPage(CoursePage, PaginatedUIMixin, BreadcrumbsMixin):
|
||||
|
||||
def format_capacity_text(self, num_members, max_size):
|
||||
""" Helper method to format the expected team capacity text. """
|
||||
return '{num_members} / {max_size} {members_text}'.format(
|
||||
return u'{num_members} / {max_size} {members_text}'.format(
|
||||
num_members=num_members,
|
||||
max_size=max_size,
|
||||
members_text='Member' if num_members == max_size else 'Members'
|
||||
|
||||
@@ -34,7 +34,7 @@ CSS_CLASS_NAMES = {
|
||||
'captions_rendered': '.video.is-captions-rendered',
|
||||
'captions': '.subtitles',
|
||||
'captions_text': '.subtitles li span',
|
||||
'captions_text_getter': '.subtitles li span[role="link"][data-index="{}"]',
|
||||
'captions_text_getter': u'.subtitles li span[role="link"][data-index="{}"]',
|
||||
'closed_captions': '.closed-captions',
|
||||
'error_message': '.video .video-player .video-error',
|
||||
'video_container': '.video',
|
||||
@@ -121,7 +121,7 @@ class VideoPage(PageObject):
|
||||
video_player_buttons.append('play')
|
||||
|
||||
for button in video_player_buttons:
|
||||
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button))
|
||||
self.wait_for_element_visibility(VIDEO_BUTTONS[button], u'{} button is visible'.format(button))
|
||||
|
||||
def _is_finished_loading():
|
||||
"""
|
||||
@@ -148,7 +148,7 @@ class VideoPage(PageObject):
|
||||
|
||||
video_player_buttons = ['do_not_show_again', 'skip_bumper', 'volume']
|
||||
for button in video_player_buttons:
|
||||
self.wait_for_element_visibility(VIDEO_BUTTONS[button], '{} button is visible'.format(button))
|
||||
self.wait_for_element_visibility(VIDEO_BUTTONS[button], u'{} button is visible'.format(button))
|
||||
|
||||
@property
|
||||
def is_poster_shown(self):
|
||||
@@ -179,7 +179,7 @@ class VideoPage(PageObject):
|
||||
if video_display_name:
|
||||
video_display_names = self.q(css=CSS_CLASS_NAMES['video_display_name']).text
|
||||
if video_display_name not in video_display_names:
|
||||
raise ValueError("Incorrect Video Display Name: '{0}'".format(video_display_name))
|
||||
raise ValueError(u"Incorrect Video Display Name: '{0}'".format(video_display_name))
|
||||
return '.vert.vert-{}'.format(video_display_names.index(video_display_name))
|
||||
else:
|
||||
return '.vert.vert-0'
|
||||
@@ -197,7 +197,7 @@ class VideoPage(PageObject):
|
||||
|
||||
"""
|
||||
if vertical:
|
||||
return '{vertical} {video_element}'.format(
|
||||
return u'{vertical} {video_element}'.format(
|
||||
vertical=self.get_video_vertical_selector(self.current_video_display_name),
|
||||
video_element=class_name)
|
||||
else:
|
||||
@@ -245,7 +245,7 @@ class VideoPage(PageObject):
|
||||
is_present = href_src.startswith('blob:') or href_src.startswith('mediasource:')
|
||||
return is_present, is_present
|
||||
|
||||
return Promise(_is_element_present, 'Video Rendering Failed in {0} mode.'.format(mode)).fulfill()
|
||||
return Promise(_is_element_present, u'Video Rendering Failed in {0} mode.'.format(mode)).fulfill()
|
||||
|
||||
@property
|
||||
def video_download_url(self):
|
||||
@@ -387,7 +387,7 @@ class VideoPage(PageObject):
|
||||
|
||||
# Verify that captions state is toggled/changed
|
||||
EmptyPromise(lambda: self.is_captions_visible() == captions_new_state,
|
||||
"Transcripts are {state}".format(state=state)).fulfill()
|
||||
u"Transcripts are {state}".format(state=state)).fulfill()
|
||||
|
||||
@wait_for_js
|
||||
def _closed_captions_visibility(self, closed_captions_new_state):
|
||||
@@ -404,7 +404,7 @@ class VideoPage(PageObject):
|
||||
|
||||
# Make sure that the captions are visible
|
||||
EmptyPromise(lambda: self.is_closed_captions_visible() == closed_captions_new_state,
|
||||
"Closed captions are {state}".format(state=state)).fulfill()
|
||||
u"Closed captions are {state}".format(state=state)).fulfill()
|
||||
|
||||
@property
|
||||
def captions_text(self):
|
||||
@@ -485,7 +485,7 @@ class VideoPage(PageObject):
|
||||
hover = ActionChains(self.browser).move_to_element(element_to_hover_over)
|
||||
hover.perform()
|
||||
|
||||
speed_selector = self.get_element_selector('li[data-speed="{speed}"] .control'.format(speed=speed))
|
||||
speed_selector = self.get_element_selector(u'li[data-speed="{speed}"] .control'.format(speed=speed))
|
||||
self.q(css=speed_selector).first.click()
|
||||
# Click triggers an ajax event
|
||||
self.wait_for_ajax()
|
||||
@@ -677,7 +677,7 @@ class VideoPage(PageObject):
|
||||
element_to_hover_over = self.q(css=cc_button_selector).results[0]
|
||||
ActionChains(self.browser).move_to_element(element_to_hover_over).perform()
|
||||
|
||||
language_selector = VIDEO_MENUS["language"] + ' li[data-lang-code="{code}"]'.format(code=code)
|
||||
language_selector = VIDEO_MENUS["language"] + u' li[data-lang-code="{code}"]'.format(code=code)
|
||||
language_selector = self.get_element_selector(language_selector)
|
||||
self.wait_for_element_visibility(language_selector, 'language menu is visible')
|
||||
hover_target = self.q(css=language_selector).results[0]
|
||||
@@ -786,7 +786,7 @@ class VideoPage(PageObject):
|
||||
|
||||
# For troubleshooting purposes show what the current state is.
|
||||
# The debug statements will only be displayed in the event of a failure.
|
||||
logging.debug("Current state of '{}' element is '{}'".format(state_selector, current_state))
|
||||
logging.debug(u"Current state of '{}' element is '{}'".format(state_selector, current_state))
|
||||
|
||||
# See the JS video player's onStateChange function
|
||||
if 'is-playing' in current_state:
|
||||
@@ -824,7 +824,7 @@ class VideoPage(PageObject):
|
||||
"""
|
||||
self._wait_for(
|
||||
lambda: self.state == state,
|
||||
'State is {state}'.format(state=state)
|
||||
u'State is {state}'.format(state=state)
|
||||
)
|
||||
|
||||
def seek(self, seek_value):
|
||||
@@ -837,7 +837,7 @@ class VideoPage(PageObject):
|
||||
"""
|
||||
seek_time = _parse_time_str(seek_value)
|
||||
seek_selector = self.get_element_selector(' .video')
|
||||
js_code = "$('{seek_selector}').data('video-player-state').videoPlayer.onSlideSeek({{time: {seek_time}}})".format(
|
||||
js_code = u"$('{seek_selector}').data('video-player-state').videoPlayer.onSlideSeek({{time: {seek_time}}})".format(
|
||||
seek_selector=seek_selector, seek_time=seek_time)
|
||||
self.browser.execute_script(js_code)
|
||||
|
||||
@@ -887,7 +887,7 @@ class VideoPage(PageObject):
|
||||
"""
|
||||
self._wait_for(
|
||||
lambda: self.position == position,
|
||||
'Position is {position}'.format(position=position)
|
||||
u'Position is {position}'.format(position=position)
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -219,7 +219,7 @@ class AssetIndexPageStudioFrontend(CoursePage):
|
||||
"""Delete the asset with the specified name."""
|
||||
names = self.asset_files_names
|
||||
if name not in names:
|
||||
raise LookupError('Asset with filename {} not found.'.format(name))
|
||||
raise LookupError(u'Asset with filename {} not found.'.format(name))
|
||||
delete_buttons = self.asset_delete_buttons
|
||||
assets = dict(zip(names, delete_buttons))
|
||||
# Now click the link in that row
|
||||
|
||||
@@ -27,7 +27,7 @@ class ContainerPage(PageObject, HelpMixin):
|
||||
@property
|
||||
def url(self):
|
||||
"""URL to the container page for an xblock."""
|
||||
return "{}/container/{}".format(BASE_URL, self.locator)
|
||||
return u"{}/container/{}".format(BASE_URL, self.locator)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -39,7 +39,7 @@ class ContainerPage(PageObject, HelpMixin):
|
||||
|
||||
def is_browser_on_page(self):
|
||||
def _xblock_count(class_name, request_token):
|
||||
return len(self.q(css='{body_selector} .xblock.{class_name}[data-request-token="{request_token}"]'.format(
|
||||
return len(self.q(css=u'{body_selector} .xblock.{class_name}[data-request-token="{request_token}"]'.format(
|
||||
body_selector=XBlockWrapper.BODY_SELECTOR, class_name=class_name, request_token=request_token
|
||||
)).results)
|
||||
|
||||
@@ -50,7 +50,7 @@ class ContainerPage(PageObject, HelpMixin):
|
||||
if len(data_request_elements) > 0:
|
||||
request_token = data_request_elements.first.attrs('data-request-token')[0]
|
||||
# Then find the number of Studio xblock wrappers on the page with that request token.
|
||||
num_wrappers = len(self.q(css='{} [data-request-token="{}"]'.format(XBlockWrapper.BODY_SELECTOR, request_token)).results)
|
||||
num_wrappers = len(self.q(css=u'{} [data-request-token="{}"]'.format(XBlockWrapper.BODY_SELECTOR, request_token)).results)
|
||||
# Wait until all components have been loaded and marked as either initialized or failed.
|
||||
# See:
|
||||
# - common/static/js/xblock/core.js which adds the class "xblock-initialized"
|
||||
@@ -323,7 +323,7 @@ class ContainerPage(PageObject, HelpMixin):
|
||||
text = self.q(css='#page-alert .alert.confirmation #alert-confirmation-title').text
|
||||
return text and message not in text[0] if verify_hidden else text and message in text[0]
|
||||
|
||||
self.wait_for(_verify_message, description='confirmation message {status}'.format(
|
||||
self.wait_for(_verify_message, description=u'confirmation message {status}'.format(
|
||||
status='hidden' if verify_hidden else 'present'
|
||||
))
|
||||
|
||||
@@ -402,7 +402,7 @@ class ContainerPage(PageObject, HelpMixin):
|
||||
Returns:
|
||||
list
|
||||
"""
|
||||
css = '#tab{tab_index} button[data-category={category_type}] span'.format(
|
||||
css = u'#tab{tab_index} button[data-category={category_type}] span'.format(
|
||||
tab_index=tab_index,
|
||||
category_type=category_type
|
||||
)
|
||||
@@ -435,7 +435,7 @@ class XBlockWrapper(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to this particular `CourseOutlineChild` context
|
||||
"""
|
||||
return '{}[data-locator="{}"] {}'.format(
|
||||
return u'{}[data-locator="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
@@ -487,7 +487,7 @@ class XBlockWrapper(PageObject):
|
||||
|
||||
def _validation_paragraph(self, css_class):
|
||||
""" Helper method to return the <p> element of a validation warning """
|
||||
return self.q(css=self._bounded_selector('{} p.{}'.format(self.VALIDATION_SELECTOR, css_class)))
|
||||
return self.q(css=self._bounded_selector(u'{} p.{}'.format(self.VALIDATION_SELECTOR, css_class)))
|
||||
|
||||
@property
|
||||
def has_validation_warning(self):
|
||||
@@ -619,7 +619,7 @@ class XBlockWrapper(PageObject):
|
||||
"""
|
||||
If editing, set the value of a field.
|
||||
"""
|
||||
selector = '{} li.field label:contains("{}") + input'.format(self.editor_selector, field_display_name)
|
||||
selector = u'{} li.field label:contains("{}") + input'.format(self.editor_selector, field_display_name)
|
||||
script = "$(arguments[0]).val(arguments[1]).change();"
|
||||
self.browser.execute_script(script, selector, field_value)
|
||||
|
||||
@@ -627,7 +627,7 @@ class XBlockWrapper(PageObject):
|
||||
"""
|
||||
If editing, reset the value of a field to its default.
|
||||
"""
|
||||
scope = '{} li.field label:contains("{}")'.format(self.editor_selector, field_display_name)
|
||||
scope = u'{} li.field label:contains("{}")'.format(self.editor_selector, field_display_name)
|
||||
script = "$(arguments[0]).siblings('.setting-clear').click();"
|
||||
self.browser.execute_script(script, scope)
|
||||
|
||||
@@ -635,18 +635,18 @@ class XBlockWrapper(PageObject):
|
||||
"""
|
||||
Set the text of a CodeMirror editor that is part of this xblock's settings.
|
||||
"""
|
||||
type_in_codemirror(self, index, text, find_prefix='$("{}").find'.format(self.editor_selector))
|
||||
type_in_codemirror(self, index, text, find_prefix=u'$("{}").find'.format(self.editor_selector))
|
||||
|
||||
def set_license(self, license_type):
|
||||
"""
|
||||
Uses the UI to set the course's license to the given license_type (str)
|
||||
"""
|
||||
css_selector = (
|
||||
"ul.license-types li[data-license={license_type}] button"
|
||||
u"ul.license-types li[data-license={license_type}] button"
|
||||
).format(license_type=license_type)
|
||||
self.wait_for_element_presence(
|
||||
css_selector,
|
||||
"{license_type} button is present".format(license_type=license_type)
|
||||
u"{license_type} button is present".format(license_type=license_type)
|
||||
)
|
||||
self.q(css=css_selector).click()
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class DiscussionComponentEditor(XBlockEditorView):
|
||||
"""
|
||||
If editing, set the value of a field.
|
||||
"""
|
||||
selector = '.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
selector = u'.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
script = "$(arguments[0]).val(arguments[1]).change();"
|
||||
self.browser.execute_script(script, selector, field_value)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class PagesPage(CoursePage):
|
||||
description="Static tab is added"
|
||||
)
|
||||
self.wait_for_element_visibility(
|
||||
'.tab-list :nth-child({}) .xblock-student_view'.format(total_tabs),
|
||||
u'.tab-list :nth-child({}) .xblock-student_view'.format(total_tabs),
|
||||
'Static tab is visible'
|
||||
)
|
||||
# self.wait_for_ajax()
|
||||
@@ -121,7 +121,7 @@ class PagesPage(CoursePage):
|
||||
true(bool): if tab is visible
|
||||
false(bool): if tab is not visible
|
||||
"""
|
||||
css_selector = '[data-tab-id="{}"] .toggle-checkbox'.format(tab_name)
|
||||
css_selector = u'[data-tab-id="{}"] .toggle-checkbox'.format(tab_name)
|
||||
return True if not self.q(css=css_selector).selected else False
|
||||
|
||||
def toggle_tab(self, tab_name):
|
||||
@@ -130,7 +130,7 @@ class PagesPage(CoursePage):
|
||||
Args:
|
||||
tab_name(string): Name of the tab to be toggled
|
||||
"""
|
||||
css_selector = '[data-tab-id="{}"] .action-visible'.format(tab_name)
|
||||
css_selector = u'[data-tab-id="{}"] .action-visible'.format(tab_name)
|
||||
return self.q(css=css_selector).first.click()
|
||||
|
||||
def set_field_val(self, field_display_name, field_value):
|
||||
@@ -141,7 +141,7 @@ class PagesPage(CoursePage):
|
||||
field_display_name(str): Display name of the field for which the value is to be changed
|
||||
field_value(str): New value for the field
|
||||
"""
|
||||
selector = '.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
selector = u'.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
script = '$(arguments[0]).val(arguments[1]).change();'
|
||||
self.browser.execute_script(script, selector, field_value)
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ class HtmlXBlockEditorView(XBlockEditorView):
|
||||
"""
|
||||
If editing, set the value of a field.
|
||||
"""
|
||||
selector = '.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
selector = u'.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
script = "$(arguments[0]).val(arguments[1]).change();"
|
||||
self.browser.execute_script(script, selector, field_value)
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ class ImportExportMixin(object):
|
||||
"""
|
||||
Return python datetime object from the parsed timestamp tuple (date, time)
|
||||
"""
|
||||
timestamp = "{0} {1}".format(*self.timestamp)
|
||||
formatted_timestamp = time.strptime(timestamp, "%m/%d/%Y %H:%M")
|
||||
timestamp = u"{0} {1}".format(*self.timestamp)
|
||||
formatted_timestamp = time.strptime(timestamp, u"%m/%d/%Y %H:%M")
|
||||
return datetime.fromtimestamp(time.mktime(formatted_timestamp))
|
||||
|
||||
@property
|
||||
@@ -65,7 +65,7 @@ class ImportExportMixin(object):
|
||||
"""
|
||||
string = self.q(css='.item-progresspoint-success-date').text[0]
|
||||
|
||||
return re.match(r'\(([^ ]+).+?(\d{2}:\d{2})', string).groups()
|
||||
return re.match(ur'\(([^ ]+).+?(\d{2}:\d{2})', string).groups()
|
||||
|
||||
def wait_for_tasks(self, completed=False, fail_on=None):
|
||||
"""
|
||||
@@ -80,11 +80,11 @@ class ImportExportMixin(object):
|
||||
for desc, css_class in self.task_classes.items():
|
||||
desc_text = desc_template.format(desc)
|
||||
# pylint: disable=cell-var-from-loop
|
||||
EmptyPromise(lambda: self.q(css='.{}.{}'.format(css_class, state)).present, desc_text, timeout=30)
|
||||
EmptyPromise(lambda: self.q(css=u'.{}.{}'.format(css_class, state)).present, desc_text, timeout=30)
|
||||
if fail_on == desc:
|
||||
EmptyPromise(
|
||||
lambda: self.q(css='.{}.is-complete.has-error'.format(css_class)).present,
|
||||
"{} checkpoint marked as failed".format(desc),
|
||||
lambda: self.q(css=u'.{}.is-complete.has-error'.format(css_class)).present,
|
||||
u"{} checkpoint marked as failed".format(desc),
|
||||
timeout=30
|
||||
)
|
||||
# The rest should never run.
|
||||
@@ -102,9 +102,9 @@ class ImportExportMixin(object):
|
||||
Outputs the CSS class and promise description for task states based on completion.
|
||||
"""
|
||||
if completed:
|
||||
return 'is-complete', "'{}' is marked complete"
|
||||
return 'is-complete', u"'{}' is marked complete"
|
||||
else:
|
||||
return 'is-not-started', "'{}' is in not-yet-started status"
|
||||
return 'is-not-started', u"'{}' is in not-yet-started status"
|
||||
|
||||
|
||||
class ExportMixin(ImportExportMixin):
|
||||
|
||||
@@ -114,7 +114,7 @@ class DashboardPage(PageObject, HelpMixin):
|
||||
Fill out the form to create a new library.
|
||||
Must have called click_new_library() first.
|
||||
"""
|
||||
field = lambda fn: self.q(css='.wrapper-create-library #new-library-{}'.format(fn))
|
||||
field = lambda fn: self.q(css=u'.wrapper-create-library #new-library-{}'.format(fn))
|
||||
field('name').fill(display_name)
|
||||
field('org').fill(org)
|
||||
field('number').fill(number)
|
||||
@@ -158,7 +158,7 @@ class DashboardPage(PageObject, HelpMixin):
|
||||
"""
|
||||
Fill out the form to create a new course.
|
||||
"""
|
||||
field = lambda fn: self.q(css='.wrapper-create-course #new-course-{}'.format(fn))
|
||||
field = lambda fn: self.q(css=u'.wrapper-create-course #new-course-{}'.format(fn))
|
||||
field('name').fill(display_name)
|
||||
field('org').fill(org)
|
||||
field('number').fill(number)
|
||||
@@ -218,7 +218,7 @@ class DashboardPage(PageObject, HelpMixin):
|
||||
List all the courses found on the page's list of courses.
|
||||
"""
|
||||
# Workaround Selenium/Firefox bug: `.text` property is broken on invisible elements
|
||||
tab_selector = '#course-index-tabs .{} a'.format('archived-courses-tab' if archived else 'courses-tab')
|
||||
tab_selector = u'#course-index-tabs .{} a'.format('archived-courses-tab' if archived else 'courses-tab')
|
||||
self.wait_for_element_presence(tab_selector, "Courses Tab")
|
||||
self.q(css=tab_selector).click()
|
||||
div2info = lambda element: {
|
||||
@@ -228,7 +228,7 @@ class DashboardPage(PageObject, HelpMixin):
|
||||
'run': element.find_element_by_css_selector('.course-run .value').text,
|
||||
'url': element.find_element_by_css_selector('a.course-link').get_attribute('href'),
|
||||
}
|
||||
course_list_selector = '.{} li.course-item'.format('archived-courses' if archived else 'courses')
|
||||
course_list_selector = u'.{} li.course-item'.format('archived-courses' if archived else 'courses')
|
||||
return self.q(css=course_list_selector).map(div2info).results
|
||||
|
||||
def has_course(self, org, number, run, archived=False):
|
||||
@@ -364,7 +364,7 @@ class AccessibilityPage(IndexPage):
|
||||
"""
|
||||
To simulate leaving a field blank, click on the field, then press TAB to move off focus off the field.
|
||||
"""
|
||||
field = self.q(css='#root {}#{}'.format(field_type, field_id))[0]
|
||||
field = self.q(css=u'#root {}#{}'.format(field_type, field_id))[0]
|
||||
field.click()
|
||||
field.send_keys(Keys.TAB)
|
||||
|
||||
@@ -378,7 +378,7 @@ class AccessibilityPage(IndexPage):
|
||||
"""
|
||||
Check that at least one error message is shown and at least one contains the specified text.
|
||||
"""
|
||||
selector = '#root div#error-{}'.format(field_id)
|
||||
selector = u'#root div#error-{}'.format(field_id)
|
||||
self.wait_for_element_visibility(selector, 'An error message is visible')
|
||||
error_messages = self.q(css=selector)
|
||||
for message in error_messages:
|
||||
|
||||
@@ -82,7 +82,7 @@ class LibraryEditPage(LibraryPage, PaginatedMixin, UsersPageMixin):
|
||||
self.q(css='.toggle-preview-button').click()
|
||||
EmptyPromise(
|
||||
lambda: self.are_previews_showing() == toggle,
|
||||
'Preview is visible: %s' % toggle,
|
||||
u'Preview is visible: %s' % toggle,
|
||||
timeout=30
|
||||
).fulfill()
|
||||
self.wait_until_ready()
|
||||
@@ -129,7 +129,7 @@ class LibraryEditPage(LibraryPage, PaginatedMixin, UsersPageMixin):
|
||||
action is 'edit', 'duplicate', or 'delete'
|
||||
"""
|
||||
return self._div_for_xblock_id(xblock_id)[0].find_element_by_css_selector(
|
||||
'.header-actions .{action}-button.action-button'.format(action=action)
|
||||
u'.header-actions .{action}-button.action-button'.format(action=action)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -38,9 +38,9 @@ class CourseOutlineItem(object):
|
||||
# Check for the existence of a locator so that errors when navigating to the course outline page don't show up
|
||||
# as errors in the repr method instead.
|
||||
try:
|
||||
return "{}(<browser>, {!r})".format(self.__class__.__name__, self.locator)
|
||||
return u"{}(<browser>, {!r})".format(self.__class__.__name__, self.locator)
|
||||
except AttributeError:
|
||||
return "{}(<browser>)".format(self.__class__.__name__)
|
||||
return u"{}(<browser>)".format(self.__class__.__name__)
|
||||
|
||||
def _bounded_selector(self, selector):
|
||||
"""
|
||||
@@ -50,7 +50,7 @@ class CourseOutlineItem(object):
|
||||
# This happens in the context of the CourseOutlinePage
|
||||
# pylint: disable=no-member
|
||||
if self.BODY_SELECTOR and hasattr(self, 'locator'):
|
||||
return '{}[data-locator="{}"] {}'.format(
|
||||
return u'{}[data-locator="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
@@ -153,7 +153,7 @@ class CourseOutlineItem(object):
|
||||
select_option_by_text(groups_select, partition_name)
|
||||
|
||||
for group_id in group_ids:
|
||||
checkbox = self.q(css='#content-group-{group_id}'.format(group_id=group_id))
|
||||
checkbox = self.q(css=u'#content-group-{group_id}'.format(group_id=group_id))
|
||||
checkbox.click()
|
||||
modal.save()
|
||||
|
||||
@@ -302,11 +302,11 @@ class CourseOutlineContainer(CourseOutlineItem):
|
||||
self.scroll_to_element(css_element) # pylint: disable=no-member
|
||||
ele = self.browser.find_element_by_css_selector(css_element) # pylint: disable=no-member
|
||||
ActionChains(self.browser).move_to_element_with_offset(ele, 8, 8).click().perform() # pylint: disable=no-member
|
||||
self.wait_for_element_presence(self._bounded_selector(self.ADD_BUTTON_SELECTOR), 'Subsection is expanded')
|
||||
self.wait_for_element_presence(self._bounded_selector(self.ADD_BUTTON_SELECTOR), u'Subsection is expanded')
|
||||
|
||||
EmptyPromise(
|
||||
lambda: subsection_expanded() != currently_expanded,
|
||||
"Check that the container {} has been toggled".format(self.locator)
|
||||
u"Check that the container {} has been toggled".format(self.locator)
|
||||
).fulfill()
|
||||
|
||||
enable_animations(self)
|
||||
@@ -348,7 +348,7 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
|
||||
"""
|
||||
Return `selector`, but limited to this particular `CourseOutlineChild` context
|
||||
"""
|
||||
return '{}[data-locator="{}"] {}'.format(
|
||||
return u'{}[data-locator="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
@@ -528,7 +528,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
"""
|
||||
Find and click on first section name in course outline
|
||||
"""
|
||||
self.q(css='{} .section-name'.format(parent_css)).first.click()
|
||||
self.q(css=u'{} .section-name'.format(parent_css)).first.click()
|
||||
|
||||
def get_section_name(self, parent_css='', page_refresh=False):
|
||||
"""
|
||||
@@ -536,21 +536,21 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
|
||||
"""
|
||||
if page_refresh:
|
||||
self.browser.refresh()
|
||||
return self.q(css='{} .section-name'.format(parent_css)).text
|
||||
return self.q(css=u'{} .section-name'.format(parent_css)).text
|
||||
|
||||
def section_name_edit_form_present(self, parent_css=''):
|
||||
"""
|
||||
Check that section name edit form present
|
||||
"""
|
||||
return self.q(css='{} .section-name input'.format(parent_css)).present
|
||||
return self.q(css=u'{} .section-name input'.format(parent_css)).present
|
||||
|
||||
def change_section_name(self, new_name, parent_css=''):
|
||||
"""
|
||||
Change section name of first section present in course outline
|
||||
"""
|
||||
self.click_section_name(parent_css)
|
||||
self.q(css='{} .section-name input'.format(parent_css)).first.fill(new_name)
|
||||
self.q(css='{} .section-name .save-button'.format(parent_css)).first.click()
|
||||
self.q(css=u'{} .section-name input'.format(parent_css)).first.fill(new_name)
|
||||
self.q(css=u'{} .section-name .save-button'.format(parent_css)).first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def sections(self):
|
||||
@@ -1007,14 +1007,14 @@ class CourseOutlineModal(object):
|
||||
else: # Use default timepicker values, which are current month and year.
|
||||
current_month, current_year = datetime.datetime.today().month, datetime.datetime.today().year
|
||||
date_diff = 12 * (year - current_year) + month - current_month
|
||||
selector = "a.ui-datepicker-{}".format('next' if date_diff > 0 else 'prev')
|
||||
selector = u"a.ui-datepicker-{}".format('next' if date_diff > 0 else 'prev')
|
||||
for __ in xrange(abs(date_diff)):
|
||||
self.page.q(css=selector).click()
|
||||
self.page.q(css="a.ui-state-default").nth(day - 1).click() # set day
|
||||
self.page.wait_for_element_invisibility("#ui-datepicker-div", "datepicker should be closed")
|
||||
EmptyPromise(
|
||||
lambda: getattr(self, property_name) == u'{m}/{d}/{y}'.format(m=month, d=day, y=year),
|
||||
"{} is updated in modal.".format(property_name)
|
||||
u"{} is updated in modal.".format(property_name)
|
||||
).fulfill()
|
||||
|
||||
def set_time(self, input_selector, time):
|
||||
|
||||
@@ -17,7 +17,7 @@ class PaginatedMixin(object):
|
||||
To specify a specific arrow, pass an iterable with a single element, 'next' or 'previous'.
|
||||
"""
|
||||
return all([
|
||||
self.q(css='nav.%s * .%s-page-link.is-disabled' % (position, arrow))
|
||||
self.q(css=u'nav.%s * .%s-page-link.is-disabled' % (position, arrow))
|
||||
for arrow in arrows
|
||||
])
|
||||
|
||||
@@ -25,14 +25,14 @@ class PaginatedMixin(object):
|
||||
"""
|
||||
Clicks one of the forward nav buttons. Position can be 'top' or 'bottom'.
|
||||
"""
|
||||
self.q(css='nav.%s * .previous-page-link' % position)[0].click()
|
||||
self.q(css=u'nav.%s * .previous-page-link' % position)[0].click()
|
||||
self.wait_until_ready()
|
||||
|
||||
def move_forward(self, position):
|
||||
"""
|
||||
Clicks one of the forward nav buttons. Position can be 'top' or 'bottom'.
|
||||
"""
|
||||
self.q(css='nav.%s * .next-page-link' % position)[0].click()
|
||||
self.q(css=u'nav.%s * .next-page-link' % position)[0].click()
|
||||
self.wait_until_ready()
|
||||
|
||||
def go_to_page(self, number):
|
||||
|
||||
@@ -24,7 +24,7 @@ class ProblemXBlockEditorView(XBlockEditorView):
|
||||
"""
|
||||
If editing, set the value of a field.
|
||||
"""
|
||||
selector = '.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
selector = u'.xblock-studio_view li.field label:contains("{}") + input'.format(field_display_name)
|
||||
script = "$(arguments[0]).val(arguments[1]).change();"
|
||||
self.browser.execute_script(script, selector, field_value)
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProblemXBlockEditorView(XBlockEditorView):
|
||||
Returns:
|
||||
(string): Value of the field
|
||||
"""
|
||||
script = "return $('.wrapper-comp-setting label:contains({}) + input').val();".format(field_display_name)
|
||||
script = u"return $('.wrapper-comp-setting label:contains({}) + input').val();".format(field_display_name)
|
||||
return self.browser.execute_script(script)
|
||||
|
||||
def get_default_dropdown_value(self, css):
|
||||
@@ -60,9 +60,9 @@ class ProblemXBlockEditorView(XBlockEditorView):
|
||||
dropdown_name(string): Name of the dropdown to be opened
|
||||
value(string): Value to be selected
|
||||
"""
|
||||
self.q(css='select[class="input setting-input"][name="{}"]'.format(dropdown_name)).first.click()
|
||||
self.wait_for_element_visibility('option[value="{}"]'.format(value), 'Dropdown is visible')
|
||||
self.q(css='option[value="{}"]'.format(value)).click()
|
||||
self.q(css=u'select[class="input setting-input"][name="{}"]'.format(dropdown_name)).first.click()
|
||||
self.wait_for_element_visibility(u'option[value="{}"]'.format(value), 'Dropdown is visible')
|
||||
self.q(css=u'option[value="{}"]'.format(value)).click()
|
||||
|
||||
def get_value_from_the_dropdown(self, dropdown_name):
|
||||
"""
|
||||
@@ -74,7 +74,7 @@ class ProblemXBlockEditorView(XBlockEditorView):
|
||||
|
||||
"""
|
||||
dropdown = self.browser.find_element_by_css_selector(
|
||||
'select[class="input setting-input"][name="{}"]'.format(dropdown_name)
|
||||
u'select[class="input setting-input"][name="{}"]'.format(dropdown_name)
|
||||
)
|
||||
return Select(dropdown).first_selected_option.text
|
||||
|
||||
@@ -86,7 +86,7 @@ class ProblemXBlockEditorView(XBlockEditorView):
|
||||
"""
|
||||
settings_dict = {}
|
||||
number_of_settings = len(self.q(css='.wrapper-comp-setting'))
|
||||
css = '.list-input.settings-list .field.comp-setting-entry:nth-of-type({}) {}'
|
||||
css = u'.list-input.settings-list .field.comp-setting-entry:nth-of-type({}) {}'
|
||||
|
||||
for index in range(1, number_of_settings + 1):
|
||||
key = self.q(css=css.format(index, "label")).text[0]
|
||||
|
||||
@@ -196,7 +196,7 @@ class CertificateSectionPage(CertificatesPage):
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.selector = prefix + ' .certificates-list-item-{}'.format(index)
|
||||
self.selector = prefix + u' .certificates-list-item-{}'.format(index)
|
||||
self.index = index
|
||||
|
||||
super(CertificateSectionPage, self).__init__(container.browser, **container.course_info)
|
||||
@@ -446,7 +446,7 @@ class SignatorySectionPage(CertificatesPage):
|
||||
"""
|
||||
Return selector fo signatory container
|
||||
"""
|
||||
selector = self.prefix + ' .signatory-{}-view-{}'.format(self.mode, self.index)
|
||||
selector = self.prefix + u' .signatory-{}-view-{}'.format(self.mode, self.index)
|
||||
return ' '.join([selector, css])
|
||||
|
||||
def find_css(self, css_selector):
|
||||
|
||||
@@ -110,7 +110,7 @@ class GroupConfiguration(object):
|
||||
|
||||
def __init__(self, page, prefix, index):
|
||||
self.page = page
|
||||
self.SELECTOR = prefix + ' .wrapper-collection-{}'.format(index)
|
||||
self.SELECTOR = prefix + u' .wrapper-collection-{}'.format(index)
|
||||
self.index = index
|
||||
|
||||
def get_selector(self, css=''):
|
||||
|
||||
@@ -94,14 +94,14 @@ class TextbookUploadPage(CoursePage):
|
||||
Adds chapter name by taking the ordinal of the chapter.
|
||||
"""
|
||||
index = ["first", "second", "third"].index(ordinal)
|
||||
self.set_input_field_value('.textbook .chapter{i} input.chapter-name'.format(i=index + 1), chapter_name)
|
||||
self.set_input_field_value(u'.textbook .chapter{i} input.chapter-name'.format(i=index + 1), chapter_name)
|
||||
|
||||
def fill_chapter_asset(self, ordinal, chapter_asset):
|
||||
"""
|
||||
Adds chapter asset by taking the ordinal of the chapter.
|
||||
"""
|
||||
index = ["first", "second", "third"].index(ordinal)
|
||||
self.set_input_field_value('.textbook .chapter{i} input.chapter-asset-path'.format(i=index + 1), chapter_asset)
|
||||
self.set_input_field_value(u'.textbook .chapter{i} input.chapter-asset-path'.format(i=index + 1), chapter_asset)
|
||||
|
||||
def submit_chapter(self):
|
||||
"""
|
||||
|
||||
@@ -113,7 +113,7 @@ class UsersPageMixin(PageObject):
|
||||
|
||||
def modal_dialog_text(self, dialog_type):
|
||||
""" Gets modal dialog text """
|
||||
return self.q(css='.prompt.{dialog_type} .message'.format(dialog_type=dialog_type)).text[0]
|
||||
return self.q(css=u'.prompt.{dialog_type} .message'.format(dialog_type=dialog_type)).text[0]
|
||||
|
||||
def wait_until_no_loading_indicator(self):
|
||||
"""
|
||||
@@ -204,7 +204,7 @@ class UserWrapper(PageObject):
|
||||
def __init__(self, browser, email):
|
||||
super(UserWrapper, self).__init__(browser)
|
||||
self.email = email
|
||||
self.selector = '.user-list .user-item[data-email="{}"]'.format(self.email)
|
||||
self.selector = u'.user-list .user-item[data-email="{}"]'.format(self.email)
|
||||
|
||||
def is_browser_on_page(self):
|
||||
"""
|
||||
@@ -216,7 +216,7 @@ class UserWrapper(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to this particular user entry's context
|
||||
"""
|
||||
return '{} {}'.format(self.selector, selector)
|
||||
return u'{} {}'.format(self.selector, selector)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -23,8 +23,8 @@ def press_the_notification_button(page, name):
|
||||
# the "Save" button at the UI level.
|
||||
# Instead, we use JavaScript to reliably click
|
||||
# the button.
|
||||
btn_css = 'div#page-notification button.action-%s' % name.lower()
|
||||
page.browser.execute_script("$('{}').focus().click()".format(btn_css))
|
||||
btn_css = u'div#page-notification button.action-%s' % name.lower()
|
||||
page.browser.execute_script(u"$('{}').focus().click()".format(btn_css))
|
||||
page.wait_for_ajax()
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ def add_advanced_component(page, menu_index, name):
|
||||
page.wait_for_element_visibility('.new-component-advanced', 'Advanced component menu is visible')
|
||||
|
||||
# Now click on the component to add it.
|
||||
component_css = 'button[data-category={}]'.format(name)
|
||||
page.wait_for_element_visibility(component_css, 'Advanced component {} is visible'.format(name))
|
||||
component_css = u'button[data-category={}]'.format(name)
|
||||
page.wait_for_element_visibility(component_css, u'Advanced component {} is visible'.format(name))
|
||||
|
||||
# Adding some components, e.g. the Discussion component, will make an ajax call
|
||||
# but we should be OK because the click_css method is written to handle that.
|
||||
@@ -76,11 +76,11 @@ def add_component(page, item_type, specific_type, is_advanced_problem=False):
|
||||
specific_type is required for some types and should be something like
|
||||
"Blank Common Problem".
|
||||
"""
|
||||
btn = page.q(css='.add-xblock-component .add-xblock-component-button[data-type={}]'.format(item_type))
|
||||
btn = page.q(css=u'.add-xblock-component .add-xblock-component-button[data-type={}]'.format(item_type))
|
||||
multiple_templates = btn.filter(lambda el: 'multiple-templates' in el.get_attribute('class')).present
|
||||
btn.click()
|
||||
if multiple_templates:
|
||||
sub_template_menu_div_selector = '.new-component-{}'.format(item_type)
|
||||
sub_template_menu_div_selector = u'.new-component-{}'.format(item_type)
|
||||
page.wait_for_element_visibility(sub_template_menu_div_selector, 'Wait for the templates sub-menu to appear')
|
||||
page.wait_for_element_invisibility(
|
||||
'.add-xblock-component .new-component',
|
||||
@@ -96,11 +96,11 @@ def add_component(page, item_type, specific_type, is_advanced_problem=False):
|
||||
# Wait for the advanced tab to be active
|
||||
css = '.problem-type-tabs li.ui-tabs-active a'
|
||||
page.wait_for(
|
||||
lambda: len(page.q(css=css).filter(text='Advanced').execute()) > 0,
|
||||
lambda: len(page.q(css=css).filter(text=u'Advanced').execute()) > 0,
|
||||
'Waiting for the Advanced problem tab to be active'
|
||||
)
|
||||
|
||||
all_options = page.q(css='.new-component-{} ul.new-component-template li button span'.format(item_type))
|
||||
all_options = page.q(css=u'.new-component-{} ul.new-component-template li button span'.format(item_type))
|
||||
chosen_option = all_options.filter(text=specific_type).first
|
||||
chosen_option.click()
|
||||
sync_on_notification(page)
|
||||
@@ -134,13 +134,13 @@ def add_html_component(page, menu_index, boilerplate=None):
|
||||
page.wait_for_element_visibility('.new-component-html', 'HTML component menu is visible')
|
||||
|
||||
# Now click on the component to add it.
|
||||
component_css = 'button[data-category=html]'
|
||||
component_css = u'button[data-category=html]'
|
||||
if boilerplate:
|
||||
component_css += '[data-boilerplate={}]'.format(boilerplate)
|
||||
component_css += u'[data-boilerplate={}]'.format(boilerplate)
|
||||
else:
|
||||
component_css += ':not([data-boilerplate])'
|
||||
component_css += u':not([data-boilerplate])'
|
||||
|
||||
page.wait_for_element_visibility(component_css, 'HTML component {} is visible'.format(boilerplate))
|
||||
page.wait_for_element_visibility(component_css, u'HTML component {} is visible'.format(boilerplate))
|
||||
|
||||
# Adding some components will make an ajax call but we should be OK because
|
||||
# the click_css method is written to handle that.
|
||||
@@ -149,7 +149,7 @@ def add_html_component(page, menu_index, boilerplate=None):
|
||||
|
||||
@js_defined('window.jQuery')
|
||||
def type_in_codemirror(page, index, text, find_prefix="$"):
|
||||
script = """
|
||||
script = u"""
|
||||
var cm = {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror;
|
||||
CodeMirror.signal(cm, "focus", cm);
|
||||
cm.setValue(arguments[0]);
|
||||
@@ -161,9 +161,9 @@ def type_in_codemirror(page, index, text, find_prefix="$"):
|
||||
@js_defined('window.jQuery')
|
||||
def get_codemirror_value(page, index=0, find_prefix="$"):
|
||||
return page.browser.execute_script(
|
||||
"""
|
||||
return {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror.getValue();
|
||||
""".format(index=index, find_prefix=find_prefix)
|
||||
u"return {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror.getValue();".format(
|
||||
index=index, find_prefix=find_prefix
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ def get_input_value(page, css_selector):
|
||||
"""
|
||||
page.wait_for_element_presence(
|
||||
css_selector,
|
||||
'Elements matching "{}" selector are present'.format(css_selector)
|
||||
u'Elements matching "{}" selector are present'.format(css_selector)
|
||||
)
|
||||
return page.q(css=css_selector).attrs('value')[0]
|
||||
|
||||
@@ -237,7 +237,7 @@ def verify_ordering(test_class, page, expected_orderings):
|
||||
expected_length = len(expected_ordering.get(parent))
|
||||
test_class.assertEqual(
|
||||
expected_length, len(children),
|
||||
"Number of children incorrect for group {0}. Expected {1} but got {2}.".format(parent, expected_length, len(children)))
|
||||
u"Number of children incorrect for group {0}. Expected {1} but got {2}.".format(parent, expected_length, len(children)))
|
||||
for idx, expected in enumerate(expected_ordering.get(parent)):
|
||||
test_class.assertEqual(expected, children[idx].name)
|
||||
blocks_checked.add(expected)
|
||||
|
||||
@@ -328,7 +328,7 @@ class VideoComponentPage(VideoPage):
|
||||
line_number (int): caption line number
|
||||
|
||||
"""
|
||||
caption_line_selector = ".subtitles li span[data-index='{index}']".format(index=line_number - 1)
|
||||
caption_line_selector = u".subtitles li span[data-index='{index}']".format(index=line_number - 1)
|
||||
self.q(css=caption_line_selector).results[0].send_keys(Keys.ENTER)
|
||||
|
||||
def is_caption_line_focused(self, line_number):
|
||||
@@ -339,7 +339,7 @@ class VideoComponentPage(VideoPage):
|
||||
line_number (int): caption line number
|
||||
|
||||
"""
|
||||
caption_line_selector = ".subtitles li span[data-index='{index}']".format(index=line_number - 1)
|
||||
caption_line_selector = u".subtitles li span[data-index='{index}']".format(index=line_number - 1)
|
||||
caption_container = self.q(css=caption_line_selector).results[0].find_element_by_xpath('..')
|
||||
return 'focused' in caption_container.get_attribute('class').split()
|
||||
|
||||
@@ -487,7 +487,7 @@ class VideoComponentPage(VideoPage):
|
||||
|
||||
"""
|
||||
translations_items = '.wrapper-translations-settings .list-settings-item'
|
||||
language_selector = translations_items + ' select option[value="{}"]'.format(language_code)
|
||||
language_selector = translations_items + u' select option[value="{}"]'.format(language_code)
|
||||
self.q(css=language_selector).nth(index).click()
|
||||
|
||||
def upload_translation(self, transcript_name, language_code):
|
||||
@@ -575,7 +575,7 @@ class VideoComponentPage(VideoPage):
|
||||
As all the captions lines are exactly same so only getting partial lines will work.
|
||||
"""
|
||||
self.wait_for_captions()
|
||||
selector = '.subtitles li:nth-child({})'
|
||||
selector = u'.subtitles li:nth-child({})'
|
||||
return ' '.join([self.q(css=selector.format(i)).text[0] for i in range(1, 6)])
|
||||
|
||||
def set_url_field(self, url, field_number):
|
||||
@@ -607,7 +607,7 @@ class VideoComponentPage(VideoPage):
|
||||
"""
|
||||
if message_type == 'status':
|
||||
self.wait_for_element_visibility(CLASS_SELECTORS[message_type],
|
||||
'{} message is Visible'.format(message_type.title()))
|
||||
u'{} message is Visible'.format(message_type.title()))
|
||||
|
||||
return self.q(css=CLASS_SELECTORS[message_type]).text[0]
|
||||
|
||||
@@ -653,7 +653,7 @@ class VideoComponentPage(VideoPage):
|
||||
"""
|
||||
Clear video url fields.
|
||||
"""
|
||||
script = """
|
||||
script = u"""
|
||||
$('{selector}')
|
||||
.prop('disabled', false)
|
||||
.removeClass('is-disabled')
|
||||
|
||||
@@ -30,7 +30,7 @@ class BaseXBlockEditorView(PageObject):
|
||||
"""
|
||||
Return `selector`, but limited to this particular `XBlockEditorView` context
|
||||
"""
|
||||
return '{}[data-locator="{}"] {}'.format(
|
||||
return u'{}[data-locator="{}"] {}'.format(
|
||||
self.BODY_SELECTOR,
|
||||
self.locator,
|
||||
selector
|
||||
|
||||
@@ -31,7 +31,7 @@ class AcidView(PageObject):
|
||||
# First make sure that an element with the view-container class is present on the page,
|
||||
# and then wait to make sure that the xblock has finished initializing.
|
||||
return (
|
||||
self.q(css='{} .acid-block'.format(self.context_selector)).present and
|
||||
self.q(css=u'{} .acid-block'.format(self.context_selector)).present and
|
||||
wait_for_xblock_initialization(self, self.context_selector) and
|
||||
self._ajax_finished()
|
||||
)
|
||||
@@ -52,14 +52,14 @@ class AcidView(PageObject):
|
||||
"""
|
||||
Return whether a particular :class:`.AcidBlock` test passed.
|
||||
"""
|
||||
selector = '{} .acid-block {} .pass'.format(self.context_selector, test_selector)
|
||||
selector = u'{} .acid-block {} .pass'.format(self.context_selector, test_selector)
|
||||
return bool(self.q(css=selector).results)
|
||||
|
||||
def child_test_passed(self, test_selector):
|
||||
"""
|
||||
Return whether a particular :class:`.AcidParentBlock` test passed.
|
||||
"""
|
||||
selector = '{} .acid-parent-block {} .pass'.format(self.context_selector, test_selector)
|
||||
selector = u'{} .acid-parent-block {} .pass'.format(self.context_selector, test_selector)
|
||||
return bool(self.q(css=selector).execute(try_interval=0.1, timeout=3))
|
||||
|
||||
@property
|
||||
@@ -88,7 +88,7 @@ class AcidView(PageObject):
|
||||
|
||||
def scope_passed(self, scope):
|
||||
return all(
|
||||
self.test_passed('.scope-storage-test.scope-{} {}'.format(scope, test))
|
||||
self.test_passed(u'.scope-storage-test.scope-{} {}'.format(scope, test))
|
||||
for test in (
|
||||
".server-storage-test-returned",
|
||||
".server-storage-test-succeeded",
|
||||
|
||||
@@ -10,7 +10,7 @@ def wait_for_xblock_initialization(page, xblock_css):
|
||||
"""
|
||||
def _is_finished_loading():
|
||||
# Wait for the xblock javascript to finish initializing
|
||||
is_done = page.browser.execute_script("return $({!r}).data('initialized')".format(xblock_css))
|
||||
is_done = page.browser.execute_script(u"return $({!r}).data('initialized')".format(xblock_css))
|
||||
return (is_done, is_done)
|
||||
|
||||
return Promise(_is_finished_loading, 'Finished initializing the xblock.').fulfill()
|
||||
|
||||
@@ -578,7 +578,7 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
|
||||
start_time = datetime.now(UTC)
|
||||
self.cohort_management_page.upload_cohort_file(filename)
|
||||
self._verify_cohort_by_csv_notification(
|
||||
"Your file '{}' has been uploaded. Allow a few minutes for processing.".format(filename)
|
||||
u"Your file '{}' has been uploaded. Allow a few minutes for processing.".format(filename)
|
||||
)
|
||||
|
||||
if not skip_events:
|
||||
|
||||
@@ -50,7 +50,7 @@ class CohortedDiscussionTestMixin(BaseDiscussionMixin, CohortTestMixin):
|
||||
self.refresh_thread_page(self.thread_id)
|
||||
self.assertEquals(
|
||||
self.thread_page.get_group_visibility_label(),
|
||||
"This post is visible only to {}.".format(self.cohort_1_name)
|
||||
u"This post is visible only to {}.".format(self.cohort_1_name)
|
||||
)
|
||||
|
||||
# Disable cohorts and verify that the post now shows as visible to everyone.
|
||||
|
||||
@@ -36,7 +36,7 @@ from common.test.acceptance.tests.helpers import UniqueCourseTest, get_modal_ale
|
||||
from openedx.core.lib.tests import attr
|
||||
|
||||
|
||||
THREAD_CONTENT_WITH_LATEX = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
||||
THREAD_CONTENT_WITH_LATEX = u"""Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
|
||||
ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||
ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit sse cillum dolore eu fugiat nulla pariatur.
|
||||
@@ -123,7 +123,7 @@ class DiscussionResponsePaginationTestMixin(BaseDiscussionMixin):
|
||||
(
|
||||
None if response_total == 0 else
|
||||
"Showing all responses" if response_total == displayed_responses else
|
||||
"Showing first {} responses".format(displayed_responses)
|
||||
u"Showing first {} responses".format(displayed_responses)
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
@@ -648,10 +648,10 @@ class DiscussionResponseEditTest(BaseDiscussionTestCase):
|
||||
page.submit_response_edit(response_id, description)
|
||||
|
||||
expected_response_html = (
|
||||
'<p><a href="{}">{}</a></p>'.format(url, description)
|
||||
u'<p><a href="{}">{}</a></p>'.format(url, description)
|
||||
)
|
||||
actual_response_html = page.q(
|
||||
css=".response_{} .response-body".format(response_id)
|
||||
css=u".response_{} .response-body".format(response_id)
|
||||
).html[0]
|
||||
self.assertEqual(expected_response_html, actual_response_html)
|
||||
|
||||
@@ -682,10 +682,10 @@ class DiscussionResponseEditTest(BaseDiscussionTestCase):
|
||||
page.submit_response_edit(response_id, '')
|
||||
|
||||
expected_response_html = (
|
||||
'<p><img src="{}" alt="{}" title=""></p>'.format(url, description)
|
||||
u'<p><img src="{}" alt="{}" title=""></p>'.format(url, description)
|
||||
)
|
||||
actual_response_html = page.q(
|
||||
css=".response_{} .response-body".format(response_id)
|
||||
css=u".response_{} .response-body".format(response_id)
|
||||
).html[0]
|
||||
self.assertEqual(expected_response_html, actual_response_html)
|
||||
|
||||
@@ -737,11 +737,11 @@ class DiscussionResponseEditTest(BaseDiscussionTestCase):
|
||||
page.submit_response_edit(response_id, "Some content")
|
||||
|
||||
expected_response_html = (
|
||||
'<p>Some content<img src="{}" alt="{}" title=""></p>'.format(
|
||||
u'<p>Some content<img src="{}" alt="{}" title=""></p>'.format(
|
||||
url, description)
|
||||
)
|
||||
actual_response_html = page.q(
|
||||
css=".response_{} .response-body".format(response_id)
|
||||
css=u".response_{} .response-body".format(response_id)
|
||||
).html[0]
|
||||
self.assertEqual(expected_response_html, actual_response_html)
|
||||
|
||||
@@ -1027,9 +1027,9 @@ class DiscussionEditorPreviewTest(UniqueCourseTest):
|
||||
appear in the preview box
|
||||
"""
|
||||
self.page.set_new_post_editor_value(
|
||||
r'\begin{equation}'
|
||||
r'\tau_g(\omega) = - \frac{d}{d\omega}\phi(\omega) \hspace{2em} (1) '
|
||||
r'\end{equation}'
|
||||
ur'\begin{equation}'
|
||||
ur'\tau_g(\omega) = - \frac{d}{d\omega}\phi(\omega) \hspace{2em} (1) '
|
||||
ur'\end{equation}'
|
||||
)
|
||||
self.assertIsNotNone(self.page.get_new_post_preview_text())
|
||||
self.page.click_element(".cancel")
|
||||
|
||||
@@ -482,7 +482,7 @@ class DivisionSchemeTest(BaseDividedDiscussionTest, BaseDiscussionMixin):
|
||||
refresh_thread_page()
|
||||
self.assertEquals(
|
||||
self.thread_page.get_group_visibility_label(),
|
||||
"This post is visible only to {}.".format("Audit")
|
||||
u"This post is visible only to {}.".format("Audit")
|
||||
)
|
||||
|
||||
# Disable dividing discussions and verify that the post now shows as visible to everyone.
|
||||
|
||||
@@ -58,7 +58,7 @@ def skip_if_browser(browser):
|
||||
@functools.wraps(test_function)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self.browser.name == browser:
|
||||
raise SkipTest('Skipping as this test will not work with {}'.format(browser))
|
||||
raise SkipTest(u'Skipping as this test will not work with {}'.format(browser))
|
||||
test_function(self, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
@@ -102,7 +102,7 @@ def is_focused_on_element(browser, selector):
|
||||
"""
|
||||
Check if the focus is on the element that matches the selector.
|
||||
"""
|
||||
return browser.execute_script("return $('{}').is(':focus')".format(selector))
|
||||
return browser.execute_script(u"return $('{}').is(':focus')".format(selector))
|
||||
|
||||
|
||||
def load_data_str(rel_path):
|
||||
@@ -159,7 +159,7 @@ def disable_css_animations(page):
|
||||
"""
|
||||
Disable CSS3 animations, transitions, transforms.
|
||||
"""
|
||||
page.browser.execute_script("""
|
||||
page.browser.execute_script(u"""
|
||||
var id = 'no-transitions';
|
||||
|
||||
// if styles were already added, just do nothing.
|
||||
@@ -236,7 +236,7 @@ def select_option_by_text(select_browser_query, option_text, focus_out=False):
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
msg = 'Selected option {}'.format(option_text)
|
||||
msg = u'Selected option {}'.format(option_text)
|
||||
EmptyPromise(lambda: select_option(select_browser_query, option_text), msg).fulfill()
|
||||
|
||||
|
||||
@@ -350,7 +350,7 @@ def get_element_padding(page, selector):
|
||||
progress_page.get_element_padding('.wrapper-msg.wrapper-auto-cert')
|
||||
|
||||
"""
|
||||
js_script = """
|
||||
js_script = u"""
|
||||
var $element = $('%(selector)s');
|
||||
|
||||
element_padding = {
|
||||
@@ -380,7 +380,7 @@ def create_multiple_choice_xml(correct_choice=2, num_choices=4):
|
||||
choices[correct_choice] = True
|
||||
|
||||
choice_names = ['choice_{}'.format(index) for index in range(num_choices)]
|
||||
question_text = 'The correct answer is Choice {}'.format(correct_choice)
|
||||
question_text = u'The correct answer is Choice {}'.format(correct_choice)
|
||||
|
||||
return MultipleChoiceResponseXMLFactory().build_xml(
|
||||
question_text=question_text,
|
||||
@@ -434,7 +434,7 @@ def assert_opened_help_link_is_correct(test, url):
|
||||
# Check that the URL loads. Can't do this in the browser because it might
|
||||
# be loading a "Maze Found" missing content page.
|
||||
response = requests.get(url)
|
||||
test.assertEqual(response.status_code, 200, "URL {!r} returned {}".format(url, response.status_code))
|
||||
test.assertEqual(response.status_code, 200, u"URL {!r} returned {}".format(url, response.status_code))
|
||||
|
||||
|
||||
EDX_BOOKS = {
|
||||
@@ -560,7 +560,7 @@ class EventsTestMixin(TestCase):
|
||||
# This is a bit of a hack, Promise calls str(description), so I set the description to an object with a
|
||||
# custom __str__ and have it do some intelligent stuff to generate a helpful error message.
|
||||
CollectedEventsDescription(
|
||||
'Waiting for {number_of_matches} events to match the filter:\n{event_filter}'.format(
|
||||
u'Waiting for {number_of_matches} events to match the filter:\n{event_filter}'.format(
|
||||
number_of_matches=number_of_matches,
|
||||
event_filter=self.event_filter_to_descriptive_string(event_filter),
|
||||
),
|
||||
@@ -855,7 +855,7 @@ class YouTubeStubConfig(object):
|
||||
|
||||
if not response.ok:
|
||||
raise YouTubeConfigError(
|
||||
'YouTube Server Configuration Failed. URL {0}, Configuration Data: {1}, Status was {2}'.format(
|
||||
u'YouTube Server Configuration Failed. URL {0}, Configuration Data: {1}, Status was {2}'.format(
|
||||
youtube_stub_config_url, config, response.status_code))
|
||||
|
||||
@classmethod
|
||||
@@ -873,7 +873,7 @@ class YouTubeStubConfig(object):
|
||||
|
||||
if not response.ok:
|
||||
raise YouTubeConfigError(
|
||||
'YouTube Server Configuration Failed. URL: {0} Status was {1}'.format(
|
||||
u'YouTube Server Configuration Failed. URL: {0} Status was {1}'.format(
|
||||
youtube_stub_config_url, response.status_code))
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -52,7 +52,7 @@ class LibraryContentTestBase(UniqueCourseTest):
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
self.library_fixture = LibraryFixture('test_org', self.unique_id, 'Test Library {}'.format(self.unique_id))
|
||||
self.library_fixture = LibraryFixture('test_org', self.unique_id, u'Test Library {}'.format(self.unique_id))
|
||||
self.populate_library_fixture(self.library_fixture)
|
||||
|
||||
self.library_fixture.install()
|
||||
@@ -218,11 +218,11 @@ class StudioLibraryContainerCapaFilterTest(LibraryContentTestBase, TestWithSearc
|
||||
def _get_problem_choice_group_text(self, name, items):
|
||||
""" Generates Choice Group CAPA problem XML """
|
||||
items_text = "\n".join([
|
||||
"<choice correct='{correct}'>{item}</choice>".format(correct=correct, item=item)
|
||||
u"<choice correct='{correct}'>{item}</choice>".format(correct=correct, item=item)
|
||||
for item, correct in items
|
||||
])
|
||||
|
||||
return textwrap.dedent("""
|
||||
return textwrap.dedent(u"""
|
||||
<problem>
|
||||
<p>{name}</p>
|
||||
<multiplechoiceresponse>
|
||||
@@ -234,7 +234,7 @@ class StudioLibraryContainerCapaFilterTest(LibraryContentTestBase, TestWithSearc
|
||||
""" Generates Select Option CAPA problem XML """
|
||||
items_text = ",".join(["'{0}'".format(item) for item in items])
|
||||
|
||||
return textwrap.dedent("""
|
||||
return textwrap.dedent(u"""
|
||||
<problem>
|
||||
<p>{name}</p>
|
||||
<optionresponse>
|
||||
|
||||
@@ -263,8 +263,8 @@ class LoginFromCombinedPageTest(UniqueCourseTest):
|
||||
"""
|
||||
Create a new user with a unique name and email.
|
||||
"""
|
||||
username = "test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = "{user}@example.com".format(user=username)
|
||||
username = u"test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = u"{user}@example.com".format(user=username)
|
||||
password = "password"
|
||||
|
||||
# Create the user (automatically logs us in)
|
||||
@@ -306,8 +306,8 @@ class RegisterFromCombinedPageTest(UniqueCourseTest):
|
||||
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)
|
||||
username = u"test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = u"{user}@example.com".format(user=username)
|
||||
self.register_page.register(
|
||||
email=email,
|
||||
password="password",
|
||||
@@ -329,8 +329,8 @@ class RegisterFromCombinedPageTest(UniqueCourseTest):
|
||||
# Don't agree to the terms of service / honor code.
|
||||
# Don't specify a country code, which is required.
|
||||
# Don't specify a favorite movie.
|
||||
username = "test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = "{user}@example.com".format(user=username)
|
||||
username = u"test_{uuid}".format(uuid=self.unique_id[0:6])
|
||||
email = u"{user}@example.com".format(user=username)
|
||||
self.register_page.register(
|
||||
email=email,
|
||||
password="password",
|
||||
@@ -721,7 +721,7 @@ class PDFTextBooksTabTest(UniqueCourseTest):
|
||||
|
||||
# Add PDF textbooks to course fixture.
|
||||
for i in range(1, 3):
|
||||
course_fix.add_textbook("PDF Book {}".format(i), [{"title": "Chapter Of Book {}".format(i), "url": ""}])
|
||||
course_fix.add_textbook(u"PDF Book {}".format(i), [{"title": u"Chapter Of Book {}".format(i), "url": ""}])
|
||||
|
||||
course_fix.install()
|
||||
|
||||
@@ -736,7 +736,7 @@ class PDFTextBooksTabTest(UniqueCourseTest):
|
||||
|
||||
# Verify each PDF textbook tab by visiting, it will fail if correct tab is not loaded.
|
||||
for i in range(1, 3):
|
||||
self.tab_nav.go_to_tab("PDF Book {}".format(i))
|
||||
self.tab_nav.go_to_tab(u"PDF Book {}".format(i))
|
||||
|
||||
|
||||
@attr(shard=1)
|
||||
|
||||
@@ -113,9 +113,9 @@ class CoursewareTest(UniqueCourseTest):
|
||||
"""
|
||||
xblocks = self.course_fix.get_nested_xblocks(category="problem")
|
||||
for index in range(1, len(xblocks) + 1):
|
||||
test_section_title = 'Test Section {}'.format(index)
|
||||
test_subsection_title = 'Test Subsection {}'.format(index)
|
||||
test_unit_title = 'Test Problem {}'.format(index)
|
||||
test_section_title = u'Test Section {}'.format(index)
|
||||
test_subsection_title = u'Test Subsection {}'.format(index)
|
||||
test_unit_title = u'Test Problem {}'.format(index)
|
||||
self.course_home_page.visit()
|
||||
self.course_home_page.outline.go_to_section(test_section_title, test_subsection_title)
|
||||
course_nav = self.courseware_page.nav
|
||||
|
||||
@@ -9,8 +9,8 @@ from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.lms.dashboard import DashboardPage
|
||||
from common.test.acceptance.tests.helpers import UniqueCourseTest, generate_course_key
|
||||
|
||||
DEFAULT_SHORT_DATE_FORMAT = '{dt:%b} {dt.day}, {dt.year}'
|
||||
TEST_DATE_FORMAT = '{dt:%b} {dt.day}, {dt.year} {dt.hour:02}:{dt.minute:02}'
|
||||
DEFAULT_SHORT_DATE_FORMAT = u'{dt:%b} {dt.day}, {dt.year}'
|
||||
TEST_DATE_FORMAT = u'{dt:%b} {dt.day}, {dt.year} {dt.hour:02}:{dt.minute:02}'
|
||||
|
||||
|
||||
class BaseLmsDashboardTest(UniqueCourseTest):
|
||||
@@ -201,7 +201,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
|
||||
self.course_fixture.configure_course()
|
||||
|
||||
end_date = DEFAULT_SHORT_DATE_FORMAT.format(dt=course_end_date)
|
||||
expected_course_date = "Ended - {end_date}".format(end_date=end_date)
|
||||
expected_course_date = u"Ended - {end_date}".format(end_date=end_date)
|
||||
|
||||
# reload the page for changes to course date changes to appear in dashboard
|
||||
self.dashboard_page.visit()
|
||||
@@ -234,7 +234,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
|
||||
self.course_fixture.configure_course()
|
||||
|
||||
start_date = DEFAULT_SHORT_DATE_FORMAT.format(dt=course_start_date)
|
||||
expected_course_date = "Started - {start_date}".format(start_date=start_date)
|
||||
expected_course_date = u"Started - {start_date}".format(start_date=start_date)
|
||||
|
||||
# reload the page for changes to course date changes to appear in dashboard
|
||||
self.dashboard_page.visit()
|
||||
@@ -267,7 +267,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
|
||||
self.course_fixture.configure_course()
|
||||
|
||||
start_date = DEFAULT_SHORT_DATE_FORMAT.format(dt=course_start_date)
|
||||
expected_course_date = "Starts - {start_date}".format(start_date=start_date)
|
||||
expected_course_date = u"Starts - {start_date}".format(start_date=start_date)
|
||||
|
||||
# reload the page for changes to course date changes to appear in dashboard
|
||||
self.dashboard_page.visit()
|
||||
@@ -301,7 +301,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
|
||||
self.course_fixture.configure_course()
|
||||
|
||||
start_date = TEST_DATE_FORMAT.format(dt=course_start_date)
|
||||
expected_course_date = "Starts - {start_date} UTC".format(start_date=start_date)
|
||||
expected_course_date = u"Starts - {start_date} UTC".format(start_date=start_date)
|
||||
|
||||
# reload the page for changes to course date changes to appear in dashboard
|
||||
self.dashboard_page.visit()
|
||||
@@ -338,7 +338,7 @@ class LmsDashboardPageTest(BaseLmsDashboardTest):
|
||||
})
|
||||
self.course_fixture._add_advanced_settings()
|
||||
|
||||
expected_course_date = "Starts - {start_date}".format(start_date=course_advertised_start)
|
||||
expected_course_date = u"Starts - {start_date}".format(start_date=course_advertised_start)
|
||||
|
||||
self.dashboard_page.visit()
|
||||
course_date = self.dashboard_page.get_course_date()
|
||||
|
||||
@@ -59,14 +59,14 @@ class EdxNotesTestMixin(UniqueCourseTest):
|
||||
XBlockFixtureDesc(
|
||||
"html",
|
||||
"Test HTML 2",
|
||||
data="""<p><span class="{}">Annotate this!</span></p>""".format(self.selector)
|
||||
data=u"""<p><span class="{}">Annotate this!</span></p>""".format(self.selector)
|
||||
),
|
||||
),
|
||||
XBlockFixtureDesc("vertical", "Test Unit 2").add_children(
|
||||
XBlockFixtureDesc(
|
||||
"html",
|
||||
"Test HTML 3",
|
||||
data="""<p><span class="{}">Annotate this!</span></p>""".format(self.selector)
|
||||
data=u"""<p><span class="{}">Annotate this!</span></p>""".format(self.selector)
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -75,7 +75,7 @@ class EdxNotesTestMixin(UniqueCourseTest):
|
||||
XBlockFixtureDesc(
|
||||
"html",
|
||||
"Test HTML 4",
|
||||
data="""
|
||||
data=u"""
|
||||
<p><span class="{}">Annotate this!</span></p>
|
||||
""".format(self.selector)
|
||||
),
|
||||
@@ -88,14 +88,14 @@ class EdxNotesTestMixin(UniqueCourseTest):
|
||||
XBlockFixtureDesc(
|
||||
"html",
|
||||
"Test HTML 5",
|
||||
data="""
|
||||
data=u"""
|
||||
<p><span class="{}">Annotate this!</span></p>
|
||||
""".format(self.selector)
|
||||
),
|
||||
XBlockFixtureDesc(
|
||||
"html",
|
||||
"Test HTML 6",
|
||||
data="""<p><span class="{}">Annotate this!</span></p>""".format(self.selector)
|
||||
data=u"""<p><span class="{}">Annotate this!</span></p>""".format(self.selector)
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -132,7 +132,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
|
||||
index = offset
|
||||
for component in components:
|
||||
for note in component.create_note(".{}".format(self.selector)):
|
||||
note.text = "TEST TEXT {}".format(index)
|
||||
note.text = u"TEST TEXT {}".format(index)
|
||||
index += 1
|
||||
|
||||
def edit_notes(self, components, offset=0):
|
||||
@@ -141,7 +141,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
|
||||
for component in components:
|
||||
self.assertGreater(len(component.notes), 0)
|
||||
for note in component.edit_note():
|
||||
note.text = "TEST TEXT {}".format(index)
|
||||
note.text = u"TEST TEXT {}".format(index)
|
||||
index += 1
|
||||
|
||||
def edit_tags_in_notes(self, components, tags):
|
||||
@@ -166,7 +166,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
|
||||
|
||||
def assert_text_in_notes(self, notes):
|
||||
actual = [note.text for note in notes]
|
||||
expected = ["TEST TEXT {}".format(i) for i in xrange(len(notes))]
|
||||
expected = [u"TEST TEXT {}".format(i) for i in xrange(len(notes))]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def assert_tags_in_notes(self, notes, expected_tags):
|
||||
|
||||
@@ -999,7 +999,7 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
self.certificates_section.add_certificate_exception(self.user_name, '')
|
||||
|
||||
self.assertIn(
|
||||
'{user} already in exception list.'.format(user=self.user_name),
|
||||
u'{user} already in exception list.'.format(user=self.user_name),
|
||||
self.certificates_section.message.text
|
||||
)
|
||||
|
||||
@@ -1046,7 +1046,7 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
self.certificates_section.wait_for_ajax()
|
||||
|
||||
self.assertIn(
|
||||
"{user} does not exist in the LMS. Please check your spelling and retry.".format(user=invalid_user),
|
||||
u"{user} does not exist in the LMS. Please check your spelling and retry.".format(user=invalid_user),
|
||||
self.certificates_section.message.text
|
||||
)
|
||||
|
||||
@@ -1079,7 +1079,7 @@ class CertificatesTest(BaseInstructorDashboardTest):
|
||||
self.certificates_section.wait_for_ajax()
|
||||
|
||||
self.assertIn(
|
||||
"{user} is not enrolled in this course. Please check your spelling and retry.".format(user=new_user),
|
||||
u"{user} is not enrolled in this course. Please check your spelling and retry.".format(user=new_user),
|
||||
self.certificates_section.message.text
|
||||
)
|
||||
|
||||
@@ -1217,7 +1217,7 @@ class CertificateInvalidationTest(BaseInstructorDashboardTest):
|
||||
|
||||
# Validate success message
|
||||
self.assertIn(
|
||||
"Certificate has been successfully invalidated for {user}.".format(user=self.student_name),
|
||||
u"Certificate has been successfully invalidated for {user}.".format(user=self.student_name),
|
||||
self.certificates_section.certificate_invalidation_message.text
|
||||
)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class MatlabProblemTest(ProblemsTest):
|
||||
"""
|
||||
Create a matlab problem for the test.
|
||||
"""
|
||||
problem_data = dedent("""
|
||||
problem_data = dedent(u"""
|
||||
<problem markdown="null">
|
||||
<text>
|
||||
<p>
|
||||
|
||||
@@ -76,7 +76,7 @@ class ProblemClarificationTest(ProblemsTest):
|
||||
"""
|
||||
Create a problem with a <clarification>
|
||||
"""
|
||||
xml = dedent("""
|
||||
xml = dedent(u"""
|
||||
<problem markdown="null">
|
||||
<text>
|
||||
<p>
|
||||
@@ -705,7 +705,7 @@ class ProblemQuestionDescriptionTest(ProblemsTest):
|
||||
"""
|
||||
Create a problem with question and description.
|
||||
"""
|
||||
xml = dedent("""
|
||||
xml = dedent(u"""
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>Eggplant is a _____?</label>
|
||||
|
||||
@@ -51,7 +51,7 @@ class ProblemTypeTestBaseMeta(ABCMeta):
|
||||
]
|
||||
|
||||
for required_attr in required_attrs:
|
||||
msg = ('{} is a required attribute for {}').format(
|
||||
msg = (u'{} is a required attribute for {}').format(
|
||||
required_attr, str(cls)
|
||||
)
|
||||
|
||||
@@ -126,7 +126,7 @@ class ProblemTypeTestBase(ProblemsTest, EventsTestMixin):
|
||||
Args:
|
||||
status: one of ("correct", "incorrect", "unanswered", "submitted")
|
||||
"""
|
||||
msg = "Wait for status to be {}".format(status)
|
||||
msg = u"Wait for status to be {}".format(status)
|
||||
selector = ', '.join(self.status_indicators[status])
|
||||
self.problem_page.wait_for_element_visibility(selector, msg)
|
||||
|
||||
@@ -536,9 +536,9 @@ class ProblemNeverShowCorrectnessMixin(object):
|
||||
# Problem progress text depends on points possible
|
||||
possible = 'possible (ungraded, results hidden)'
|
||||
if self.problem_points == 1:
|
||||
problem_progress = '1 point {}'.format(possible)
|
||||
problem_progress = u'1 point {}'.format(possible)
|
||||
else:
|
||||
problem_progress = '{} points {}'.format(self.problem_points, possible)
|
||||
problem_progress = u'{} points {}'.format(self.problem_points, possible)
|
||||
|
||||
# Make sure we're looking at the right problem
|
||||
self.problem_page.wait_for(
|
||||
@@ -994,7 +994,7 @@ class MultipleChoiceProblemTypeTestMultipleAttempt(MultipleChoiceProblemTypeBase
|
||||
for attempts_used in range(3):
|
||||
self.assertEqual(
|
||||
self.problem_page.submission_feedback,
|
||||
"You have used {} of 3 attempts".format(str(attempts_used)),
|
||||
u"You have used {} of 3 attempts".format(str(attempts_used)),
|
||||
"All 3 attempts are not available"
|
||||
)
|
||||
if attempts_used == 2:
|
||||
@@ -1777,7 +1777,7 @@ class ChoiceTextProblemTypeTestBase(ProblemTypeTestBase):
|
||||
Selects the nth (where n == input_num) choice of the problem.
|
||||
"""
|
||||
self.problem_page.q(
|
||||
css='div.problem input.ctinput[type="{}"]'.format(self.choice_type)
|
||||
css=u'div.problem input.ctinput[type="{}"]'.format(self.choice_type)
|
||||
).nth(input_num).click()
|
||||
|
||||
def _fill_input_text(self, value, input_num):
|
||||
|
||||
@@ -372,8 +372,7 @@ class SubsectionGradingPolicyA11yTest(SubsectionGradingPolicyBase):
|
||||
# Verify that y-Axis labels are aria-hidden
|
||||
self.assertEqual(['100%', 'true'], self.progress_page.y_tick_label(0))
|
||||
self.assertEqual(['0%', 'true'], self.progress_page.y_tick_label(1))
|
||||
self.assertEqual(['Pass 50%', 'true'], self.progress_page.y_tick_label(2))
|
||||
|
||||
self.assertEqual(['Pass 50%', 'true'], self.progress_page.y_tick_label(2)) # pylint: disable=unicode-format-string,line-too-long
|
||||
# Verify x-Axis labels and sr-text
|
||||
self._check_tick_text(0, [u'Homework 1 - Test Subsection 1 - 50% (1/2)'], u'HW 01')
|
||||
|
||||
@@ -434,7 +433,7 @@ class SubsectionGradingPolicyA11yTest(SubsectionGradingPolicyBase):
|
||||
|
||||
# Verify the overall score. The first element in the array is the sr-only text, and the
|
||||
# second is the total text (including the sr-only text).
|
||||
self.assertEqual(['Overall Score', 'Overall Score\n2%'], self.progress_page.graph_overall_score())
|
||||
self.assertEqual(['Overall Score', 'Overall Score\n2%'], self.progress_page.graph_overall_score()) # pylint: disable=unicode-format-string,line-too-long
|
||||
|
||||
|
||||
class ProgressPageA11yTest(ProgressPageBaseTest):
|
||||
|
||||
@@ -56,8 +56,8 @@ class TeamsTabBase(EventsTestMixin, ForumsConfigMixin, UniqueCourseTest):
|
||||
team = {
|
||||
'course_id': self.course_id,
|
||||
'topic_id': topic['id'],
|
||||
'name': 'Team {}'.format(i),
|
||||
'description': 'Description {}'.format(i),
|
||||
'name': u'Team {}'.format(i),
|
||||
'description': u'Description {}'.format(i),
|
||||
'language': 'aa',
|
||||
'country': 'AF'
|
||||
}
|
||||
@@ -623,7 +623,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
|
||||
self.assertEqual(search_results_page.header_name, 'Team Search')
|
||||
self.assertEqual(
|
||||
search_results_page.header_description,
|
||||
'Showing results for "{search_query}"'.format(search_query=search_query)
|
||||
u'Showing results for "{search_query}"'.format(search_query=search_query)
|
||||
)
|
||||
|
||||
def verify_on_page(self, teams_page, page_num, total_teams, pagination_header_text, footer_visible):
|
||||
@@ -916,7 +916,7 @@ class TeamFormActions(TeamsTabBase):
|
||||
title='Create a New Team',
|
||||
description='Create a new team if you can\'t find an existing team to join, '
|
||||
'or if you would like to learn with friends you know.',
|
||||
breadcrumbs='All Topics {topic_name}'.format(topic_name=self.topic['name'])
|
||||
breadcrumbs=u'All Topics {topic_name}'.format(topic_name=self.topic['name'])
|
||||
)
|
||||
|
||||
def verify_and_navigate_to_edit_team_page(self):
|
||||
@@ -933,7 +933,7 @@ class TeamFormActions(TeamsTabBase):
|
||||
title='Edit Team',
|
||||
description='If you make significant changes, make sure you notify '
|
||||
'members of the team before making these changes.',
|
||||
breadcrumbs='All Topics {topic_name} {team_name}'.format(
|
||||
breadcrumbs=u'All Topics {topic_name} {team_name}'.format(
|
||||
topic_name=self.topic['name'],
|
||||
team_name=self.team['name']
|
||||
)
|
||||
|
||||
@@ -144,7 +144,7 @@ class StudioLibraryTest(AcceptanceTest):
|
||||
fixture = LibraryFixture(
|
||||
'test_org',
|
||||
self.unique_id,
|
||||
'Test Library {}'.format(self.unique_id),
|
||||
u'Test Library {}'.format(self.unique_id),
|
||||
)
|
||||
self.populate_library_fixture(fixture)
|
||||
fixture.install()
|
||||
|
||||
@@ -48,7 +48,7 @@ class NestedVerticalTest(ContainerBase):
|
||||
self.group_a_item_1_action_index = 0
|
||||
self.group_a_item_2_action_index = 1
|
||||
|
||||
self.duplicate_label = "Duplicate of '{0}'"
|
||||
self.duplicate_label = u"Duplicate of '{0}'"
|
||||
self.discussion_label = "Discussion"
|
||||
|
||||
course_fixture.add_children(
|
||||
@@ -286,7 +286,7 @@ class BaseGroupConfigurationsTest(ContainerBase):
|
||||
self.assertEqual("Access is not restricted", visibility_editor.current_groups_message)
|
||||
else:
|
||||
self.assertEqual(
|
||||
"Access is restricted to: {groups}".format(groups=expected_current_groups),
|
||||
u"Access is restricted to: {groups}".format(groups=expected_current_groups),
|
||||
visibility_editor.current_groups_message
|
||||
)
|
||||
|
||||
@@ -1236,8 +1236,8 @@ class MoveComponentTest(ContainerBase):
|
||||
}
|
||||
self.source_component_display_name = 'HTML 11'
|
||||
self.source_xblock_category = 'component'
|
||||
self.message_move = 'Success! "{display_name}" has been moved.'
|
||||
self.message_undo = 'Move cancelled. "{display_name}" has been moved back to its original location.'
|
||||
self.message_move = u'Success! "{display_name}" has been moved.'
|
||||
self.message_undo = u'Move cancelled. "{display_name}" has been moved back to its original location.'
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""
|
||||
|
||||
@@ -141,7 +141,7 @@ class GradingPageTest(StudioCourseTest):
|
||||
self.assertIn(
|
||||
'0-3',
|
||||
grade_ranges,
|
||||
'expected range: 0-3, not found in grade ranges:{}'.format(grade_ranges)
|
||||
u'expected range: 0-3, not found in grade ranges:{}'.format(grade_ranges)
|
||||
)
|
||||
|
||||
def test_settings_are_persisted_on_save_only(self):
|
||||
|
||||
@@ -150,7 +150,7 @@ class HTMLComponentEditorTests(ContainerBase):
|
||||
--></style>
|
||||
""
|
||||
"""
|
||||
content = '<p class="title">pages</p><style><!-- .title { color: red; } --></style>'
|
||||
content = u'<p class="title">pages</p><style><!-- .title { color: red; } --></style>'
|
||||
|
||||
# Add HTML Text type component
|
||||
self._add_component('Text')
|
||||
|
||||
@@ -218,8 +218,8 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest, TestWithSe
|
||||
And I set Problem Type selector so "Any"
|
||||
Then I can see that "No matching content" warning is shown
|
||||
"""
|
||||
expected_tpl = "The specified library is configured to fetch {count} problems, " \
|
||||
"but there are only {actual} matching problems."
|
||||
expected_tpl = u"The specified library is configured to fetch {count} problems, " \
|
||||
u"but there are only {actual} matching problems."
|
||||
|
||||
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[1])
|
||||
|
||||
|
||||
@@ -43,9 +43,9 @@ class CertificatesTest(StudioCourseTest):
|
||||
Makes signatory dict which can be used in the tests to create certificates
|
||||
"""
|
||||
return {
|
||||
'name': '{prefix} Signatory Name'.format(prefix=prefix),
|
||||
'title': '{prefix} Signatory Title'.format(prefix=prefix),
|
||||
'organization': '{prefix} Signatory Organization'.format(prefix=prefix),
|
||||
'name': u'{prefix} Signatory Name'.format(prefix=prefix),
|
||||
'title': u'{prefix} Signatory Title'.format(prefix=prefix),
|
||||
'organization': u'{prefix} Signatory Organization'.format(prefix=prefix),
|
||||
}
|
||||
|
||||
def create_and_verify_certificate(self, course_title_override, existing_certs, signatories):
|
||||
|
||||
@@ -106,7 +106,7 @@ class PagesTest(StudioCourseTest):
|
||||
self.assertEqual(
|
||||
static_tab_titles,
|
||||
['Empty', 'First'],
|
||||
'Order should be:["Empty", "First] but getting {} from the page'.format(static_tab_titles)
|
||||
u'Order should be:["Empty", "First] but getting {} from the page'.format(static_tab_titles)
|
||||
)
|
||||
|
||||
def test_user_can_reorder_builtin_tabs(self):
|
||||
|
||||
@@ -28,14 +28,14 @@ class AnnotatableProblemTest(UniqueCourseTest):
|
||||
USERNAME = "STAFF_TESTER"
|
||||
EMAIL = "johndoe@example.com"
|
||||
|
||||
DATA_TEMPLATE = dedent("""\
|
||||
DATA_TEMPLATE = dedent(u"""\
|
||||
<annotatable>
|
||||
<instructions>Instruction text</instructions>
|
||||
<p>{}</p>
|
||||
</annotatable>
|
||||
""")
|
||||
|
||||
ANNOTATION_TEMPLATE = dedent("""\
|
||||
ANNOTATION_TEMPLATE = dedent(u"""\
|
||||
Before {0}.
|
||||
<annotation title="region {0}" body="Comment {0}" highlight="yellow" problem="{0}">
|
||||
Region Contents {0}
|
||||
@@ -43,7 +43,7 @@ class AnnotatableProblemTest(UniqueCourseTest):
|
||||
After {0}.
|
||||
""")
|
||||
|
||||
PROBLEM_TEMPLATE = dedent("""\
|
||||
PROBLEM_TEMPLATE = dedent(u"""\
|
||||
<problem max_attempts="1" weight="">
|
||||
<annotationresponse>
|
||||
<annotationinput>
|
||||
@@ -63,7 +63,7 @@ class AnnotatableProblemTest(UniqueCourseTest):
|
||||
</problem>
|
||||
""")
|
||||
|
||||
OPTION_TEMPLATE = """<option choice="{correctness}">{number}</option>"""
|
||||
OPTION_TEMPLATE = u"""<option choice="{correctness}">{number}</option>"""
|
||||
|
||||
def setUp(self):
|
||||
super(AnnotatableProblemTest, self).setUp()
|
||||
|
||||
@@ -46,10 +46,10 @@ class VideoEventsTestMixin(EventsTestMixin, VideoBaseTest):
|
||||
|
||||
def assert_field_type(self, event_dict, field, field_type):
|
||||
"""Assert that a particular `field` in the `event_dict` has a particular type"""
|
||||
self.assertIn(field, event_dict, '{0} not found in the root of the event'.format(field))
|
||||
self.assertIn(field, event_dict, u'{0} not found in the root of the event'.format(field))
|
||||
self.assertTrue(
|
||||
isinstance(event_dict[field], field_type),
|
||||
'Expected "{key}" to be a "{field_type}", but it has the value "{value}" of type "{t}"'.format(
|
||||
u'Expected "{key}" to be a "{field_type}", but it has the value "{value}" of type "{t}"'.format(
|
||||
key=field,
|
||||
value=event_dict[field],
|
||||
t=type(event_dict[field]),
|
||||
@@ -123,7 +123,7 @@ class VideoEventsTest(VideoEventsTestMixin):
|
||||
)
|
||||
for field in dynamic_string_fields:
|
||||
self.assert_field_type(load_video_event, field, basestring)
|
||||
self.assertIn(field, load_video_event, '{0} not found in the root of the event'.format(field))
|
||||
self.assertIn(field, load_video_event, u'{0} not found in the root of the event'.format(field))
|
||||
del load_video_event[field]
|
||||
|
||||
# A weak assertion for the timestamp as well
|
||||
@@ -360,7 +360,7 @@ class VideoBumperEventsTest(VideoEventsTestMixin):
|
||||
)
|
||||
for field in dynamic_string_fields:
|
||||
self.assert_field_type(load_video_event, field, basestring)
|
||||
self.assertIn(field, load_video_event, '{0} not found in the root of the event'.format(field))
|
||||
self.assertIn(field, load_video_event, u'{0} not found in the root of the event'.format(field))
|
||||
del load_video_event[field]
|
||||
|
||||
# A weak assertion for the timestamp as well
|
||||
|
||||
@@ -128,7 +128,7 @@ class VideoBaseTest(UniqueCourseTest):
|
||||
:param vertical_index: index for the vertical display name
|
||||
:return: XBlockFixtureDesc
|
||||
"""
|
||||
xblock_course_vertical = XBlockFixtureDesc('vertical', 'Test Vertical-{0}'.format(vertical_index))
|
||||
xblock_course_vertical = XBlockFixtureDesc('vertical', u'Test Vertical-{0}'.format(vertical_index))
|
||||
|
||||
for video in vertical_contents:
|
||||
xblock_course_vertical.add_children(
|
||||
@@ -1202,8 +1202,8 @@ class HLSVideoTest(VideoBaseTest):
|
||||
|
||||
for line_no in range(5):
|
||||
self.video.click_transcript_line(line_no=line_no)
|
||||
self.video.wait_for_position('0:0{}'.format(line_no))
|
||||
self.video.wait_for_position(u'0:0{}'.format(line_no))
|
||||
|
||||
for line_no in range(5):
|
||||
self.video.seek('0:0{}'.format(line_no))
|
||||
self.assertEqual(self.video.active_caption_text, 'Hi, edX welcomes you{}.'.format(line_no))
|
||||
self.video.seek(u'0:0{}'.format(line_no))
|
||||
self.assertEqual(self.video.active_caption_text, u'Hi, edX welcomes you{}.'.format(line_no))
|
||||
|
||||
@@ -133,7 +133,7 @@ class reprwrapper(object):
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
self.repr = 'Func: {}'.format(func.__name__)
|
||||
self.repr = u'Func: {}'.format(func.__name__)
|
||||
functools.update_wrapper(self, func)
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
|
||||
Reference in New Issue
Block a user