Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

chore: remove bok-choy usage #1527

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/docker-compose-github.yml
Original file line number Diff line number Diff line change
@@ -33,7 +33,6 @@ services:
ENABLE_COURSE_LIST_FILTERS: "True"
ENABLE_COURSE_LIST_PASSING: "True"
SELENIUM_BROWSER: "firefox"
BOKCHOY_HEADLESS: "true"
depends_on:
- "es"
- "analyticsapi"
13 changes: 0 additions & 13 deletions a11y_tests/mixins.py

This file was deleted.

50 changes: 0 additions & 50 deletions a11y_tests/pages.py

This file was deleted.

42 changes: 0 additions & 42 deletions a11y_tests/test_course_enrollment_demographics_axs.py

This file was deleted.

367 changes: 0 additions & 367 deletions acceptance_tests/mixins.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,12 @@
import datetime
import locale
from unittest import skip

from analyticsclient.client import Client
from bok_choy.promise import EmptyPromise
from selenium.webdriver.common.keys import Keys

from acceptance_tests import (
API_AUTH_TOKEN,
API_SERVER_URL,
COURSE_API_KEY,
COURSE_API_URL,
DASHBOARD_FEEDBACK_EMAIL,
DASHBOARD_SERVER_URL,
DOC_BASE_URL,
ENABLE_AUTO_AUTH,
ENABLE_COURSE_API,
LMS_PASSWORD,
LMS_USERNAME,
SOAPBOX_GLOBAL_MESSAGE,
SOAPBOX_INACTIVE_MESSAGE,
SOAPBOX_SINGLE_PAGE_MESSAGE,
SOAPBOX_SINGLE_PAGE_PATH,
SUPPORT_EMAIL,
)
from acceptance_tests.pages import LMSLoginPage
from common.clients import CourseStructureApiClient

MAX_SUMMARY_POINT_VALUE_LENGTH = 13


class AnalyticsApiClientMixin:
analytics_api_client = None

def setUp(self):
super().setUp()

api_url = API_SERVER_URL
auth_token = API_AUTH_TOKEN
self.analytics_api_client = Client(api_url, auth_token=auth_token, timeout=10)


class CourseApiMixin:
course_api_client = None
@@ -89,19 +56,6 @@ def assertValidFeedbackLink(self, selector):
element = self.page.q(css=selector)
self.assertEqual(element.text[0], DASHBOARD_FEEDBACK_EMAIL)

def fulfill_loading_promise(self, css_selector):
"""
Ensure the info contained by `css_selector` is loaded via AJAX.
Arguments
css_selector (string) -- CSS selector of the parent element that will contain the loading message.
"""

EmptyPromise(
lambda: 'Loading...' not in self.page.q(css=css_selector + ' .loading-container').text,
"Loading finished."
).fulfill()

def assertTable(self, table_selector, columns, download_selector=None):
# Ensure the table is loaded via AJAX
self.fulfill_loading_promise(table_selector)
@@ -134,135 +88,6 @@ def assertRowTextEquals(self, cols, expected_texts):
self.assertListEqual(actual, expected_texts)


class PageTestMixin:
def test_page(self):
pass


class FooterMixin(AssertMixin):
footer_selector = "footer[class=footer]"

def _test_footer(self):
# make sure we have the footer
element = self.page.q(css=self.footer_selector)
self.assertTrue(element.present)

def test_page(self):
super().test_page()
self._test_footer()


class FooterLegalMixin(FooterMixin):
def _test_footer(self):
super()._test_footer()

# Verify the terms of service link is present
selector = self.footer_selector + " a[data-role=tos]"
element = self.page.q(css=selector)
self.assertTrue(element.present)
self.assertEqual(element.text[0], 'Terms of Service')

# Verify the privacy policy link is present
selector = self.footer_selector + " a[data-role=privacy-policy]"
element = self.page.q(css=selector)
self.assertTrue(element.present)
self.assertEqual(element.text[0], 'Privacy Policy')


class FooterFeedbackMixin(FooterMixin):
def _test_footer(self):
super()._test_footer()
# check that we have an email
self.assertValidFeedbackLink(self.footer_selector + " a[class=feedback-email]")

# check that we have the support email
selector = self.footer_selector + " a[class=support-email]"
self.assertHrefEqual(selector, SUPPORT_EMAIL)


class PrimaryNavMixin(CourseApiMixin):
# set to True if the URL fragement should be checked when testing the skip link
test_skip_link_url = True

def _test_user_menu(self):
"""
Verify the user menu functions properly.
"""
element = self.page.q(css='.active-user.dropdown-toggle')
self.assertTrue(element.present)
self.assertEqual(element.attrs('aria-expanded')[0], 'false')

element.click()

# Ensure the menu is actually visible onscreen
element = self.page.q(css='ul.dropdown-menu.active-user-nav')
self.assertTrue(element.visible)

def _test_active_course(self):
""" Ensure the active course item contains either the course name or ID. """
course_id = getattr(self.page, 'course_id', None)

if not course_id:
return skip('Page has no course_id attribute set.')

element = self.page.q(css='.navbar-header .active-course-name')
self.assertTrue(element.visible)

course_name = self.get_course_name_or_id(course_id)
self.assertEqual(element.text[0], course_name)

return None

def _test_skip_link(self, test_url):
active_element = self.driver.switch_to.active_element
skip_link = self.page.q(css='.skip-link').results[0]
skip_link_ref = '#' + skip_link.get_attribute('href').split('#')[-1]
target_element = self.page.q(css=skip_link_ref)
self.assertEqual(len(target_element), 1)

active_element.send_keys(Keys.TAB)
active_element = self.driver.switch_to.active_element
active_element.send_keys(Keys.ENTER)

if test_url:
url_hash = self.driver.execute_script('return window.location.hash;')
self.assertEqual(url_hash, skip_link_ref)

def test_page(self):
self._test_skip_link(self.test_skip_link_url)
self._test_user_menu()
self._test_active_course()


class LoginMixin:
def setUp(self):
super().setUp()
self.lms_login_page = LMSLoginPage(self.browser)

def login(self):
if ENABLE_AUTO_AUTH:
self.login_with_auto_auth()
else:
self.login_with_lms()

def login_with_auto_auth(self):
url = f'{DASHBOARD_SERVER_URL}/test/auto_auth/'
self.browser.get(url)

def login_with_lms(self):
""" Visit LMS and login. """

# Note: We use Selenium directly here (as opposed to Bok Choy) to avoid issues with promises being broken.
self.lms_login_page.browser.get(self.lms_login_page.url)
self.lms_login_page.login(LMS_USERNAME, LMS_PASSWORD)


class LogoutMixin:
def logout(self):
url = f'{DASHBOARD_SERVER_URL}/logout/'
self.browser.get(url)


class ContextSensitiveHelpMixin:
help_path = 'index.html'

@@ -273,195 +98,3 @@ def help_url(self):
def test_page(self):
# Validate the help link
self.assertHrefEqual('#help', self.help_url)


class SoapboxMessagesMixin:
soapbox_selector = "div[class=announcement-container]"

def _test_soapbox_messages(self):
# make sure we have the correct soapbox messages displayed
element = self.page.q(css=self.soapbox_selector)
self.assertTrue(element.present)
self.assertTrue(SOAPBOX_GLOBAL_MESSAGE in element.text)
self.assertFalse(SOAPBOX_INACTIVE_MESSAGE in element.text)

if self.page.path == SOAPBOX_SINGLE_PAGE_PATH:
element = self.page.q(css=self.soapbox_selector)
self.assertTrue(SOAPBOX_SINGLE_PAGE_MESSAGE in element.text)

def test_page(self):
super().test_page()
self._test_soapbox_messages()


class AnalyticsDashboardWebAppTestMixin(FooterMixin, PrimaryNavMixin, ContextSensitiveHelpMixin, AssertMixin,
LoginMixin, SoapboxMessagesMixin):
def test_page(self):
self.login()
self.page.visit()
PrimaryNavMixin.test_page(self)
ContextSensitiveHelpMixin.test_page(self)

def date_strip_leading_zeroes(self, s):
"""
Remove the leading 0 on formatted date strings.
:param s: Date formatted as string
"""
return s.replace(' 0', ' ')

@staticmethod
def format_number(value):
""" Format the given value for the current locale (e.g. include decimal separator). """
if isinstance(value, int):
return locale.format("%d", value, grouping=True) # pylint: disable=deprecated-method
return locale.format("%.1f", value, grouping=True) # pylint: disable=deprecated-method

def assertSummaryPointValueEquals(self, data_selector, value):
"""
Compares the value in the summary card the "value" argument.
Arguments:
data_selector (String): Attribute selector (ex. data-stat-type=current_enrollment)
tip_text (String): expected value
"""
# Account for Django truncation
if len(value) > MAX_SUMMARY_POINT_VALUE_LENGTH:
value = value[:(MAX_SUMMARY_POINT_VALUE_LENGTH - 1)] + '…'

element = self.page.q(css=f"div[{data_selector}] .summary-point-number")
self.assertTrue(element.present)
self.assertEqual(element.text[0], value)

def assertSummaryTooltipEquals(self, data_selector, tip_text):
"""
Compares the tooltip in the summary card the "tip_text" argument.
Arguments:
data_selector (String): Attribute selector (ex. data-stat-type=current_enrollment)
tip_text (String): expected text
"""
help_selector = f"div[{data_selector}] .summary-point-help"
element = self.page.q(css=help_selector)
self.assertTrue(element.present)

# check to see if
screen_reader_element = self.page.q(css=help_selector + " > span[class=sr-only]")
self.assertTrue(screen_reader_element.present)
self.assertEqual(screen_reader_element.text[0], tip_text)

tooltip_element = self.page.q(css=help_selector + " > span[data-toggle='tooltip']")
self.assertTrue(tooltip_element.present)
# the context of title gets move to "data-original-title"
self.assertEqual(tooltip_element[0].get_attribute('data-original-title'), tip_text)

def assertMetricTileValid(self, stat_type, value, tooltip):
selector = 'data-stat-type=%s' % stat_type
if value is not None:
self.assertSummaryPointValueEquals(selector, self.format_number(value))
self.assertSummaryTooltipEquals(selector, tooltip)


class CoursePageTestsMixin(AnalyticsApiClientMixin, FooterLegalMixin, FooterFeedbackMixin,
AnalyticsDashboardWebAppTestMixin):
""" Mixin for common course page assertions and tests. """

DASHBOARD_DATE_FORMAT = '%B %d, %Y'
page = None

def setUp(self):
super().setUp()
self.api_date_format = self.analytics_api_client.DATE_FORMAT
self.api_datetime_format = self.analytics_api_client.DATETIME_FORMAT

def assertDataUpdateMessageEquals(self, value):
element = self.page.q(css='div.data-update-message')
self.assertEqual(element.text[0], value)

def format_time_as_dashboard(self, value):
return value.strftime(self.DASHBOARD_DATE_FORMAT)

def _format_last_updated_time(self, d):
return d.strftime('%I:%M %p').lstrip('0')

def format_last_updated_date_and_time(self, d):
return {'update_date': d.strftime(self.DASHBOARD_DATE_FORMAT), 'update_time': self._format_last_updated_time(d)}

def build_display_percentage(self, count, total, zero_percent_default='0.0%'):
if total and count:
percent = count / float(total) * 100.0
return f'{percent:.1f}%' if percent >= 1.0 else '< 1%'
return zero_percent_default

def _get_data_update_message(self):
raise NotImplementedError

def _test_data_update_message(self):
""" Validate the content in the data update message container. """

message = self._get_data_update_message()
self.assertDataUpdateMessageEquals(message)

def _test_course_home_nav(self):
element = self.page.q(css='.course-label')
self.assertEqual(element.text[0], 'Course Home')

def test_page(self):
"""
Primary test method.
Sub-classes should override this method and add additional tests. Sub-classes can safely assume that, if tests
pass, execution of this parent method will leave the browser on the page being tested.
:return:
"""
super().test_page()
self._test_data_update_message()
self._test_course_home_nav()


class CourseDemographicsPageTestsMixin(CoursePageTestsMixin):
demographic_type = None
data_information_message = 'All above demographic data was self-reported at the time of registration.'
chart_selector = '#enrollment-chart-view'
table_section_selector = 'div[data-role=enrollment-table]'
table_download_selector = 'a[data-role=enrollment-csv]'
table_columns = None
demographic_data = None

def test_page(self):
super().test_page()
self._test_data_information_message()
self._test_chart()
self._test_table()

def _test_chart(self):
self.fulfill_loading_promise(self.chart_selector)
self.assertElementHasContent(self.chart_selector)

def _test_table(self):
self.assertTable(self.table_section_selector, self.table_columns, self.table_download_selector)

rows = self.page.browser.find_elements_by_css_selector(f'{self.table_section_selector} tbody tr')
self.assertGreater(len(rows), 0)
sum_count = 0.0
if self.demographic_data and 'count' in self.demographic_data[0]:
sum_count = float(sum([datum['count'] for datum in self.demographic_data]))

for i, row in enumerate(rows):
columns = row.find_elements_by_css_selector('td')
self._test_table_row(self.demographic_data[i], columns, sum_count)

def _test_table_row(self, datum, column, sum_count):
raise NotImplementedError

def _test_data_information_message(self):
element = self.page.q(css='div.data-information-message')
self.assertEqual(element.text[0], self.data_information_message)

def _get_data_update_message(self):
return self._build_data_update_message(self.course.enrollment(self.demographic_type))

def _build_data_update_message(self, api_response):
current_data = api_response[0]
last_updated = datetime.datetime.strptime(current_data['created'], self.api_datetime_format)
return 'Demographic learner data was last updated %(update_date)s at %(update_time)s UTC.' % \
self.format_last_updated_date_and_time(last_updated)
351 changes: 0 additions & 351 deletions acceptance_tests/pages.py

This file was deleted.

30 changes: 0 additions & 30 deletions acceptance_tests/test_auth.py

This file was deleted.

140 changes: 0 additions & 140 deletions acceptance_tests/test_course_detail.py

This file was deleted.

273 changes: 0 additions & 273 deletions acceptance_tests/test_course_engagement.py

This file was deleted.

211 changes: 0 additions & 211 deletions acceptance_tests/test_course_enrollment.py

This file was deleted.

221 changes: 0 additions & 221 deletions acceptance_tests/test_course_enrollment_demographics.py

This file was deleted.

335 changes: 0 additions & 335 deletions acceptance_tests/test_course_index.py

This file was deleted.

438 changes: 0 additions & 438 deletions acceptance_tests/test_course_performance.py

This file was deleted.

38 changes: 0 additions & 38 deletions acceptance_tests/test_error_pages.py

This file was deleted.

81 changes: 0 additions & 81 deletions acceptance_tests/test_landing.py

This file was deleted.

4 changes: 2 additions & 2 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -133,7 +133,7 @@ lxml==4.9.3
# via edx-i18n-tools
markupsafe==2.1.3
# via jinja2
newrelic==9.1.1
newrelic==9.1.2
# via edx-django-utils
oauthlib==3.2.2
# via
@@ -145,7 +145,7 @@ path==16.7.1
# path-py
path-py==12.5.0
# via -r requirements/base.in
pbr==5.11.1
pbr==6.0.0
# via stevedore
polib==1.2.0
# via edx-i18n-tools
7 changes: 4 additions & 3 deletions requirements/common_constraints.txt
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ elasticsearch<7.14.0
# django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected
django-simple-history==3.0.0

# tox>4.0.0 isn't yet compatible with many tox plugins, causing CI failures in almost all repos.
# Details can be found in this discussion: https://github.com/tox-dev/tox/discussions/1810
tox<4.0.0
# virtualenv latest version requires platformdirs<4.0 which conflicts with tox>4.0 version
# This constraint can be removed once the issue
# https://github.com/pypa/virtualenv/issues/2666 gets resolved
platformdirs<4.0
4 changes: 2 additions & 2 deletions requirements/doc.txt
Original file line number Diff line number Diff line change
@@ -176,7 +176,7 @@ markupsafe==2.1.3
# via
# -r requirements/base.txt
# jinja2
newrelic==9.1.1
newrelic==9.1.2
# via
# -r requirements/base.txt
# edx-django-utils
@@ -196,7 +196,7 @@ path==16.7.1
# path-py
path-py==12.5.0
# via -r requirements/base.txt
pbr==5.11.1
pbr==6.0.0
# via
# -r requirements/base.txt
# stevedore
10 changes: 0 additions & 10 deletions requirements/local.txt
Original file line number Diff line number Diff line change
@@ -20,8 +20,6 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/test.txt
# django
bok-choy==2.0.2
# via -r requirements/test.txt
build==1.0.3
# via
# -r requirements/pip_tools.txt
@@ -210,10 +208,6 @@ jinja2==3.1.2
# via
# -r requirements/test.txt
# code-annotations
lazy==1.6
# via
# -r requirements/test.txt
# bok-choy
libsass==0.22.0
# via -r requirements/test.txt
logutils==0.3.5
@@ -375,10 +369,6 @@ requests-oauthlib==1.3.1
# via
# -r requirements/test.txt
# social-auth-core
selenium==3.141.0
# via
# -r requirements/test.txt
# bok-choy
semantic-version==2.10.0
# via
# -r requirements/test.txt
2 changes: 0 additions & 2 deletions requirements/test.in
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
-r base.txt

astroid
bok-choy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need selenium in this repo or should that be removed as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not, i searched and removed it. thanks for pointing this.

coverage
ddt
django-dynamic-fixture
@@ -16,5 +15,4 @@ edx-lint
pytest
pytest-cov
pytest-django
selenium
testfixtures
24 changes: 9 additions & 15 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -20,8 +20,6 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/base.txt
# django
bok-choy==2.0.2
# via -r requirements/test.in
certifi==2023.7.22
# via
# -r requirements/base.txt
@@ -58,7 +56,7 @@ cryptography==41.0.5
# -r requirements/base.txt
# pyjwt
# social-auth-core
ddt==1.6.0
ddt==1.7.0
# via -r requirements/test.in
defusedxml==0.8.0rc2
# via
@@ -67,6 +65,7 @@ defusedxml==0.8.0rc2
# social-auth-core
dill==0.3.7
# via pylint
django==4.2.7
# via
# -c requirements/constraints.txt
# -r requirements/base.txt
@@ -176,8 +175,6 @@ jinja2==3.1.2
# via
# -r requirements/base.txt
# code-annotations
lazy==1.6
# via bok-choy
libsass==0.22.0
# via -r requirements/base.txt
logutils==0.3.5
@@ -192,7 +189,7 @@ markupsafe==2.1.3
# jinja2
mccabe==0.7.0
# via pylint
newrelic==9.1.1
newrelic==9.1.2
# via
# -r requirements/base.txt
# edx-django-utils
@@ -210,12 +207,14 @@ path==16.7.1
# path-py
path-py==12.5.0
# via -r requirements/base.txt
pbr==5.11.1
pbr==6.0.0
# via
# -r requirements/base.txt
# stevedore
platformdirs==3.11.0
# via pylint
# via
# -c requirements/common_constraints.txt
# pylint
pluggy==1.3.0
# via pytest
polib==1.2.0
@@ -273,7 +272,7 @@ pytest==7.4.3
# pytest-django
pytest-cov==4.1.0
# via -r requirements/test.in
pytest-django==4.6.0
pytest-django==4.7.0
# via -r requirements/test.in
python-slugify==8.0.1
# via
@@ -306,10 +305,6 @@ requests-oauthlib==1.3.1
# via
# -r requirements/base.txt
# social-auth-core
selenium==3.141.0
# via
# -r requirements/test.in
# bok-choy
semantic-version==2.10.0
# via
# -r requirements/base.txt
@@ -357,7 +352,7 @@ tomli==2.0.1
# coverage
# pylint
# pytest
tomlkit==0.12.2
tomlkit==0.12.3
# via pylint
typing-extensions==4.8.0
# via
@@ -372,4 +367,3 @@ urllib3==1.26.18
# -c requirements/constraints.txt
# -r requirements/base.txt
# requests
# selenium
14 changes: 10 additions & 4 deletions requirements/tox.txt
Original file line number Diff line number Diff line change
@@ -11,20 +11,26 @@ filelock==3.13.1
# tox
# virtualenv
packaging==23.2
# via tox
# via
# pyproject-api
# tox
platformdirs==3.11.0
# via virtualenv
# via
# -c requirements/common_constraints.txt
# tox
# virtualenv
pluggy==1.3.0
# via tox
py==1.11.0
# via tox
six==1.16.0
# via tox
tomli==2.0.1
# via tox
# via
# pyproject-api
# tox
tox==3.28.0
# via
# -c requirements/common_constraints.txt
# -r requirements/tox.in
# tox-battery
tox-battery==0.6.2
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -20,7 +20,6 @@ passenv =
COVERAGE_DIR
DISPLAY
SELENIUM_BROWSER
BOKCHOY_HEADLESS
deps =
django42: -r requirements/django.txt
-r {toxinidir}/requirements/test.txt