refactor: pyupgrade in common/tests (#26725)

This commit is contained in:
M. Zulqarnain
2021-03-09 18:03:34 +05:00
committed by GitHub
parent f0cac0c09d
commit a664067323
55 changed files with 320 additions and 362 deletions

View File

@@ -10,10 +10,10 @@ CMS_PORT = os.environ.get('BOK_CHOY_CMS_PORT', '8031')
LMS_PORT = os.environ.get('BOK_CHOY_LMS_PORT', '8003')
# Get the URL of the Studio instance under test
STUDIO_BASE_URL = os.environ.get('studio_url', 'http://{}:{}'.format(HOSTNAME, CMS_PORT))
STUDIO_BASE_URL = os.environ.get('studio_url', f'http://{HOSTNAME}:{CMS_PORT}')
# Get the URL of the LMS instance under test
LMS_BASE_URL = os.environ.get('lms_url', 'http://{}:{}'.format(HOSTNAME, LMS_PORT))
LMS_BASE_URL = os.environ.get('lms_url', f'http://{HOSTNAME}:{LMS_PORT}')
# Get the URL of the XQueue stub used in the test
XQUEUE_STUB_URL = os.environ.get('xqueue_url', 'http://localhost:8040')
@@ -22,10 +22,10 @@ XQUEUE_STUB_URL = os.environ.get('xqueue_url', 'http://localhost:8040')
ORA_STUB_URL = os.environ.get('ora_url', 'http://localhost:8041')
# Get the URL of the comments service stub used in the test
COMMENTS_STUB_URL = os.environ.get('comments_url', 'http://{}:4567'.format(HOSTNAME))
COMMENTS_STUB_URL = os.environ.get('comments_url', f'http://{HOSTNAME}:4567')
# Get the URL of the EdxNotes service stub used in the test
EDXNOTES_STUB_URL = os.environ.get('edxnotes_url', 'http://{}:8042'.format(HOSTNAME))
EDXNOTES_STUB_URL = os.environ.get('edxnotes_url', f'http://{HOSTNAME}:8042')
# Get the URL of the Catalog service stub used in the test
CATALOG_STUB_URL = os.environ.get('catalog_url', 'http://localhost:8091')

View File

@@ -18,7 +18,7 @@ class StudioApiLoginError(Exception):
pass # lint-amnesty, pylint: disable=unnecessary-pass
class StudioApiFixture(object):
class StudioApiFixture:
"""
Base class for fixtures that use the Studio restful API.
"""
@@ -42,12 +42,12 @@ class StudioApiFixture(object):
self.user = response.json()
if not self.user:
raise StudioApiLoginError(u'Auto-auth failed. Response was: {}'.format(self.user))
raise StudioApiLoginError(f'Auto-auth failed. Response was: {self.user}')
return session
else:
msg = u'Could not log in to use Studio restful API. Status code: {0}'.format(response.status_code)
msg = f'Could not log in to use Studio restful API. Status code: {response.status_code}'
raise StudioApiLoginError(msg)
@lazy
@@ -84,7 +84,7 @@ class XBlockContainerFixture(StudioApiFixture):
def __init__(self):
self.children = []
super(XBlockContainerFixture, self).__init__() # lint-amnesty, pylint: disable=super-with-arguments
super().__init__()
def add_children(self, *args):
"""
@@ -125,14 +125,14 @@ class XBlockContainerFixture(StudioApiFixture):
)
if not response.ok:
msg = u"Could not create {0}. Status was {1}".format(xblock_desc, response.status_code)
msg = f"Could not create {xblock_desc}. Status was {response.status_code}"
raise FixtureError(msg)
try:
loc = response.json().get('locator')
xblock_desc.locator = loc
except ValueError:
raise FixtureError(u"Could not decode JSON from '{0}'".format(response.content)) # lint-amnesty, pylint: disable=raise-missing-from
raise FixtureError(f"Could not decode JSON from '{response.content}'") # lint-amnesty, pylint: disable=raise-missing-from
# Configure the XBlock
response = self.session.post(
@@ -144,7 +144,7 @@ class XBlockContainerFixture(StudioApiFixture):
if response.ok:
return loc
else:
raise FixtureError(u"Could not update {0}. Status code: {1}".format(xblock_desc, response.status_code))
raise FixtureError(f"Could not update {xblock_desc}. Status code: {response.status_code}")
def _update_xblock(self, locator, data):
"""
@@ -152,13 +152,13 @@ class XBlockContainerFixture(StudioApiFixture):
"""
# Create the new XBlock
response = self.session.put(
"{}/xblock/{}".format(STUDIO_BASE_URL, locator),
f"{STUDIO_BASE_URL}/xblock/{locator}",
data=json.dumps(data),
headers=self.headers,
)
if not response.ok:
msg = u"Could not update {} with data {}. Status was {}".format(locator, data, response.status_code)
msg = f"Could not update {locator} with data {data}. Status was {response.status_code}"
raise FixtureError(msg)
def _encode_post_dict(self, post_dict):

View File

@@ -11,7 +11,7 @@ from common.test.acceptance.fixtures import CATALOG_STUB_URL
from common.test.acceptance.fixtures.config import ConfigModelFixture
class CatalogFixture(object):
class CatalogFixture:
"""
Interface to set up mock responses from the Catalog stub server.
"""
@@ -30,15 +30,15 @@ class CatalogFixture(object):
uuid = program['uuid']
uuids.append(uuid)
program_key = '{base}.{uuid}'.format(base=key, uuid=uuid)
program_key = f'{key}.{uuid}'
requests.put(
'{}/set_config'.format(CATALOG_STUB_URL),
f'{CATALOG_STUB_URL}/set_config',
data={program_key: json.dumps(program)},
)
# Stub list endpoint as if the uuids_only query param had been passed.
requests.put(
'{}/set_config'.format(CATALOG_STUB_URL),
f'{CATALOG_STUB_URL}/set_config',
data={key: json.dumps(uuids)},
)
@@ -50,7 +50,7 @@ class CatalogFixture(object):
pathways (list): A list of credit pathways. List endpoint will be stubbed using data from this list.
"""
requests.put(
'{}/set_config'.format(CATALOG_STUB_URL),
f'{CATALOG_STUB_URL}/set_config',
data={'catalog.pathways': json.dumps({'results': pathways, 'next': None})}
)
@@ -62,18 +62,18 @@ class CatalogFixture(object):
program_types (list): A list of program types. List endpoint will be stubbed using data from this list.
"""
requests.put(
'{}/set_config'.format(CATALOG_STUB_URL),
f'{CATALOG_STUB_URL}/set_config',
data={'catalog.programs_types': json.dumps(program_types)},
)
class CatalogIntegrationMixin(object):
class CatalogIntegrationMixin:
"""Mixin providing a method used to configure the catalog integration."""
def set_catalog_integration(self, is_enabled=False, service_username=None):
"""Use this to change the catalog integration config model during tests."""
ConfigModelFixture('/config/catalog', {
'enabled': is_enabled,
'internal_api_url': '{}/api/v1/'.format(CATALOG_STUB_URL),
'internal_api_url': f'{CATALOG_STUB_URL}/api/v1/',
'cache_ttl': 0,
'service_username': service_username,
}).install()

View File

@@ -32,21 +32,21 @@ class CertificateConfigFixture(StudioApiFixture):
def __init__(self, course_id, certificates_data):
self.course_id = course_id
self.certificates = certificates_data
super(CertificateConfigFixture, self).__init__() # lint-amnesty, pylint: disable=super-with-arguments
super().__init__()
def install(self):
"""
Push the certificates config data to certificate endpoint.
"""
response = self.session.post(
'{}/certificates/{}'.format(STUDIO_BASE_URL, self.course_id),
f'{STUDIO_BASE_URL}/certificates/{self.course_id}',
data=json.dumps(self.certificates),
headers=self.headers
)
if not response.ok:
raise CertificateConfigFixtureError(
u"Could not create certificate {0}. Status was {1}".format(
"Could not create certificate {}. Status was {}".format(
json.dumps(self.certificates), response.status_code
)
)
@@ -58,14 +58,14 @@ class CertificateConfigFixture(StudioApiFixture):
Update the certificates config data to certificate endpoint.
"""
response = self.session.put(
'{}/certificates/{}/{}'.format(STUDIO_BASE_URL, self.course_id, certificate_id),
f'{STUDIO_BASE_URL}/certificates/{self.course_id}/{certificate_id}',
data=json.dumps(self.certificates),
headers=self.headers
)
if not response.ok:
raise CertificateConfigUpdateFixtureError(
u"Could not update certificate {0}. Status was {1}".format(
"Could not update certificate {}. Status was {}".format(
json.dumps(self.certificates), response.status_code
)
)

View File

@@ -7,7 +7,6 @@ import json
import re
import requests
import six
from lazy import lazy
from common.test.acceptance.fixtures import LMS_BASE_URL, STUDIO_BASE_URL
@@ -20,7 +19,7 @@ class ConfigModelFixtureError(Exception):
pass # lint-amnesty, pylint: disable=unnecessary-pass
class ConfigModelFixture(object):
class ConfigModelFixture:
"""
Configure a ConfigurationModel by using it's JSON api.
"""
@@ -49,7 +48,7 @@ class ConfigModelFixture(object):
if not response.ok:
raise ConfigModelFixtureError(
u"Could not configure url '{}'. response: {} - {}".format(
"Could not configure url '{}'. response: {} - {}".format(
self._api_base,
response,
response.content,
@@ -90,7 +89,7 @@ class ConfigModelFixture(object):
# auto_auth returns information about the newly created user
# capture this so it can be used by by the testcases.
user_pattern = re.compile(
six.text_type(r'Logged in user {0} \({1}\) with password {2} and user_id {3}').format(
r'Logged in user {} \({}\) with password {} and user_id {}'.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:
@@ -99,5 +98,5 @@ class ConfigModelFixture(object):
return session
else:
msg = u"Could not log in to use ConfigModel restful API. Status code: {0}".format(response.status_code)
msg = f"Could not log in to use ConfigModel restful API. Status code: {response.status_code}"
raise ConfigModelFixtureError(msg)

View File

@@ -9,7 +9,6 @@ import mimetypes
from collections import namedtuple
from textwrap import dedent
import six
from opaque_keys.edx.keys import CourseKey
from path import Path
@@ -17,7 +16,7 @@ from common.test.acceptance.fixtures import STUDIO_BASE_URL
from common.test.acceptance.fixtures.base import FixtureError, XBlockContainerFixture
class XBlockFixtureDesc(object):
class XBlockFixtureDesc:
"""
Description of an XBlock, used to configure a course fixture.
"""
@@ -76,7 +75,7 @@ class XBlockFixtureDesc(object):
Return a string representation of the description.
Useful for error messages.
"""
return dedent(u"""
return dedent("""
<XBlockFixtureDescriptor:
category={0},
data={1},
@@ -120,7 +119,7 @@ class CourseFixture(XBlockContainerFixture):
to enable entrance exam settings would be a dict like this {"entrance_exam_enabled": "true"}
These have the same meaning as in the Studio restful API /course end-point.
"""
super(CourseFixture, self).__init__() # lint-amnesty, pylint: disable=super-with-arguments
super().__init__()
self._course_dict = {
'org': org,
'number': number,
@@ -154,7 +153,7 @@ class CourseFixture(XBlockContainerFixture):
"""
String representation of the course fixture, useful for debugging.
"""
return u"<CourseFixture: org='{org}', number='{number}', run='{run}'>".format(**self._course_dict)
return "<CourseFixture: org='{org}', number='{number}', run='{run}'>".format(**self._course_dict)
def add_course_details(self, course_details):
"""
@@ -236,14 +235,14 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not retrieve course outline json. Status was {0}".format(
"Could not retrieve course outline json. Status was {}".format(
response.status_code))
try:
course_outline_json = response.json()
except ValueError:
raise FixtureError( # lint-amnesty, pylint: disable=raise-missing-from
u"Could not decode course outline as JSON: '{0}'".format(response)
f"Could not decode course outline as JSON: '{response}'"
)
return course_outline_json
@@ -257,7 +256,7 @@ class CourseFixture(XBlockContainerFixture):
block_id = self._course_dict['run']
else:
block_id = 'course'
return six.text_type(course_key.make_usage_key('course', block_id))
return str(course_key.make_usage_key('course', block_id))
@property
def _assets_url(self):
@@ -272,7 +271,7 @@ class CourseFixture(XBlockContainerFixture):
Return the locator string for the course handouts
"""
course_key = CourseKey.from_string(self._course_key)
return six.text_type(course_key.make_usage_key('course_info', 'handouts'))
return str(course_key.make_usage_key('course_info', 'handouts'))
def _create_course(self):
"""
@@ -291,18 +290,18 @@ class CourseFixture(XBlockContainerFixture):
except ValueError:
raise FixtureError( # lint-amnesty, pylint: disable=raise-missing-from
u"Could not parse response from course request as JSON: '{0}'".format(
"Could not parse response from course request as JSON: '{}'".format(
response.content))
# This will occur if the course identifier is not unique
if err is not None:
raise FixtureError(u"Could not create course {0}. Error message: '{1}'".format(self, err))
raise FixtureError(f"Could not create course {self}. Error message: '{err}'")
if response.ok:
self._course_key = response.json()['course_key']
else:
raise FixtureError(
u"Could not create course {0}. Status was {1}\nResponse content was: {2}".format(
"Could not create course {}. Status was {}\nResponse content was: {}".format(
self._course_dict, response.status_code, response.content))
def _configure_course(self):
@@ -316,14 +315,14 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not retrieve course details. Status was {0}".format(
"Could not retrieve course details. Status was {}".format(
response.status_code))
try:
details = response.json()
except ValueError:
raise FixtureError( # lint-amnesty, pylint: disable=raise-missing-from
u"Could not decode course details as JSON: '{0}'".format(details)
f"Could not decode course details as JSON: '{details}'"
)
# Update the old details with our overrides
@@ -337,7 +336,7 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not update course details to '{0}' with {1}: Status was {2}.".format(
"Could not update course details to '{}' with {}: Status was {}.".format(
self._course_details, url, response.status_code))
def _install_course_handouts(self):
@@ -348,10 +347,10 @@ class CourseFixture(XBlockContainerFixture):
# Construct HTML with each of the handout links
handouts_li = [
u'<li><a href="/static/{handout}">Example Handout</a></li>'.format(handout=handout)
f'<li><a href="/static/{handout}">Example Handout</a></li>'
for handout in self._handouts
]
handouts_html = u'<ol class="treeview-handoutsnav">{}</ol>'.format("".join(handouts_li))
handouts_html = '<ol class="treeview-handoutsnav">{}</ol>'.format("".join(handouts_li))
# Update the course's handouts HTML
payload = json.dumps({
@@ -365,7 +364,7 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not update course handouts with {0}. Status was {1}".format(url, response.status_code))
f"Could not update course handouts with {url}. Status was {response.status_code}")
def _install_course_updates(self):
"""
@@ -382,7 +381,7 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not add update to course: {0} with {1}. Status was {2}".format(
"Could not add update to course: {} with {}. Status was {}".format(
update, url, response.status_code))
def _upload_assets(self):
@@ -408,7 +407,7 @@ class CourseFixture(XBlockContainerFixture):
upload_response = self.session.post(url, files=files, headers=headers)
if not upload_response.ok:
raise FixtureError(u'Could not upload {asset_name} with {url}. Status code: {code}'.format(
raise FixtureError('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):
@@ -423,7 +422,7 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not add book to course: {0} with {1}. Status was {2}".format(
"Could not add book to course: {} with {}. Status was {}".format(
book, url, response.status_code))
def _add_advanced_settings(self):
@@ -440,12 +439,12 @@ class CourseFixture(XBlockContainerFixture):
if not response.ok:
raise FixtureError(
u"Could not update advanced details to '{0}' with {1}: Status was {2}.".format(
"Could not update advanced details to '{}' with {}: Status was {}.".format(
self._advanced_settings, url, response.status_code))
def _create_xblock_children(self, parent_loc, xblock_descriptions):
"""
Recursively create XBlock children.
"""
super(CourseFixture, self)._create_xblock_children(parent_loc, xblock_descriptions) # lint-amnesty, pylint: disable=super-with-arguments
super()._create_xblock_children(parent_loc, xblock_descriptions)
self._publish_xblock(parent_loc)

View File

@@ -14,7 +14,7 @@ from common.test.acceptance.fixtures.config import ConfigModelFixture
class ContentFactory(factory.Factory): # lint-amnesty, pylint: disable=missing-class-docstring
class Meta(object):
class Meta:
model = dict
id = None
@@ -68,7 +68,7 @@ class Response(Comment):
class SearchResult(factory.Factory): # lint-amnesty, pylint: disable=missing-class-docstring
class Meta(object):
class Meta:
model = dict
discussion_data = []
@@ -78,14 +78,14 @@ class SearchResult(factory.Factory): # lint-amnesty, pylint: disable=missing-cl
corrected_text = None
class DiscussionContentFixture(object): # lint-amnesty, pylint: disable=missing-class-docstring
class DiscussionContentFixture: # lint-amnesty, pylint: disable=missing-class-docstring
def push(self):
"""
Push the data to the stub comments service.
"""
return requests.put(
'{}/set_config'.format(COMMENTS_STUB_URL),
f'{COMMENTS_STUB_URL}/set_config',
data=self.get_config_data()
)
@@ -175,7 +175,7 @@ class SearchResultFixture(DiscussionContentFixture): # lint-amnesty, pylint: di
return {"search_result": json.dumps(self.result)}
class ForumsConfigMixin(object):
class ForumsConfigMixin:
"""Mixin providing a method used to configure the forums integration."""
def enable_forums(self, is_enabled=True):
"""Configures whether or not forums are enabled."""

View File

@@ -1,9 +1,6 @@
"""
Fixture to create a Content Library
"""
import six
from opaque_keys.edx.keys import CourseKey
from common.test.acceptance.fixtures import STUDIO_BASE_URL
@@ -22,7 +19,7 @@ class LibraryFixture(XBlockContainerFixture):
"""
Configure the library fixture to create a library with
"""
super(LibraryFixture, self).__init__() # lint-amnesty, pylint: disable=super-with-arguments
super().__init__()
self.library_info = {
'org': org,
'number': number,
@@ -31,13 +28,13 @@ class LibraryFixture(XBlockContainerFixture):
self.display_name = display_name
self._library_key = None
super(LibraryFixture, self).__init__() # lint-amnesty, pylint: disable=super-with-arguments
super().__init__()
def __str__(self):
"""
String representation of the library fixture, useful for debugging.
"""
return u"<LibraryFixture: org='{org}', number='{number}'>".format(**self.library_info)
return "<LibraryFixture: org='{org}', number='{number}'>".format(**self.library_info)
def install(self):
"""
@@ -64,7 +61,7 @@ class LibraryFixture(XBlockContainerFixture):
Return the locator string for the LibraryRoot XBlock that is the root of the library hierarchy.
"""
lib_key = CourseKey.from_string(self._library_key)
return six.text_type(lib_key.make_usage_key('library', 'library'))
return str(lib_key.make_usage_key('library', 'library'))
def _create_library(self):
"""
@@ -84,7 +81,7 @@ class LibraryFixture(XBlockContainerFixture):
err_msg = response.json().get('ErrMsg')
except ValueError:
err_msg = "Unknown Error"
raise FixtureError(u"Could not create library {}. Status was {}, error was: {}".format(
raise FixtureError("Could not create library {}. Status was {}, error was: {}".format(
self.library_info, response.status_code, err_msg
))
@@ -92,4 +89,4 @@ class LibraryFixture(XBlockContainerFixture):
# Disable publishing for library XBlocks:
xblock_desc.publish = "not-applicable"
return super(LibraryFixture, self).create_xblock(parent_loc, xblock_desc) # lint-amnesty, pylint: disable=super-with-arguments
return super().create_xblock(parent_loc, xblock_desc)

View File

@@ -6,7 +6,7 @@ Tools to create programs-related data for use in bok choy tests.
from common.test.acceptance.fixtures.config import ConfigModelFixture
class ProgramsConfigMixin(object):
class ProgramsConfigMixin:
"""Mixin providing a method used to configure the programs feature."""
def set_programs_api_configuration(self, is_enabled=False):
"""Dynamically adjusts the Programs config model during tests."""

View File

@@ -10,7 +10,7 @@ CMS_PORT = os.environ.get('BOK_CHOY_CMS_PORT', 8031)
LMS_PORT = os.environ.get('BOK_CHOY_LMS_PORT', 8003)
# Get the URL of the instance under test
BASE_URL = os.environ.get('test_url', 'http://{}:{}'.format(HOSTNAME, LMS_PORT))
BASE_URL = os.environ.get('test_url', f'http://{HOSTNAME}:{LMS_PORT}')
# The URL used for user auth in testing
AUTH_BASE_URL = os.environ.get('test_url', 'http://{}:{}'.format(HOSTNAME, CMS_PORT))
AUTH_BASE_URL = os.environ.get('test_url', f'http://{HOSTNAME}:{CMS_PORT}')

View File

@@ -5,14 +5,14 @@ Auto-auth page (used to automatically log in during testing).
import json
import os
from urllib import parse
from six.moves import urllib
from bok_choy.page_object import PageObject, unguarded
# The URL used for user auth in testing
HOSTNAME = os.environ.get('BOK_CHOY_HOSTNAME', 'localhost')
CMS_PORT = os.environ.get('BOK_CHOY_CMS_PORT', 8031)
AUTH_BASE_URL = os.environ.get('test_url', 'http://{}:{}'.format(HOSTNAME, CMS_PORT))
AUTH_BASE_URL = os.environ.get('test_url', f'http://{HOSTNAME}:{CMS_PORT}')
FULL_NAME = 'Test'
@@ -45,7 +45,7 @@ class AutoAuthPage(PageObject):
Note that "global staff" is NOT the same as course staff.
"""
super(AutoAuthPage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
# This will eventually hold the details about the user account
self._user_info = None
@@ -94,7 +94,7 @@ class AutoAuthPage(PageObject):
Construct the URL.
"""
url = AUTH_BASE_URL + "/auto_auth"
query_str = urllib.parse.urlencode(self._params)
query_str = parse.urlencode(self._params)
if query_str:
url += "?" + query_str

View File

@@ -1,10 +1,6 @@
"""
Utility methods common to Studio and the LMS.
"""
import six
from common.test.acceptance.tests.helpers import disable_animations
@@ -21,7 +17,7 @@ def click_css(page, css, source_index=0):
"""Is the given element visible?"""
# Only make the call to size once (instead of once for the height and once for the width)
# because otherwise you will trigger a extra query on a remote element.
return element.is_displayed() and all(size > 0 for size in six.itervalues(element.size))
return element.is_displayed() and all(size > 0 for size in element.size.values())
# Disable all animations for faster testing with more reliable synchronization
disable_animations(page)

View File

@@ -8,4 +8,4 @@ import os
# Get the URL of the instance under test
HOSTNAME = os.environ.get('BOK_CHOY_HOSTNAME', 'localhost')
LMS_PORT = os.environ.get('BOK_CHOY_LMS_PORT', 8003)
BASE_URL = os.environ.get('test_url', 'http://{}:{}'.format(HOSTNAME, LMS_PORT))
BASE_URL = os.environ.get('test_url', f'http://{HOSTNAME}:{LMS_PORT}')

View File

@@ -63,7 +63,7 @@ class AccountSettingsPage(FieldsMixin, PageObject):
"""
Switch between the different account settings tabs.
"""
self.q(css='#{}'.format(tab_id)).click()
self.q(css=f'#{tab_id}').click()
@property
def is_order_history_tab_visible(self):
@@ -72,14 +72,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'.u-field-{} .u-field-order-{}'.format(field_id, field_name))
query = self.q(css=f'.u-field-{field_id} .u-field-order-{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'.u-field-{} .u-field-{}'.format(field_id, 'link')).visible
return self.q(css='.u-field-{} .u-field-{}'.format(field_id, 'link')).visible
@property
def is_delete_button_visible(self):

View File

@@ -20,7 +20,7 @@ class CourseHomePage(CoursePage):
return self.q(css='.course-outline').present
def __init__(self, browser, course_id):
super(CourseHomePage, self).__init__(browser, course_id) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser, course_id)
self.course_id = course_id
self.preview = StaffPreviewPage(browser, self)
# TODO: TNL-6546: Remove the following

View File

@@ -23,7 +23,7 @@ class CoursePage(PageObject): # lint-amnesty, pylint: disable=abstract-method
Course ID is currently of the form "edx/999/2013_Spring"
but this format could change.
"""
super(CoursePage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.course_id = course_id
@property

View File

@@ -57,7 +57,7 @@ class CourseWikiSubviewPage(CoursePage): # pylint: disable=abstract-method
Course ID is currently of the form "edx/999/2013_Spring"
but this format could change.
"""
super(CourseWikiSubviewPage, self).__init__(browser, course_id) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser, course_id)
self.course_id = course_id
self.course_info = course_info
self.article_name = "{org}.{course_number}.{course_run}".format(

View File

@@ -21,7 +21,7 @@ class CoursewarePage(CoursePage):
subsection_selector = '.chapter-content-container a'
def __init__(self, browser, course_id): # lint-amnesty, pylint: disable=useless-super-delegation
super(CoursewarePage, self).__init__(browser, course_id) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser, course_id)
# self.nav = CourseNavPage(browser, self)
def is_browser_on_page(self):
@@ -45,7 +45,7 @@ class CoursewarePage(CoursePage):
except IndexError:
return False
sequential_position_css = u'#sequence-list #tab_{0}'.format(sequential_position - 1)
sequential_position_css = '#sequence-list #tab_{}'.format(sequential_position - 1)
self.q(css=sequential_position_css).first.click()
EmptyPromise(is_at_new_position, "Position navigation fulfilled").fulfill()
@@ -74,6 +74,6 @@ class CoursewarePage(CoursePage):
return False
self.q(
css=u'.{} > .sequence-nav-button.{}'.format(top_or_bottom_class, next_or_previous_class)
css=f'.{top_or_bottom_class} > .sequence-nav-button.{next_or_previous_class}'
).first.click()
EmptyPromise(is_at_new_tab_id, "Button navigation fulfilled").fulfill()

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
Student dashboard page.
"""
@@ -14,7 +13,7 @@ class DashboardPage(PageObject):
Student dashboard, where the student can view
courses she/he has registered for.
"""
url = "{base}/dashboard".format(base=BASE_URL)
url = f"{BASE_URL}/dashboard"
def is_browser_on_page(self):
return self.q(css='.my-courses').present

View File

@@ -18,7 +18,7 @@ class DiscussionThreadPage(PageObject):
url = None
def __init__(self, browser, thread_selector):
super(DiscussionThreadPage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.thread_selector = thread_selector
def _find_within(self, selector):
@@ -76,10 +76,10 @@ class DiscussionThreadPage(PageObject):
class DiscussionTabSingleThreadPage(CoursePage): # lint-amnesty, pylint: disable=missing-class-docstring
def __init__(self, browser, course_id, discussion_id, thread_id):
super(DiscussionTabSingleThreadPage, self).__init__(browser, course_id) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser, course_id)
self.thread_page = DiscussionThreadPage(
browser,
u"body.discussion .discussion-article[data-id='{thread_id}']".format(thread_id=thread_id)
f"body.discussion .discussion-article[data-id='{thread_id}']"
)
self.url_path = "discussion/forum/{discussion_id}/threads/{thread_id}".format(
discussion_id=discussion_id, thread_id=thread_id
@@ -103,7 +103,7 @@ class DiscussionTabHomePage(CoursePage):
ALERT_SELECTOR = ".discussion-body .forum-nav .search-alert"
def __init__(self, browser, course_id):
super(DiscussionTabHomePage, self).__init__(browser, course_id) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser, course_id)
self.url_path = "discussion/forum/"
self.root_selector = None

View File

@@ -1,16 +1,12 @@
# -*- coding: utf-8 -*-
"""
Mixins for fields.
"""
import six
from bok_choy.promise import EmptyPromise
from common.test.acceptance.tests.helpers import get_selected_option_text, select_option_by_text
class FieldsMixin(object):
class FieldsMixin:
"""
Methods for testing fields in pages.
"""
@@ -19,7 +15,7 @@ class FieldsMixin(object):
"""
Return field with field_id.
"""
query = self.q(css=u'.u-field-{}'.format(field_id))
query = self.q(css=f'.u-field-{field_id}')
return query.text[0] if query.present else None
def wait_for_field(self, field_id):
@@ -28,7 +24,7 @@ class FieldsMixin(object):
"""
EmptyPromise(
lambda: self.field(field_id) is not None,
u"Field with id \"{0}\" is in DOM.".format(field_id)
f"Field with id \"{field_id}\" is in DOM."
).fulfill()
def mode_for_field(self, field_id):
@@ -40,7 +36,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-{}'.format(field_id))
query = self.q(css=f'.u-field-{field_id}')
if not query.present:
return None
@@ -62,7 +58,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-{} .u-field-icon'.format(field_id))
query = self.q(css=f'.u-field-{field_id} .u-field-icon')
return query.present and icon_id in query.attrs('class')[0].split()
def title_for_field(self, field_id):
@@ -70,7 +66,7 @@ class FieldsMixin(object):
Return the title of a field.
"""
self.wait_for_field(field_id)
query = self.q(css=six.u('.u-field-{} .u-field-title').format(field_id))
query = self.q(css=f'.u-field-{field_id} .u-field-title')
return query.text[0] if query.present else None
def message_for_field(self, field_id):
@@ -78,7 +74,7 @@ class FieldsMixin(object):
Return the current message in a field.
"""
self.wait_for_field(field_id)
query = self.q(css=six.u('.u-field-{} .u-field-message'.format(field_id)))
query = self.q(css=f'.u-field-{field_id} .u-field-message')
return query.text[0] if query.present else None
def message_for_textarea_field(self, field_id):
@@ -87,7 +83,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-{} .u-field-message-help'.format(field_id))
query = self.q(css=f'.u-field-{field_id} .u-field-message-help')
return query.text[0] if query.present else None
def wait_for_message(self, field_id, message):
@@ -96,7 +92,7 @@ class FieldsMixin(object):
"""
EmptyPromise(
lambda: message in (self.message_for_field(field_id) or ''),
u"Messsage \"{0}\" is visible.".format(message)
f"Messsage \"{message}\" is visible."
).fulfill()
def indicator_for_field(self, field_id):
@@ -105,7 +101,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-{} .u-field-message .fa'.format(field_id))
query = self.q(css=f'.u-field-{field_id} .u-field-message .fa')
return [
class_name for class_name
in query.attrs('class')[0].split(' ')
@@ -118,14 +114,14 @@ class FieldsMixin(object):
"""
EmptyPromise(
lambda: indicator == self.indicator_for_field(field_id),
u"Indicator \"{0}\" is visible.".format(self.indicator_for_field(field_id))
"Indicator \"{}\" 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'.u-field-{}'.format(field_id))
query = self.q(css=f'.u-field-{field_id}')
if not query.present:
return None
@@ -138,7 +134,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'.u-field-{}'.format(field_id)).first.click()
self.q(css=f'.u-field-{field_id}').first.click()
def value_for_readonly_field(self, field_id):
"""
@@ -146,7 +142,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-{} .u-field-value'.format(field_id))
query = self.q(css=f'.u-field-{field_id} .u-field-value')
if not query.present:
return None
@@ -157,16 +153,16 @@ class FieldsMixin(object):
Get or set the value of a text field.
"""
self.wait_for_field(field_id)
query = self.q(css=six.u('.u-field-{} input'.format(field_id)))
query = self.q(css=f'.u-field-{field_id} input')
if not query.present:
return None
if value is not None:
current_value = query.attrs('value')[0]
query.results[0].send_keys(u'\ue003' * len(current_value)) # Delete existing value.
query.results[0].send_keys('\ue003' * len(current_value)) # Delete existing value.
query.results[0].send_keys(value) # Input new value
if press_enter:
query.results[0].send_keys(u'\ue007') # Press Enter
query.results[0].send_keys('\ue007') # Press Enter
return query.attrs('value')[0]
def set_value_for_textarea_field(self, field_id, value):
@@ -176,12 +172,12 @@ class FieldsMixin(object):
self.wait_for_field(field_id)
self.make_field_editable(field_id)
field_selector = u'.u-field-{} textarea'.format(field_id)
field_selector = f'.u-field-{field_id} textarea'
self.wait_for_element_presence(field_selector, 'Editable textarea is present.')
query = self.q(css=field_selector)
query.fill(value)
query.results[0].send_keys(u'\ue007') # Press Enter
query.results[0].send_keys('\ue007') # Press Enter
def get_non_editable_mode_value(self, field_id):
"""
@@ -190,7 +186,7 @@ class FieldsMixin(object):
self.wait_for_field(field_id)
self.wait_for_ajax()
return self.q(css=u'.u-field-{} .u-field-value .u-field-value-readonly'.format(field_id)).text[0]
return self.q(css=f'.u-field-{field_id} .u-field-value .u-field-value-readonly').text[0]
def value_for_dropdown_field(self, field_id, value=None, focus_out=False):
"""
@@ -200,7 +196,7 @@ class FieldsMixin(object):
self.make_field_editable(field_id)
query = self.q(css=u'.u-field-{} select'.format(field_id))
query = self.q(css=f'.u-field-{field_id} select')
if not query.present:
return None
@@ -218,7 +214,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-link-title-{}'.format(field_id))
query = self.q(css=f'.u-field-link-title-{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 +223,7 @@ class FieldsMixin(object):
"""
return EmptyPromise(
lambda: self.link_title_for_link_field(field_id) == expected_title,
u"Link field with link title \"{0}\" is visible.".format(expected_title)
f"Link field with link title \"{expected_title}\" is visible."
).fulfill()
def click_on_link_in_link_field(self, field_id, field_type='a'):
@@ -236,7 +232,7 @@ class FieldsMixin(object):
"""
self.wait_for_field(field_id)
query = self.q(css=u'.u-field-{} {}'.format(field_id, field_type))
query = self.q(css=f'.u-field-{field_id} {field_type}')
if query.present:
query.first.click()
@@ -244,12 +240,12 @@ class FieldsMixin(object):
"""
Returns bool based on the highlighted border for field.
"""
query = self.q(css=u'.u-field-{}.error'.format(field_id))
query = self.q(css=f'.u-field-{field_id}.error')
return True if query.present else False # lint-amnesty, pylint: disable=simplifiable-if-expression
def get_social_first_element(self):
"""
Returns the title of first social media link.
"""
query = self.q(css=six.u('.u-field-social_links > .field > .field-label'))
query = self.q(css='.u-field-social_links > .field > .field-label')
return query[0].text

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
Instructor (2) dashboard page.
"""
@@ -123,7 +122,7 @@ class CohortManagementSection(PageObject):
cohorts_warning_title = '.message-warning .message-title'
if self.q(css=cohorts_warning_title).visible:
return self.q(css='.message-title').text[0] == u'You currently have no cohorts configured'
return self.q(css='.message-title').text[0] == 'You currently have no cohorts configured'
# The page may be in either the traditional management state, or an 'add new cohort' state.
# Confirm the CSS class is visible because the CSS class can exist on the page even in different states.
return self.q(css='.cohorts-state-section').visible or self.q(css='.new-cohort-form').visible
@@ -132,7 +131,7 @@ class CohortManagementSection(PageObject):
"""
Return `selector`, but limited to the cohort management context.
"""
return u'.cohort-management {}'.format(selector)
return f'.cohort-management {selector}'
def _get_cohort_options(self):
"""
@@ -585,8 +584,8 @@ class MembershipPageAutoEnrollSection(PageObject):
MembershipPageAutoEnrollSection.NOTIFICATION_ERROR
Returns True if a {section_type} notification is displayed.
"""
notification_selector = u'.auto_enroll_csv .results .message-%s' % section_type
self.wait_for_element_presence(notification_selector, u"%s Notification" % section_type.title())
notification_selector = '.auto_enroll_csv .results .message-%s' % section_type
self.wait_for_element_presence(notification_selector, "%s Notification" % section_type.title())
return self.q(css=notification_selector).is_present()
def first_notification_message(self, section_type):
@@ -595,8 +594,8 @@ class MembershipPageAutoEnrollSection(PageObject):
MembershipPageAutoEnrollSection.NOTIFICATION_ERROR
Returns the first message from the list of messages in the {section_type} section.
"""
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())
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())
return self.q(css=error_message_selector).text[0]
def upload_correct_csv_file(self):
@@ -629,8 +628,8 @@ class MembershipPageAutoEnrollSection(PageObject):
"""
Fill in the form with the provided email and submit it.
"""
email_selector = u"{} textarea".format(self.batch_enrollment_selector)
enrollment_button = u"{} .enrollment-button[data-action='enroll']".format(self.batch_enrollment_selector)
email_selector = f"{self.batch_enrollment_selector} textarea"
enrollment_button = f"{self.batch_enrollment_selector} .enrollment-button[data-action='enroll']"
# Fill the email addresses after the email selector is visible.
self.wait_for_element_visibility(email_selector, 'Email field is visible')
@@ -646,9 +645,9 @@ class MembershipPageAutoEnrollSection(PageObject):
"""
Check notification div is visible and have message.
"""
notification_selector = u'{} .request-response'.format(self.batch_enrollment_selector)
notification_selector = f'{self.batch_enrollment_selector} .request-response'
self.wait_for_element_visibility(notification_selector, 'Notification div is visible')
return self.q(css=u"{} h3".format(notification_selector)).text
return self.q(css=f"{notification_selector} h3").text
class CertificatesPage(PageObject):

View File

@@ -13,7 +13,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'#u-field-select-account_privacy option[value="{}"]'
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
PROFILE_VISIBILITY_INPUT = '#u-field-select-account_privacy'
@@ -25,7 +25,7 @@ class Badge(PageObject):
def __init__(self, element, browser):
self.element = element
super(Badge, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
def is_browser_on_page(self):
return BrowserQuery(self.element, css=".badge-details").visible
@@ -55,8 +55,8 @@ class Badge(PageObject):
"""
Execute javascript to bring the popup(.badges-model) inside the window.
"""
script_to_execute = (u"var popup = document.querySelectorAll('.badges-modal')[0];;"
u"popup.style.left = '20%';")
script_to_execute = ("var popup = document.querySelectorAll('.badges-modal')[0];;"
"popup.style.left = '20%';")
self.browser.execute_script(script_to_execute)
def close_modal(self):
@@ -84,7 +84,7 @@ class LearnerProfilePage(FieldsMixin, PageObject):
browser (Browser): The browser instance.
username (str): Profile username.
"""
super(LearnerProfilePage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.username = username
@property
@@ -147,7 +147,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, u'Privacy is set to {}'.format(privacy)).fulfill()
EmptyPromise(lambda: privacy == self.privacy, f'Privacy is set to {privacy}').fulfill()
self.q(css='.btn-change-privacy').first.click()
self.wait_for_ajax()
@@ -165,7 +165,7 @@ class LearnerProfilePage(FieldsMixin, PageObject):
True/False
"""
self.wait_for_ajax()
return self.q(css='.u-field-{}'.format(field_id)).visible
return self.q(css=f'.u-field-{field_id}').visible
def field_is_editable(self, field_id):
"""

View File

@@ -29,7 +29,7 @@ class ProgramListingPage(PageObject):
class ProgramDetailsPage(PageObject):
"""Program details page."""
program_uuid = str(uuid4())
url = '{base}/dashboard/programs/{program_uuid}/'.format(base=BASE_URL, program_uuid=program_uuid)
url = f'{BASE_URL}/dashboard/programs/{program_uuid}/'
def is_browser_on_page(self):
return self.q(css='.js-program-details-wrapper').present

View File

@@ -28,7 +28,7 @@ class StaffPreviewPage(PageObject):
parent_page: None if this is being used as a subclass. Otherwise,
the parent_page the contains this staff preview page fragment.
"""
super(StaffPreviewPage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.parent_page = parent_page
def is_browser_on_page(self):

View File

@@ -30,7 +30,7 @@ class TabNavPage(PageObject):
"""
if tab_name not in ['Course', 'Home', 'Discussion', 'Wiki', 'Progress']:
self.warning(u"'{0}' is not a valid tab name".format(tab_name))
self.warning(f"'{tab_name}' is not a valid tab name")
# The only identifier for individual tabs is the link href
# so we find the tab with `tab_name` in its text.
@@ -39,7 +39,7 @@ class TabNavPage(PageObject):
if tab_css is not None:
self.q(css=tab_css).first.click()
else:
self.warning(u"No tabs found for '{0}'".format(tab_name))
self.warning(f"No tabs found for '{tab_name}'")
self.wait_for_page()
self._is_on_tab_promise(tab_name).fulfill()
@@ -57,9 +57,9 @@ class TabNavPage(PageObject):
return None
else:
if self.is_using_boostrap_style_tabs():
return u'ul.navbar-nav li:nth-of-type({0}) a'.format(tab_index + 1)
return 'ul.navbar-nav li:nth-of-type({}) a'.format(tab_index + 1)
else:
return u'ol.course-tabs li:nth-of-type({0}) a'.format(tab_index + 1)
return 'ol.course-tabs li:nth-of-type({}) a'.format(tab_index + 1)
@property
def tab_names(self):
@@ -105,5 +105,5 @@ 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),
u"{0} is the current tab".format(tab_name)
f"{tab_name} is the current tab"
)

View File

@@ -31,7 +31,7 @@ CSS_CLASS_NAMES = {
'captions_rendered': '.video.is-captions-rendered',
'captions': '.subtitles',
'captions_text': '.subtitles li span',
'captions_text_getter': u'.subtitles li span[role="link"][data-index="{}"]',
'captions_text_getter': '.subtitles li span[role="link"][data-index="{}"]',
'closed_captions': '.closed-captions',
'error_message': '.video .video-player .video-error',
'video_container': '.video',
@@ -76,7 +76,7 @@ class VideoPage(PageObject):
@wait_for_js
def is_browser_on_page(self):
return self.q(css='div{0}'.format(CSS_CLASS_NAMES['video_xmodule'])).present
return self.q(css='div{}'.format(CSS_CLASS_NAMES['video_xmodule'])).present
@wait_for_js
def wait_for_video_class(self):
@@ -86,7 +86,7 @@ class VideoPage(PageObject):
"""
self.wait_for_ajax()
video_selector = '{0}'.format(CSS_CLASS_NAMES['video_container'])
video_selector = '{}'.format(CSS_CLASS_NAMES['video_container'])
self.wait_for_element_presence(video_selector, 'Video is initialized')
@wait_for_js
@@ -106,7 +106,7 @@ class VideoPage(PageObject):
video_player_buttons.append('play')
for button in video_player_buttons:
self.wait_for_element_visibility(VIDEO_BUTTONS[button], u'{} button is visible'.format(button))
self.wait_for_element_visibility(VIDEO_BUTTONS[button], f'{button} button is visible')
def _is_finished_loading():
"""
@@ -136,7 +136,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(u"Incorrect Video Display Name: '{0}'".format(video_display_name))
raise ValueError(f"Incorrect Video Display Name: '{video_display_name}'")
return '.vert.vert-{}'.format(video_display_names.index(video_display_name))
else:
return '.vert.vert-0'
@@ -154,7 +154,7 @@ class VideoPage(PageObject):
"""
if vertical:
return u'{vertical} {video_element}'.format(
return '{vertical} {video_element}'.format(
vertical=self.get_video_vertical_selector(self.current_video_display_name),
video_element=class_name)
else:
@@ -224,4 +224,4 @@ class VideoPage(PageObject):
# Verify that captions state is toggled/changed
EmptyPromise(lambda: self.is_captions_visible() == captions_new_state,
u"Transcripts are {state}".format(state=state)).fulfill()
f"Transcripts are {state}").fulfill()

View File

@@ -9,5 +9,5 @@ import os
HOSTNAME = os.environ.get('BOK_CHOY_HOSTNAME', 'localhost')
CMS_PORT = os.environ.get('BOK_CHOY_CMS_PORT', 8031)
LMS_PORT = os.environ.get('BOK_CHOY_LMS_PORT', 8003)
BASE_URL = os.environ.get('test_url', 'http://{}:{}'.format(HOSTNAME, CMS_PORT))
LMS_URL = os.environ.get('test_url', 'http://{}:{}'.format(HOSTNAME, LMS_PORT))
BASE_URL = os.environ.get('test_url', f'http://{HOSTNAME}:{CMS_PORT}')
LMS_URL = os.environ.get('test_url', f'http://{HOSTNAME}:{LMS_PORT}')

View File

@@ -22,13 +22,13 @@ class ContainerPage(PageObject, HelpMixin):
ADD_MISSING_GROUPS_SELECTOR = '.notification-action-button[data-notification-action="add-missing-groups"]'
def __init__(self, browser, locator):
super(ContainerPage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.locator = locator
@property
def url(self):
"""URL to the container page for an xblock."""
return u"{}/container/{}".format(BASE_URL, self.locator)
return f"{BASE_URL}/container/{self.locator}"
@property
def name(self): # lint-amnesty, pylint: disable=missing-function-docstring
@@ -40,7 +40,7 @@ class ContainerPage(PageObject, HelpMixin):
def is_browser_on_page(self):
def _xblock_count(class_name, request_token):
return len(self.q(css=u'{body_selector} .xblock.{class_name}[data-request-token="{request_token}"]'.format(
return len(self.q(css='{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)
@@ -51,7 +51,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=u'{} [data-request-token="{}"]'.format(XBlockWrapper.BODY_SELECTOR, request_token)).results) # lint-amnesty, pylint: disable=line-too-long
num_wrappers = len(self.q(css=f'{XBlockWrapper.BODY_SELECTOR} [data-request-token="{request_token}"]').results) # lint-amnesty, pylint: disable=line-too-long
# 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"
@@ -324,7 +324,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=u'confirmation message {status}'.format(
self.wait_for(_verify_message, description='confirmation message {status}'.format(
status='hidden' if verify_hidden else 'present'
))
@@ -389,8 +389,8 @@ class ContainerPage(PageObject, HelpMixin):
Returns:
list
"""
self.q(css='.add-xblock-component-button[data-type={}]'.format(category_type)).first.click()
return self.q(css='.{}-type-tabs>li>a'.format(category_type)).text
self.q(css=f'.add-xblock-component-button[data-type={category_type}]').first.click()
return self.q(css=f'.{category_type}-type-tabs>li>a').text
def get_category_tab_components(self, category_type, tab_index):
"""
@@ -403,7 +403,7 @@ class ContainerPage(PageObject, HelpMixin):
Returns:
list
"""
css = u'#tab{tab_index} button[data-category={category_type}] span'.format(
css = '#tab{tab_index} button[data-category={category_type}] span'.format(
tab_index=tab_index,
category_type=category_type
)
@@ -433,17 +433,17 @@ class XBlockWrapper(PageObject):
}
def __init__(self, browser, locator):
super(XBlockWrapper, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.locator = locator
def is_browser_on_page(self):
return self.q(css='{}[data-locator="{}"]'.format(self.BODY_SELECTOR, self.locator)).present
return self.q(css=f'{self.BODY_SELECTOR}[data-locator="{self.locator}"]').present
def _bounded_selector(self, selector):
"""
Return `selector`, but limited to this particular `CourseOutlineChild` context
"""
return u'{}[data-locator="{}"] {}'.format(
return '{}[data-locator="{}"] {}'.format(
self.BODY_SELECTOR,
self.locator,
selector
@@ -495,7 +495,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(u'{} p.{}'.format(self.VALIDATION_SELECTOR, css_class)))
return self.q(css=self._bounded_selector(f'{self.VALIDATION_SELECTOR} p.{css_class}'))
@property
def has_validation_warning(self):
@@ -524,7 +524,7 @@ class XBlockWrapper(PageObject):
@property
def validation_error_messages(self):
return self.q(css=self._bounded_selector('{} .xblock-message-item.error'.format(self.VALIDATION_SELECTOR))).text
return self.q(css=self._bounded_selector(f'{self.VALIDATION_SELECTOR} .xblock-message-item.error')).text
@property
def validation_not_configured_warning_text(self):
@@ -627,7 +627,7 @@ class XBlockWrapper(PageObject):
"""
If editing, set the value of a field.
"""
selector = u'{} li.field label:contains("{}") + input'.format(self.editor_selector, field_display_name)
selector = f'{self.editor_selector} li.field label:contains("{field_display_name}") + input'
script = "$(arguments[0]).val(arguments[1]).change();"
self.browser.execute_script(script, selector, field_value)
@@ -635,7 +635,7 @@ class XBlockWrapper(PageObject):
"""
If editing, reset the value of a field to its default.
"""
scope = u'{} li.field label:contains("{}")'.format(self.editor_selector, field_display_name)
scope = f'{self.editor_selector} li.field label:contains("{field_display_name}")'
script = "$(arguments[0]).siblings('.setting-clear').click();"
self.browser.execute_script(script, scope)
@@ -643,18 +643,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=u'$("{}").find'.format(self.editor_selector))
type_in_codemirror(self, index, text, find_prefix=f'$("{self.editor_selector}").find')
def set_license(self, license_type):
"""
Uses the UI to set the course's license to the given license_type (str)
"""
css_selector = (
u"ul.license-types li[data-license={license_type}] button"
"ul.license-types li[data-license={license_type}] button"
).format(license_type=license_type)
self.wait_for_element_presence(
css_selector,
u"{license_type} button is present".format(license_type=license_type)
f"{license_type} button is present"
)
self.q(css=css_selector).click()
@@ -666,7 +666,7 @@ class XBlockWrapper(PageObject):
@property
def editor_selector(self):
return u'.xblock-studio_view'
return '.xblock-studio_view'
def _click_button(self, button_name):
"""

View File

@@ -6,7 +6,6 @@ Base class for pages specific to a course in Studio.
import os
from abc import abstractmethod
import six
from bok_choy.page_object import PageObject
from opaque_keys.edx.locator import CourseLocator
@@ -39,7 +38,7 @@ class CoursePage(PageObject, HelpMixin):
These identifiers will likely change in the future.
"""
super(CoursePage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.course_info = {
'course_org': course_org,
'course_num': course_num,
@@ -59,4 +58,4 @@ class CoursePage(PageObject, HelpMixin):
self.course_info['course_run'],
deprecated=(default_store == 'draft')
)
return "/".join([BASE_URL, self.url_path, six.text_type(course_key)])
return "/".join([BASE_URL, self.url_path, str(course_key)])

View File

@@ -2,9 +2,6 @@
Library edit page in Studio
"""
import six
from bok_choy.page_object import PageObject
from common.test.acceptance.pages.studio import BASE_URL
@@ -18,7 +15,7 @@ class LibraryPage(PageObject, HelpMixin):
Base page for Library pages. Defaults URL to the edit page.
"""
def __init__(self, browser, locator):
super(LibraryPage, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.locator = locator
@property
@@ -26,7 +23,7 @@ class LibraryPage(PageObject, HelpMixin):
"""
URL to the library edit page for the given library.
"""
return "{}/library/{}".format(BASE_URL, six.text_type(self.locator))
return "{}/library/{}".format(BASE_URL, str(self.locator))
def is_browser_on_page(self):
"""
@@ -50,4 +47,4 @@ class LibraryEditPage(LibraryPage, PaginatedMixin, UsersPageMixin):
for improved test reliability.
"""
self.wait_for_ajax()
super(LibraryEditPage, self).wait_until_ready() # lint-amnesty, pylint: disable=super-with-arguments
super().wait_until_ready()

View File

@@ -13,7 +13,7 @@ from common.test.acceptance.pages.studio.course_page import CoursePage
@js_defined('jQuery')
class CourseOutlineItem(object):
class CourseOutlineItem:
"""
A mixin class for any :class:`PageObject` shown in a course outline.
"""
@@ -33,9 +33,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 u"{}(<browser>, {!r})".format(self.__class__.__name__, self.locator)
return f"{self.__class__.__name__}(<browser>, {self.locator!r})"
except AttributeError:
return u"{}(<browser>)".format(self.__class__.__name__)
return f"{self.__class__.__name__}(<browser>)"
def _bounded_selector(self, selector):
"""
@@ -45,7 +45,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 u'{}[data-locator="{}"] {}'.format(
return '{}[data-locator="{}"] {}'.format(
self.BODY_SELECTOR,
self.locator,
selector
@@ -105,17 +105,17 @@ class CourseOutlineChild(PageObject, CourseOutlineItem):
BODY_SELECTOR = '.outline-item'
def __init__(self, browser, locator):
super(CourseOutlineChild, self).__init__(browser) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(browser)
self.locator = locator
def is_browser_on_page(self):
return self.q(css='{}[data-locator="{}"]'.format(self.BODY_SELECTOR, self.locator)).present
return self.q(css=f'{self.BODY_SELECTOR}[data-locator="{self.locator}"]').present
def _bounded_selector(self, selector):
"""
Return `selector`, but limited to this particular `CourseOutlineChild` context
"""
return u'{}[data-locator="{}"] {}'.format(
return '{}[data-locator="{}"] {}'.format(
self.BODY_SELECTOR,
self.locator,
selector
@@ -216,7 +216,7 @@ class CourseOutlineSection(CourseOutlineContainer, CourseOutlineChild):
self.add_child() # lint-amnesty, pylint: disable=no-member
class ExpandCollapseLinkState(object):
class ExpandCollapseLinkState:
"""
Represents the three states that the expand/collapse link can be in
"""
@@ -271,7 +271,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
self.wait_for_element_visibility('#is_prereq', 'Gating settings fields are present.')
class CourseOutlineModal(object):
class CourseOutlineModal:
"""
Page object specifically for a modal window on the course outline page.

View File

@@ -6,7 +6,7 @@ Mixin to include for Paginated container pages
from selenium.webdriver.common.keys import Keys
class PaginatedMixin(object):
class PaginatedMixin:
"""
Mixin class used for paginated page tests.
"""
@@ -18,20 +18,20 @@ class PaginatedMixin(object):
To specify a specific arrow, pass an iterable with a single element, 'next' or 'previous'.
"""
return all(self.q(css=u'nav.%s * .%s-page-link.is-disabled' % (position, arrow)) for arrow in arrows)
return all(self.q(css=f'nav.{position} * .{arrow}-page-link.is-disabled') for arrow in arrows)
def move_back(self, position):
"""
Clicks one of the forward nav buttons. Position can be 'top' or 'bottom'.
"""
self.q(css=u'nav.%s * .previous-page-link' % position)[0].click()
self.q(css='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=u'nav.%s * .next-page-link' % position)[0].click()
self.q(css='nav.%s * .next-page-link' % position)[0].click()
self.wait_until_ready()
def go_to_page(self, number):

View File

@@ -1,4 +1,3 @@
# coding: utf-8
"""
Course Schedule and Details Settings page.
"""

View File

@@ -16,7 +16,7 @@ SIDE_BAR_HELP_CSS = '.external-help a, .external-help-button'
@js_defined('window.jQuery')
def type_in_codemirror(page, index, text, find_prefix="$"): # lint-amnesty, pylint: disable=missing-function-docstring
script = u"""
script = """
var cm = {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror;
CodeMirror.signal(cm, "focus", cm);
cm.setValue(arguments[0]);
@@ -28,7 +28,7 @@ def type_in_codemirror(page, index, text, find_prefix="$"): # lint-amnesty, pyl
@js_defined('window.jQuery')
def get_codemirror_value(page, index=0, find_prefix="$"):
return page.browser.execute_script(
u"return {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror.getValue();".format(
"return {find_prefix}('div.CodeMirror:eq({index})').get(0).CodeMirror.getValue();".format(
index=index, find_prefix=find_prefix
)
)
@@ -80,7 +80,7 @@ def verify_ordering(test_class, page, expected_orderings): # pylint: disable=un
assert len(blocks_checked) == len(xblocks)
class HelpMixin(object):
class HelpMixin:
"""
Mixin for testing Help links.
"""

View File

@@ -12,7 +12,6 @@ from bok_choy.javascript import js_defined, wait_for_js
from bok_choy.promise import EmptyPromise, Promise
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from six.moves import range
from common.test.acceptance.pages.common.utils import sync_on_notification # lint-amnesty, pylint: disable=no-name-in-module
from common.test.acceptance.pages.lms.video.video import VideoPage
@@ -108,12 +107,12 @@ class VideoComponentPage(VideoPage):
@wait_for_js
def is_browser_on_page(self):
return (
self.q(css='div{0}'.format(CLASS_SELECTORS['video_xmodule'])).present or
self.q(css='div{0}'.format(CLASS_SELECTORS['xblock'])).present
self.q(css='div{}'.format(CLASS_SELECTORS['video_xmodule'])).present or
self.q(css='div{}'.format(CLASS_SELECTORS['xblock'])).present
)
def get_element_selector(self, class_name, vertical=False):
return super(VideoComponentPage, self).get_element_selector(class_name, vertical=vertical) # lint-amnesty, pylint: disable=super-with-arguments
return super().get_element_selector(class_name, vertical=vertical)
def _wait_for(self, check_func, desc, result=False, timeout=30):
"""
@@ -271,7 +270,7 @@ class VideoComponentPage(VideoPage):
Download handout at `url`
"""
kwargs = dict()
session_id = [{i['name']: i['value']} for i in self.browser.get_cookies() if i['name'] == u'sessionid']
session_id = [{i['name']: i['value']} for i in self.browser.get_cookies() if i['name'] == 'sessionid']
if session_id:
kwargs.update({
'cookies': session_id[0]
@@ -332,7 +331,7 @@ class VideoComponentPage(VideoPage):
line_number (int): caption line number
"""
caption_line_selector = u".subtitles li span[data-index='{index}']".format(index=line_number - 1)
caption_line_selector = ".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):
@@ -343,7 +342,7 @@ class VideoComponentPage(VideoPage):
line_number (int): caption line number
"""
caption_line_selector = u".subtitles li span[data-index='{index}']".format(index=line_number - 1)
caption_line_selector = ".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()
@@ -441,9 +440,9 @@ class VideoComponentPage(VideoPage):
field_id = self.q(css=query).nth(index).attrs('for')[0]
break
self.q(css='#{}'.format(field_id)).fill(field_value)
self.q(css=f'#{field_id}').fill(field_value)
elif field_type == 'select':
self.q(css=u'select[name="{0}"] option[value="{1}"]'.format(field_name, field_value)).first.click()
self.q(css=f'select[name="{field_name}"] option[value="{field_value}"]').first.click()
def verify_field_value(self, field_name, field_value):
"""
@@ -491,7 +490,7 @@ class VideoComponentPage(VideoPage):
"""
translations_items = '.wrapper-translations-settings .list-settings-item'
language_selector = translations_items + u' select option[value="{}"]'.format(language_code)
language_selector = translations_items + f' select option[value="{language_code}"]'
self.q(css=language_selector).nth(index).click()
def upload_translation(self, transcript_name, language_code):
@@ -547,7 +546,7 @@ class VideoComponentPage(VideoPage):
"""
mime_type = 'application/x-subrip'
lang_code = '?language_code={}'.format(language_code)
lang_code = f'?language_code={language_code}'
link = [link for link in self.q(css='.download-action').attrs('href') if lang_code in link]
result, headers, content = self._get_transcript(link[0]) # lint-amnesty, pylint: disable=no-member
@@ -579,7 +578,7 @@ class VideoComponentPage(VideoPage):
As all the captions lines are exactly same so only getting partial lines will work.
"""
self.wait_for_captions() # lint-amnesty, pylint: disable=no-member
selector = u'.subtitles li:nth-child({})'
selector = '.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):
@@ -611,7 +610,7 @@ class VideoComponentPage(VideoPage):
"""
if message_type == 'status':
self.wait_for_element_visibility(CLASS_SELECTORS[message_type],
u'{} message is Visible'.format(message_type.title()))
f'{message_type.title()} message is Visible')
return self.q(css=CLASS_SELECTORS[message_type]).text[0]
@@ -657,7 +656,7 @@ class VideoComponentPage(VideoPage):
"""
Clear video url fields.
"""
script = u"""
script = """
$('{selector}')
.prop('disabled', false)
.removeClass('is-disabled')

View File

@@ -6,7 +6,6 @@ Helper functions and classes for discussion tests.
import json
from uuid import uuid4
from six.moves import range
from common.test.acceptance.fixtures import LMS_BASE_URL
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
@@ -19,7 +18,7 @@ from common.test.acceptance.pages.lms.discussion import DiscussionTabSingleThrea
from common.test.acceptance.tests.helpers import UniqueCourseTest
class BaseDiscussionMixin(object):
class BaseDiscussionMixin:
"""
A mixin containing methods common to discussion tests.
"""
@@ -31,7 +30,7 @@ class BaseDiscussionMixin(object):
self.thread_ids = []
threads = []
for i in range(thread_count):
thread_id = "test_thread_{}_{}".format(i, uuid4().hex)
thread_id = f"test_thread_{i}_{uuid4().hex}"
thread_body = "Dummy long text body." * 50
threads.append(
Thread(id=thread_id, commentable_id=self.discussion_id, body=thread_body, **thread_kwargs),
@@ -42,7 +41,7 @@ class BaseDiscussionMixin(object):
assert response.ok, 'Failed to push discussion content'
class CohortTestMixin(object):
class CohortTestMixin:
"""
Mixin for tests of cohorted courses
"""
@@ -53,7 +52,7 @@ class CohortTestMixin(object):
"""
course_fixture._update_xblock(course_fixture._course_location, { # lint-amnesty, pylint: disable=protected-access
"metadata": {
u"cohort_config": {
"cohort_config": {
"auto_cohort_groups": auto_cohort_groups or [],
"cohorted_discussions": [],
"cohorted": True,
@@ -75,7 +74,7 @@ class CohortTestMixin(object):
"""
Adds a user to the specified cohort.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + "/cohorts/{}/add".format(cohort_id) # lint-amnesty, pylint: disable=protected-access
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + f"/cohorts/{cohort_id}/add" # lint-amnesty, pylint: disable=protected-access
data = {"users": username}
course_fixture.headers['Content-type'] = 'application/x-www-form-urlencoded'
response = course_fixture.session.post(url, data=data, headers=course_fixture.headers)
@@ -85,9 +84,9 @@ class CohortTestMixin(object):
class BaseDiscussionTestCase(UniqueCourseTest, ForumsConfigMixin):
"""Base test case class for all discussions-related tests."""
def setUp(self):
super(BaseDiscussionTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.discussion_id = "test_discussion_{}".format(uuid4().hex)
self.discussion_id = f"test_discussion_{uuid4().hex}"
self.course_fixture = CourseFixture(**self.course_info)
self.course_fixture.add_children(
XBlockFixtureDesc("chapter", "Test Section").add_children(

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests related to the cohort management on the LMS Instructor Dashboard
"""
@@ -24,7 +23,7 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
"""
Set up a cohorted course
"""
super(CohortConfigurationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# create course with cohorts
self.manual_cohort_name = "ManualCohort1"

View File

@@ -25,7 +25,7 @@ from common.test.acceptance.tests.discussion.helpers import BaseDiscussionMixin,
from common.test.acceptance.tests.helpers import UniqueCourseTest
from openedx.core.lib.tests import attr
THREAD_CONTENT_WITH_LATEX = u"""Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt # lint-amnesty, pylint: disable=line-too-long
THREAD_CONTENT_WITH_LATEX = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt # lint-amnesty, pylint: disable=line-too-long
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.
@@ -96,7 +96,7 @@ class DiscussionHomePageTest(BaseDiscussionTestCase):
SEARCHED_USERNAME = "gizmo"
def setUp(self):
super(DiscussionHomePageTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
AutoAuthPage(self.browser, course_id=self.course_id).visit()
self.page = DiscussionTabHomePage(self.browser, self.course_id)
self.page.visit()
@@ -119,7 +119,7 @@ class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase, BaseDiscussionMixi
Tests for the discussion page with multiple threads
"""
def setUp(self):
super(DiscussionTabMultipleThreadTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
AutoAuthPage(self.browser, course_id=self.course_id).visit()
self.thread_count = 2
self.thread_ids = []
@@ -169,9 +169,9 @@ class DiscussionOpenClosedThreadTest(BaseDiscussionTestCase):
"""
def setUp(self):
super(DiscussionOpenClosedThreadTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.thread_id = "test_thread_{}".format(uuid4().hex)
self.thread_id = f"test_thread_{uuid4().hex}"
def setup_user(self, roles=[]): # lint-amnesty, pylint: disable=dangerous-default-value
roles_str = ','.join(roles)
@@ -323,7 +323,7 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
SEARCHED_USERNAME = "gizmo"
def setUp(self):
super(DiscussionSearchAlertTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
CourseFixture(**self.course_info).install()
# first auto auth call sets up a user that we will search for in some tests
self.searched_user_id = AutoAuthPage(

View File

@@ -4,7 +4,6 @@ Test helper functions and base classes.
import functools
import io
import json
import os
import sys
@@ -12,7 +11,6 @@ from datetime import datetime
from unittest import SkipTest, TestCase
import requests
import six
from bok_choy.javascript import js_defined
from bok_choy.page_object import XSS_INJECTION
from bok_choy.promise import EmptyPromise, Promise
@@ -24,7 +22,6 @@ from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.ui import WebDriverWait
from six.moves import range # lint-amnesty, pylint: disable=unused-import
from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
@@ -52,7 +49,7 @@ def skip_if_browser(browser):
@functools.wraps(test_function)
def wrapper(self, *args, **kwargs):
if self.browser.name == browser:
raise SkipTest(u'Skipping as this test will not work with {}'.format(browser))
raise SkipTest(f'Skipping as this test will not work with {browser}')
test_function(self, *args, **kwargs)
return wrapper
return decorator
@@ -80,7 +77,7 @@ def is_youtube_available():
'transcript': 'http://video.google.com/timedtext?lang=en&v=3_yD_cEKoCk',
}
for url in six.itervalues(youtube_api_urls):
for url in youtube_api_urls.values():
try:
response = requests.get(url, allow_redirects=False)
except requests.exceptions.ConnectionError:
@@ -96,7 +93,7 @@ def is_focused_on_element(browser, selector):
"""
Check if the focus is on the element that matches the selector.
"""
return browser.execute_script(u"return $('{}').is(':focus')".format(selector))
return browser.execute_script(f"return $('{selector}').is(':focus')")
def load_data_str(rel_path):
@@ -153,7 +150,7 @@ def disable_css_animations(page):
"""
Disable CSS3 animations, transitions, transforms.
"""
page.browser.execute_script(u"""
page.browser.execute_script("""
var id = 'no-transitions';
// if styles were already added, just do nothing.
@@ -230,7 +227,7 @@ def select_option_by_text(select_browser_query, option_text, focus_out=False):
except StaleElementReferenceException:
return False
msg = u'Selected option {}'.format(option_text)
msg = f'Selected option {option_text}'
EmptyPromise(lambda: select_option(select_browser_query, option_text), msg).fulfill()
@@ -307,8 +304,8 @@ def create_multiple_choice_xml(correct_choice=2, num_choices=4):
choices = [False for _ in range(num_choices)]
choices[correct_choice] = True
choice_names = ['choice_{}'.format(index) for index in range(num_choices)]
question_text = u'The correct answer is Choice {}'.format(correct_choice)
choice_names = [f'choice_{index}' for index in range(num_choices)]
question_text = f'The correct answer is Choice {correct_choice}'
return MultipleChoiceResponseXMLFactory().build_xml(
question_text=question_text,
@@ -342,7 +339,7 @@ class EventsTestMixin(TestCase):
Helpers and setup for running tests that evaluate events emitted
"""
def setUp(self):
super(EventsTestMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
mongo_host = 'edx.devstack.mongo' if 'BOK_CHOY_HOSTNAME' in os.environ else 'localhost'
self.event_collection = MongoClient(mongo_host)["test"]["events"]
self.start_time = datetime.now()
@@ -354,14 +351,14 @@ class AcceptanceTest(WebAppTest):
"""
def __init__(self, *args, **kwargs):
super(AcceptanceTest, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(*args, **kwargs)
# Use long messages so that failures show actual and expected values
self.longMessage = True # pylint: disable=invalid-name
def tearDown(self):
self._save_console_log()
super(AcceptanceTest, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments
super().tearDown()
def _save_console_log(self):
"""
@@ -393,10 +390,10 @@ class AcceptanceTest(WebAppTest):
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir)
log_path = os.path.join(log_dir, '{}_browser.log'.format(self.id()))
with io.open(log_path, 'w') as browser_log:
log_path = os.path.join(log_dir, f'{self.id()}_browser.log')
with open(log_path, 'w') as browser_log:
for (message, url, line_no, col_no, stack) in logs:
browser_log.write(u"{}:{}:{}: {}\n {}\n".format(
browser_log.write("{}:{}:{}: {}\n {}\n".format(
url,
line_no,
col_no,
@@ -411,7 +408,7 @@ class UniqueCourseTest(AcceptanceTest):
"""
def setUp(self):
super(UniqueCourseTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course_info = {
'org': 'test_org',
@@ -433,7 +430,7 @@ class UniqueCourseTest(AcceptanceTest):
self.course_info['run'],
deprecated=(default_store == 'draft')
)
return six.text_type(course_key)
return str(course_key)
class YouTubeConfigError(Exception):
@@ -443,14 +440,14 @@ class YouTubeConfigError(Exception):
pass # lint-amnesty, pylint: disable=unnecessary-pass
class YouTubeStubConfig(object):
class YouTubeStubConfig:
"""
Configure YouTube Stub Server.
"""
YOUTUBE_HOSTNAME = os.environ.get('BOK_CHOY_HOSTNAME', '127.0.0.1')
PORT = 9080
URL = 'http://{}:{}/'.format(YOUTUBE_HOSTNAME, PORT)
URL = f'http://{YOUTUBE_HOSTNAME}:{PORT}/'
@classmethod
def configure(cls, config):
@@ -471,7 +468,7 @@ class YouTubeStubConfig(object):
if not response.ok:
raise YouTubeConfigError(
u'YouTube Server Configuration Failed. URL {0}, Configuration Data: {1}, Status was {2}'.format(
'YouTube Server Configuration Failed. URL {}, Configuration Data: {}, Status was {}'.format(
youtube_stub_config_url, config, response.status_code))
@classmethod
@@ -489,7 +486,7 @@ class YouTubeStubConfig(object):
if not response.ok:
raise YouTubeConfigError(
u'YouTube Server Configuration Failed. URL: {0} Status was {1}'.format(
'YouTube Server Configuration Failed. URL: {} Status was {}'.format(
youtube_stub_config_url, response.status_code))
@classmethod
@@ -533,7 +530,7 @@ def create_user_partition_json(partition_id, name, description, groups, scheme="
Helper method to create user partition JSON. If scheme is not supplied, "random" is used.
"""
# All that is persisted about a scheme is its name.
class MockScheme(object):
class MockScheme:
name = scheme
return UserPartition(

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for the Account Settings page.
"""
@@ -14,9 +13,9 @@ class AccountSettingsTestMixin(EventsTestMixin, AcceptanceTest):
Mixin with helper methods to test the account settings page.
"""
CHANGE_INITIATED_EVENT_NAME = u"edx.user.settings.change_initiated"
CHANGE_INITIATED_EVENT_NAME = "edx.user.settings.change_initiated"
USER_SETTINGS_CHANGED_EVENT_NAME = 'edx.user.settings.changed'
ACCOUNT_SETTINGS_REFERER = u"/account/settings"
ACCOUNT_SETTINGS_REFERER = "/account/settings"
shard = 23

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for Student's Profile Page.
"""
@@ -17,15 +16,15 @@ class LearnerProfileTestMixin(EventsTestMixin):
Mixin with helper methods for testing learner profile pages.
"""
PRIVACY_PUBLIC = u'all_users'
PRIVACY_PRIVATE = u'private'
PRIVACY_PUBLIC = 'all_users'
PRIVACY_PRIVATE = 'private'
PUBLIC_PROFILE_FIELDS = ['username', 'country', 'language_proficiencies', 'bio']
PRIVATE_PROFILE_FIELDS = ['username']
PUBLIC_PROFILE_EDITABLE_FIELDS = ['country', 'language_proficiencies', 'bio']
USER_SETTINGS_CHANGED_EVENT_NAME = u"edx.user.settings.changed"
USER_SETTINGS_CHANGED_EVENT_NAME = "edx.user.settings.changed"
def log_in_as_unique_user(self):
"""

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS.
"""
@@ -30,7 +29,7 @@ class CourseWikiA11yTest(UniqueCourseTest):
"""
Initialize pages and install a course fixture.
"""
super(CourseWikiA11yTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# self.course_info['number'] must be shorter since we are accessing the wiki. See TNL-1751
self.course_info['number'] = self.unique_id[0:6]

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS that utilize the course home page and course outline.
"""
@@ -22,7 +21,7 @@ class CourseHomeBaseTest(UniqueCourseTest):
"""
Initialize pages and install a course fixture.
"""
super(CourseHomeBaseTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course_home_page = CourseHomePage(self.browser, self.course_id)
self.courseware_page = CoursewarePage(self.browser, self.course_id)

View File

@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for the main LMS Dashboard (aka, Student Dashboard).
"""
import six
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
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 = u'{dt:%b} {dt.day}, {dt.year}'
TEST_DATE_FORMAT = u'{dt:%b} {dt.day}, {dt.year} {dt.hour:02}:{dt.minute:02}'
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}'
class BaseLmsDashboardTestMultiple(UniqueCourseTest):
@@ -23,7 +19,7 @@ class BaseLmsDashboardTestMultiple(UniqueCourseTest):
"""
# Some parameters are provided by the parent setUp() routine, such as the following:
# self.course_id, self.course_info, self.unique_id
super(BaseLmsDashboardTestMultiple, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# Load page objects for use by the tests
self.dashboard_page = DashboardPage(self.browser)
@@ -57,12 +53,12 @@ class BaseLmsDashboardTestMultiple(UniqueCourseTest):
}
self.username = "test_{uuid}".format(uuid=self.unique_id[0:6])
self.email = "{user}@example.com".format(user=self.username)
self.email = f"{self.username}@example.com"
self.course_keys = {}
self.course_fixtures = {}
for key, value in six.iteritems(self.courses):
for key, value in self.courses.items():
course_key = generate_course_key(
value['org'],
value['number'],
@@ -77,8 +73,8 @@ class BaseLmsDashboardTestMultiple(UniqueCourseTest):
)
course_fixture.add_advanced_settings({
u"social_sharing_url": {u"value": "http://custom/course/url"},
u"cert_name_long": {u"value": value['cert_name_long']}
"social_sharing_url": {"value": "http://custom/course/url"},
"cert_name_long": {"value": value['cert_name_long']}
})
course_fixture.add_children(
XBlockFixtureDesc('chapter', 'Test Section 1').add_children(

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS Instructor Dashboard.
"""
@@ -59,7 +58,7 @@ class LMSInstructorDashboardA11yTest(BaseInstructorDashboardTest):
Instructor dashboard base accessibility test.
"""
def setUp(self):
super(LMSInstructorDashboardA11yTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course_fixture = CourseFixture(**self.course_info).install()
self.log_in_as_instructor()
self.instructor_dashboard_page = self.visit_instructor_dashboard()
@@ -82,7 +81,7 @@ class BulkEmailTest(BaseInstructorDashboardTest):
shard = 23
def setUp(self):
super(BulkEmailTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course_fixture = CourseFixture(**self.course_info).install()
self.log_in_as_instructor()
instructor_dashboard_page = self.visit_instructor_dashboard()
@@ -114,7 +113,7 @@ class AutoEnrollmentWithCSVTest(BaseInstructorDashboardTest):
"""
def setUp(self):
super(AutoEnrollmentWithCSVTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course_fixture = CourseFixture(**self.course_info).install()
self.log_in_as_instructor()
instructor_dashboard_page = self.visit_instructor_dashboard()
@@ -141,7 +140,7 @@ class CertificatesTest(BaseInstructorDashboardTest):
"""
def setUp(self):
super(CertificatesTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.test_certificate_config = {
'id': 1,
'name': 'Certificate name',
@@ -183,7 +182,7 @@ class CertificateInvalidationTest(BaseInstructorDashboardTest):
@classmethod
def setUpClass(cls):
super(CertificateInvalidationTest, cls).setUpClass()
super().setUpClass()
# Create course fixture once each test run
CourseFixture(
@@ -194,7 +193,7 @@ class CertificateInvalidationTest(BaseInstructorDashboardTest):
).install()
def setUp(self):
super(CertificateInvalidationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# set same course number as we have in fixture json
self.course_info['number'] = "335535897951379478207964576572017930000"

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
Bok choy acceptance tests for problems in the LMS
"""
@@ -20,10 +19,10 @@ class ProblemsTest(UniqueCourseTest):
"""
def setUp(self):
super(ProblemsTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.username = "test_student_{uuid}".format(uuid=self.unique_id[0:8])
self.email = "{username}@example.com".format(username=self.username)
self.email = f"{self.username}@example.com"
self.password = "keep it secret; keep it safe."
self.xqueue_grade_response = None
@@ -63,7 +62,7 @@ class ProblemsTest(UniqueCourseTest):
return XBlockFixtureDesc('sequential', 'Test Subsection')
class CAPAProblemA11yBaseTestMixin(object):
class CAPAProblemA11yBaseTestMixin:
"""Base TestCase Class to verify CAPA problem accessibility."""
def test_a11y(self):

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
Tests the "preview" selector in the LMS that allows changing between Staff, Learner, and Content Groups.
"""
@@ -24,7 +23,7 @@ class StaffViewTest(UniqueCourseTest):
EMAIL = "johndoe@example.com"
def setUp(self):
super(StaffViewTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.courseware_page = CoursewarePage(self.browser, self.course_id)
@@ -60,11 +59,11 @@ class CourseWithContentGroupsTest(StaffViewTest):
"""
def setUp(self):
super(CourseWithContentGroupsTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# pylint: disable=protected-access
self.course_fixture._update_xblock(self.course_fixture._course_location, {
"metadata": {
u"user_partitions": [
"user_partitions": [
create_user_partition_json(
MINIMUM_STATIC_PARTITION_ID,
'Configuration alpha,beta',

View File

@@ -8,7 +8,6 @@ import textwrap
from abc import ABCMeta, abstractmethod
import ddt
import six
from bok_choy.promise import BrokenPromise
from capa.tests.response_xml_factory import (
@@ -49,7 +48,7 @@ class ProblemTypeTestBaseMeta(ABCMeta):
]
for required_attr in required_attrs:
msg = (u'{} is a required attribute for {}').format(
msg = ('{} is a required attribute for {}').format(
required_attr, str(cls)
)
@@ -62,7 +61,7 @@ class ProblemTypeTestBaseMeta(ABCMeta):
return obj
class ProblemTypeTestBase(six.with_metaclass(ProblemTypeTestBaseMeta, ProblemsTest, EventsTestMixin)):
class ProblemTypeTestBase(ProblemsTest, EventsTestMixin, metaclass=ProblemTypeTestBaseMeta):
"""
Base class for testing assesment problem types in bok choy.
@@ -96,7 +95,7 @@ class ProblemTypeTestBase(six.with_metaclass(ProblemTypeTestBaseMeta, ProblemsTe
"""
Visits courseware_page and defines self.problem_page.
"""
super(ProblemTypeTestBase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.courseware_page.visit()
self.problem_page = ProblemPage(self.browser)
@@ -123,7 +122,7 @@ class ProblemTypeTestBase(six.with_metaclass(ProblemTypeTestBaseMeta, ProblemsTe
Args:
status: one of ("correct", "incorrect", "unanswered", "submitted")
"""
msg = u"Wait for status to be {}".format(status)
msg = f"Wait for status to be {status}"
selector = ', '.join(self.status_indicators[status])
self.problem_page.wait_for_element_visibility(selector, msg)
@@ -154,7 +153,7 @@ class ProblemTypeTestBase(six.with_metaclass(ProblemTypeTestBaseMeta, ProblemsTe
raise NotImplementedError()
class ProblemTypeA11yTestMixin(object):
class ProblemTypeA11yTestMixin:
"""
Shared a11y tests for all problem types.
"""
@@ -215,7 +214,7 @@ class AnnotationProblemTypeBase(ProblemTypeTestBase):
"""
Additional setup for AnnotationProblemTypeBase
"""
super(AnnotationProblemTypeBase, self).setUp(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().setUp(*args, **kwargs)
self.problem_page.a11y_audit.config.set_rules({
"ignore": [
@@ -238,7 +237,7 @@ class AnnotationProblemTypeBase(ProblemTypeTestBase):
self.problem_page.q(css='div.problem textarea.comment').fill(answer)
self.problem_page.q(
css='div.problem span.tag'.format(choice=choice)
css='div.problem span.tag'
).nth(choice).click()
@@ -902,7 +901,7 @@ class ChoiceTextProblemTypeTestBase(ProblemTypeTestBase):
Selects the nth (where n == input_num) choice of the problem.
"""
self.problem_page.q(
css=u'div.problem input.ctinput[type="{}"]'.format(self.choice_type)
css=f'div.problem input.ctinput[type="{self.choice_type}"]'
).nth(input_num).click()
def _fill_input_text(self, value, input_num):
@@ -974,7 +973,7 @@ class RadioTextProblemTypeBase(ChoiceTextProblemTypeTestBase):
"""
Additional setup for RadioTextProblemTypeBase
"""
super(RadioTextProblemTypeBase, self).setUp(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().setUp(*args, **kwargs)
self.problem_page.a11y_audit.config.set_rules({
"ignore": [
@@ -1037,7 +1036,7 @@ class CheckboxTextProblemTypeBase(ChoiceTextProblemTypeTestBase):
"""
Additional setup for CheckboxTextProblemTypeBase
"""
super(CheckboxTextProblemTypeBase, self).setUp(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().setUp(*args, **kwargs)
self.problem_page.a11y_audit.config.set_rules({
"ignore": [

View File

@@ -20,7 +20,7 @@ from openedx.core.djangoapps.catalog.tests.factories import (
class ProgramPageBase(ProgramsConfigMixin, CatalogIntegrationMixin, UniqueCourseTest):
"""Base class used for program listing page tests."""
def setUp(self):
super(ProgramPageBase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.set_programs_api_configuration(is_enabled=True)
@@ -79,7 +79,7 @@ class ProgramListingPageA11yTest(ProgramPageBase):
a11y = True
def setUp(self):
super(ProgramListingPageA11yTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.listing_page = ProgramListingPage(self.browser)
@@ -128,7 +128,7 @@ class ProgramDetailsPageA11yTest(ProgramPageBase):
a11y = True
def setUp(self):
super(ProgramDetailsPageA11yTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.details_page = ProgramDetailsPage(self.browser)

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS that utilize the
progress page.
@@ -7,7 +6,6 @@ progress page.
from contextlib import contextmanager
from six.moves import range
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
from ...pages.common.logout import LogoutPage
@@ -36,7 +34,7 @@ class ProgressPageBaseTest(UniqueCourseTest):
PROBLEM_NAME_2 = 'Test Problem 2'
def setUp(self):
super(ProgressPageBaseTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.courseware_page = CoursewarePage(self.browser, self.course_id)
self.problem_page = ProblemPage(self.browser)
self.progress_page = ProgressPage(self.browser, self.course_id)
@@ -89,7 +87,7 @@ class ProgressPageBaseTest(UniqueCourseTest):
Submit the given choice for the problem.
"""
self.courseware_page.go_to_sequential_position(1)
self.problem_page.click_choice('choice_choice_{}'.format(choice))
self.problem_page.click_choice(f'choice_choice_{choice}')
self.problem_page.click_submit()
def _get_section_score(self):
@@ -128,7 +126,7 @@ class SubsectionGradingPolicyBase(ProgressPageBaseTest):
the progress page
"""
def setUp(self):
super(SubsectionGradingPolicyBase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self._set_policy_for_subsection("Homework", 0)
self._set_policy_for_subsection("Lab", 1)
@@ -194,46 +192,46 @@ class SubsectionGradingPolicyA11yTest(SubsectionGradingPolicyBase):
assert ['0%', 'true'] == self.progress_page.y_tick_label(1)
assert ['Pass 50%', 'true'] == self.progress_page.y_tick_label(2)
# Verify x-Axis labels and sr-text
self._check_tick_text(0, [u'Homework 1 - Test Subsection 1 - 50% (1/2)'], u'HW 01')
self._check_tick_text(0, ['Homework 1 - Test Subsection 1 - 50% (1/2)'], 'HW 01')
# Homeworks 2-10 are checked in the for loop below.
self._check_tick_text(
10,
[u'Homework 11 Unreleased - 0% (?/?)', u'The lowest 2 Homework scores are dropped.'],
u'HW 11'
['Homework 11 Unreleased - 0% (?/?)', 'The lowest 2 Homework scores are dropped.'],
'HW 11'
)
self._check_tick_text(
11,
[u'Homework 12 Unreleased - 0% (?/?)', u'The lowest 2 Homework scores are dropped.'],
u'HW 12'
['Homework 12 Unreleased - 0% (?/?)', 'The lowest 2 Homework scores are dropped.'],
'HW 12'
)
self._check_tick_text(12, [u'Homework Average = 5%'], u'HW Avg')
self._check_tick_text(13, [u'Lab 1 - Lab Subsection - 100% (1/1)'], u'Lab 01')
self._check_tick_text(12, ['Homework Average = 5%'], 'HW Avg')
self._check_tick_text(13, ['Lab 1 - Lab Subsection - 100% (1/1)'], 'Lab 01')
# Labs 2-10 are checked in the for loop below.
self._check_tick_text(
23,
[u'Lab 11 Unreleased - 0% (?/?)', u'The lowest 2 Lab scores are dropped.'],
u'Lab 11'
['Lab 11 Unreleased - 0% (?/?)', 'The lowest 2 Lab scores are dropped.'],
'Lab 11'
)
self._check_tick_text(
24,
[u'Lab 12 Unreleased - 0% (?/?)', u'The lowest 2 Lab scores are dropped.'],
u'Lab 12'
['Lab 12 Unreleased - 0% (?/?)', 'The lowest 2 Lab scores are dropped.'],
'Lab 12'
)
self._check_tick_text(25, [u'Lab Average = 10%'], u'Lab Avg')
self._check_tick_text(26, [u'Midterm Exam = 0%'], u'Midterm')
self._check_tick_text(27, [u'Final Exam = 0%'], u'Final')
self._check_tick_text(25, ['Lab Average = 10%'], 'Lab Avg')
self._check_tick_text(26, ['Midterm Exam = 0%'], 'Midterm')
self._check_tick_text(27, ['Final Exam = 0%'], 'Final')
self._check_tick_text(
28,
[u'Homework = 0.75% of a possible 15.00%', u'Lab = 1.50% of a possible 15.00%'],
u'Total',
['Homework = 0.75% of a possible 15.00%', 'Lab = 1.50% of a possible 15.00%'],
'Total',
False # The label "Total" should NOT be aria-hidden
)
@@ -242,13 +240,13 @@ class SubsectionGradingPolicyA11yTest(SubsectionGradingPolicyBase):
for i in range(1, 10):
self._check_tick_text(
i,
[u'Homework {index} Unreleased - 0% (?/?)'.format(index=i + 1)],
u'HW 0{index}'.format(index=i + 1) if i < 9 else u'HW {index}'.format(index=i + 1)
['Homework {index} Unreleased - 0% (?/?)'.format(index=i + 1)],
'HW 0{index}'.format(index=i + 1) if i < 9 else 'HW {index}'.format(index=i + 1)
)
self._check_tick_text(
i + 13,
[u'Lab {index} Unreleased - 0% (?/?)'.format(index=i + 1)],
u'Lab 0{index}'.format(index=i + 1) if i < 9 else u'Lab {index}'.format(index=i + 1)
['Lab {index} Unreleased - 0% (?/?)'.format(index=i + 1)],
'Lab 0{index}'.format(index=i + 1) if i < 9 else 'Lab {index}'.format(index=i + 1)
)
# Verify the overall score. The first element in the array is the sr-only text, and the

View File

@@ -22,7 +22,7 @@ class StudioCourseTest(UniqueCourseTest):
"""
Install a course with no content using a fixture.
"""
super(StudioCourseTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.test_xss = test_xss
self.install_course_fixture(is_staff)
@@ -38,7 +38,7 @@ class StudioCourseTest(UniqueCourseTest):
)
if self.test_xss:
xss_injected_unique_id = XSS_INJECTION + self.unique_id
test_improper_escaping = {u"value": xss_injected_unique_id}
test_improper_escaping = {"value": xss_injected_unique_id}
self.course_fixture.add_advanced_settings({
"advertised_start": test_improper_escaping,
"info_sidebar_name": test_improper_escaping,
@@ -90,7 +90,7 @@ class ContainerBase(StudioCourseTest):
Create a unique identifier for the course used in this test.
"""
# Ensure that the superclass sets up
super(ContainerBase, self).setUp(is_staff=is_staff) # lint-amnesty, pylint: disable=super-with-arguments
super().setUp(is_staff=is_staff)
self.outline = CourseOutlinePage(
self.browser,
@@ -142,11 +142,11 @@ class StudioLibraryTest(AcceptanceTest):
"""
Install a library with no content using a fixture.
"""
super(StudioLibraryTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
fixture = LibraryFixture(
'test_org',
self.unique_id,
u'Test Library {}'.format(self.unique_id),
f'Test Library {self.unique_id}',
)
self.populate_library_fixture(fixture)
fixture.install()

View File

@@ -1,11 +1,10 @@
# coding: utf-8
"""
Acceptance tests for Studio's Setting pages
"""
import os
from mock import patch
from unittest.mock import patch
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
from common.test.acceptance.pages.studio.overview import CourseOutlinePage
@@ -22,7 +21,7 @@ class StudioSettingsA11yTest(StudioCourseTest):
"""
def setUp(self): # pylint: disable=arguments-differ
super(StudioSettingsA11yTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.settings_page = SettingsPage(self.browser, self.course_info['org'], self.course_info['number'],
self.course_info['run'])
@@ -67,7 +66,7 @@ class StudioSubsectionSettingsA11yTest(StudioCourseTest):
browser = 'firefox'
with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}):
super(StudioSubsectionSettingsA11yTest, self).setUp(is_staff=True) # lint-amnesty, pylint: disable=super-with-arguments
super().setUp(is_staff=True)
self.course_outline = CourseOutlinePage(
self.browser,

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
Acceptance tests for Video.
"""
@@ -7,7 +5,7 @@ Acceptance tests for Video.
import os
from unittest import skipIf
from mock import patch
from unittest.mock import patch
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
@@ -25,17 +23,17 @@ VIDEO_SOURCE_PORT = 8777
VIDEO_HOSTNAME = os.environ.get('BOK_CHOY_HOSTNAME', 'localhost')
HTML5_SOURCES = [
'http://{}:{}/gizmo.mp4'.format(VIDEO_HOSTNAME, VIDEO_SOURCE_PORT),
'http://{}:{}/gizmo.webm'.format(VIDEO_HOSTNAME, VIDEO_SOURCE_PORT),
'http://{}:{}/gizmo.ogv'.format(VIDEO_HOSTNAME, VIDEO_SOURCE_PORT),
f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.mp4',
f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.webm',
f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.ogv',
]
HTML5_SOURCES_INCORRECT = [
'http://{}:{}/gizmo.mp99'.format(VIDEO_HOSTNAME, VIDEO_SOURCE_PORT),
f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/gizmo.mp99',
]
HLS_SOURCES = [
'http://{}:{}/hls/history.m3u8'.format(VIDEO_HOSTNAME, VIDEO_SOURCE_PORT),
f'http://{VIDEO_HOSTNAME}:{VIDEO_SOURCE_PORT}/hls/history.m3u8',
]
@@ -50,7 +48,7 @@ class VideoBaseTest(UniqueCourseTest):
"""
Initialization of pages and course fixture for video tests
"""
super(VideoBaseTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.longMessage = True
self.video = VideoPage(self.browser)
@@ -125,7 +123,7 @@ class VideoBaseTest(UniqueCourseTest):
:param vertical_index: index for the vertical display name
:return: XBlockFixtureDesc
"""
xblock_course_vertical = XBlockFixtureDesc('vertical', u'Test Vertical-{0}'.format(vertical_index))
xblock_course_vertical = XBlockFixtureDesc('vertical', f'Test Vertical-{vertical_index}')
for video in vertical_contents:
xblock_course_vertical.add_children(
@@ -229,7 +227,7 @@ class LMSVideoBlockA11yTest(VideoBaseTest):
browser = 'firefox'
with patch.dict(os.environ, {'SELENIUM_BROWSER': browser}):
super(LMSVideoBlockA11yTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
def test_video_player_a11y(self):
# load transcripts so we can test skipping to

View File

@@ -6,6 +6,7 @@ General testing utilities.
import functools
import sys
from contextlib import contextmanager
from django.dispatch import Signal
from markupsafe import escape
from mock import Mock, patch
@@ -19,7 +20,7 @@ def nostderr():
"""
savestderr = sys.stderr
class Devnull(object):
class Devnull:
""" /dev/null incarnation as output-stream-like object """
def write(self, _):
""" Write method - just does nothing"""
@@ -32,7 +33,7 @@ def nostderr():
sys.stderr = savestderr
class XssTestMixin(object):
class XssTestMixin:
"""
Mixin for testing XSS vulnerabilities.
"""
@@ -60,7 +61,7 @@ def disable_signal(module, signal):
return patch.object(module, signal, new=Signal())
class MockSignalHandlerMixin(object):
class MockSignalHandlerMixin:
"""Mixin for testing sending of signals."""
@contextmanager
@@ -114,12 +115,12 @@ def skip_signal(signal, **kwargs):
signal.connect(**kwargs)
class MockS3BotoMixin(object):
class MockS3BotoMixin:
"""
TestCase mixin that mocks the S3BotoStorage save method and s3 connection.
"""
def setUp(self):
super(MockS3BotoMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self._mocked_connection = patch('boto.connect_s3', return_value=Mock())
self.mocked_connection = self._mocked_connection.start()
@@ -129,16 +130,16 @@ class MockS3BotoMixin(object):
def tearDown(self):
self._mocked_connection.stop()
self.patcher.stop()
super(MockS3BotoMixin, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments
super().tearDown()
class reprwrapper(object):
class reprwrapper:
"""
Wrapper class for functions that need a normalized string representation.
"""
def __init__(self, func):
self._func = func
self.repr = u'Func: {}'.format(func.__name__)
self.repr = f'Func: {func.__name__}'
functools.update_wrapper(self, func)
def __call__(self, *args, **kw):