Commit a406a221 by Renzo Lucioni

Merge pull request #12170 from edx/renzo/program-listing-bokchoy

Acceptance tests for program listing page
parents 98b8b68d 182e34f8
...@@ -18,5 +18,5 @@ COMMENTS_STUB_URL = os.environ.get('comments_url', 'http://localhost:4567') ...@@ -18,5 +18,5 @@ COMMENTS_STUB_URL = os.environ.get('comments_url', 'http://localhost:4567')
# Get the URL of the EdxNotes service stub used in the test # Get the URL of the EdxNotes service stub used in the test
EDXNOTES_STUB_URL = os.environ.get('edxnotes_url', 'http://localhost:8042') EDXNOTES_STUB_URL = os.environ.get('edxnotes_url', 'http://localhost:8042')
# Get the URL of the EdxNotes service stub used in the test # Get the URL of the Programs service stub used in the test
PROGRAMS_STUB_URL = os.environ.get('programs_url', 'http://localhost:8090') PROGRAMS_STUB_URL = os.environ.get('programs_url', 'http://localhost:8090')
...@@ -9,7 +9,7 @@ from lazy import lazy ...@@ -9,7 +9,7 @@ from lazy import lazy
from . import LMS_BASE_URL from . import LMS_BASE_URL
class ConfigModelFixureError(Exception): class ConfigModelFixtureError(Exception):
""" """
Error occurred while configuring the stub XQueue. Error occurred while configuring the stub XQueue.
""" """
...@@ -41,7 +41,7 @@ class ConfigModelFixture(object): ...@@ -41,7 +41,7 @@ class ConfigModelFixture(object):
) )
if not response.ok: if not response.ok:
raise ConfigModelFixureError( raise ConfigModelFixtureError(
"Could not configure url '{}'. response: {} - {}".format( "Could not configure url '{}'. response: {} - {}".format(
self._api_base, self._api_base,
response, response,
...@@ -53,7 +53,7 @@ class ConfigModelFixture(object): ...@@ -53,7 +53,7 @@ class ConfigModelFixture(object):
def session_cookies(self): def session_cookies(self):
""" """
Log in as a staff user, then return the cookies for the session (as a dict) Log in as a staff user, then return the cookies for the session (as a dict)
Raises a `ConfigModelFixureError` if the login fails. Raises a `ConfigModelFixtureError` if the login fails.
""" """
return {key: val for key, val in self.session.cookies.items()} return {key: val for key, val in self.session.cookies.items()}
...@@ -92,4 +92,4 @@ class ConfigModelFixture(object): ...@@ -92,4 +92,4 @@ class ConfigModelFixture(object):
else: else:
msg = "Could not log in to use ConfigModel restful API. Status code: {0}".format(response.status_code) msg = "Could not log in to use ConfigModel restful API. Status code: {0}".format(response.status_code)
raise ConfigModelFixureError(msg) raise ConfigModelFixtureError(msg)
""" """
Tools to create programs-related data for use in bok choy tests. Tools to create programs-related data for use in bok choy tests.
""" """
from collections import namedtuple
import json import json
import factory import factory
import requests import requests
from . import PROGRAMS_STUB_URL from . import PROGRAMS_STUB_URL
from .config import ConfigModelFixture
FakeProgram = namedtuple('FakeProgram', ['name', 'status', 'org_key', 'course_id'])
class Program(factory.Factory): class Program(factory.Factory):
...@@ -17,12 +22,14 @@ class Program(factory.Factory): ...@@ -17,12 +22,14 @@ class Program(factory.Factory):
model = dict model = dict
id = factory.Sequence(lambda n: n) # pylint: disable=invalid-name id = factory.Sequence(lambda n: n) # pylint: disable=invalid-name
name = "dummy-program-name" name = 'dummy-program-name'
subtitle = "dummy-program-subtitle" subtitle = 'dummy-program-subtitle'
category = "xseries" category = 'xseries'
status = "unpublished" status = 'unpublished'
marketing_slug = factory.Sequence(lambda n: 'slug-{}'.format(n)) # pylint: disable=unnecessary-lambda
organizations = [] organizations = []
course_codes = [] course_codes = []
banner_image_urls = {}
class Organization(factory.Factory): class Organization(factory.Factory):
...@@ -32,8 +39,30 @@ class Organization(factory.Factory): ...@@ -32,8 +39,30 @@ class Organization(factory.Factory):
class Meta(object): class Meta(object):
model = dict model = dict
key = "dummyX" key = 'dummyX'
display_name = "dummy-org-display-name" display_name = 'dummy-org-display-name'
class CourseCode(factory.Factory):
"""
Factory for stubbing nested course code resources from the Programs API (v1).
"""
class Meta(object):
model = dict
display_name = 'dummy-org-display-name'
run_modes = []
class RunMode(factory.Factory):
"""
Factory for stubbing nested run mode resources from the Programs API (v1).
"""
class Meta(object):
model = dict
course_key = 'org/course/run'
mode_slug = 'verified'
class ProgramsFixture(object): class ProgramsFixture(object):
...@@ -41,16 +70,24 @@ class ProgramsFixture(object): ...@@ -41,16 +70,24 @@ class ProgramsFixture(object):
Interface to set up mock responses from the Programs stub server. Interface to set up mock responses from the Programs stub server.
""" """
def install_programs(self, program_values): def install_programs(self, fake_programs):
""" """
Sets the response data for the programs list endpoint. Sets the response data for the programs list endpoint.
At present, `program_values` needs to be a sequence of sequences of (program_name, org_key). At present, `fake_programs` must be a iterable of FakeProgram named tuples.
""" """
programs = [] programs = []
for program_name, org_key in program_values: for program in fake_programs:
org = Organization(key=org_key) run_mode = RunMode(course_key=program.course_id)
program = Program(name=program_name, organizations=[org]) course_code = CourseCode(run_modes=[run_mode])
org = Organization(key=program.org_key)
program = Program(
name=program.name,
status=program.status,
organizations=[org],
course_codes=[course_code]
)
programs.append(program) programs.append(program)
api_result = {'results': programs} api_result = {'results': programs}
...@@ -59,3 +96,24 @@ class ProgramsFixture(object): ...@@ -59,3 +96,24 @@ class ProgramsFixture(object):
'{}/set_config'.format(PROGRAMS_STUB_URL), '{}/set_config'.format(PROGRAMS_STUB_URL),
data={'programs': json.dumps(api_result)}, data={'programs': json.dumps(api_result)},
) )
class ProgramsConfigMixin(object):
"""Mixin providing a method used to configure the programs feature."""
def set_programs_api_configuration(self, is_enabled=False, api_version=1, api_url=PROGRAMS_STUB_URL,
js_path='/js', css_path='/css'):
"""Dynamically adjusts the Programs config model during tests."""
ConfigModelFixture('/config/programs', {
'enabled': is_enabled,
'api_version_number': api_version,
'internal_service_url': api_url,
'public_service_url': api_url,
'authoring_app_js_path': js_path,
'authoring_app_css_path': css_path,
'cache_ttl': 0,
'enable_student_dashboard': is_enabled,
'enable_studio_tab': is_enabled,
'enable_certification': is_enabled,
'xseries_ad_enabled': is_enabled,
'program_listing_enabled': is_enabled,
}).install()
"""LMS-hosted Programs pages"""
from bok_choy.page_object import PageObject
from . import BASE_URL
class ProgramListingPage(PageObject):
"""Program listing page."""
url = BASE_URL + '/dashboard/programs/'
def is_browser_on_page(self):
return self.q(css='.program-list-wrapper').present
@property
def are_cards_present(self):
"""Check whether program cards are present."""
return self.q(css='.program-card').present
@property
def is_sidebar_present(self):
"""Check whether sidebar is present."""
return self.q(css='.sidebar').present
"""Acceptance tests for LMS-hosted Programs pages"""
from nose.plugins.attrib import attr
from ...fixtures.programs import FakeProgram, ProgramsFixture, ProgramsConfigMixin
from ...fixtures.course import CourseFixture
from ..helpers import UniqueCourseTest
from ...pages.lms.auto_auth import AutoAuthPage
from ...pages.lms.programs import ProgramListingPage
class ProgramListingPageBase(ProgramsConfigMixin, UniqueCourseTest):
"""Base class used for program listing page tests."""
def setUp(self):
super(ProgramListingPageBase, self).setUp()
self.set_programs_api_configuration(is_enabled=True)
self.listing_page = ProgramListingPage(self.browser)
def stub_api(self, course_id=None):
"""Stub out the programs API with fake data."""
name = 'Fake Program'
status = 'active'
org_key = self.course_info['org']
course_id = course_id if course_id else self.course_id
ProgramsFixture().install_programs([
FakeProgram(name=name, status=status, org_key=org_key, course_id=course_id),
])
def auth(self, enroll=True):
"""Authenticate, enrolling the user in the configured course if requested."""
CourseFixture(**self.course_info).install()
course_id = self.course_id if enroll else None
AutoAuthPage(self.browser, course_id=course_id).visit()
class ProgramListingPageTest(ProgramListingPageBase):
"""Verify user-facing behavior of the program listing page."""
def test_no_enrollments(self):
"""Verify that no cards appear when the user has no enrollments."""
self.stub_api()
self.auth(enroll=False)
self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present)
self.assertFalse(self.listing_page.are_cards_present)
def test_no_programs(self):
"""
Verify that no cards appear when the user has enrollments
but none are included in an active program.
"""
course_id = self.course_id.replace(
self.course_info['run'],
'other_run'
)
self.stub_api(course_id=course_id)
self.auth()
self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present)
self.assertFalse(self.listing_page.are_cards_present)
def test_enrollments_and_programs(self):
"""
Verify that cards appear when the user has enrollments
which are included in at least one active program.
"""
self.stub_api()
self.auth()
self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present)
self.assertTrue(self.listing_page.are_cards_present)
@attr('a11y')
class ProgramListingPageA11yTest(ProgramListingPageBase):
"""Test program listing page accessibility."""
def test_empty_a11y(self):
"""Test a11y of the page's empty state."""
self.stub_api()
self.auth(enroll=False)
self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present)
self.assertFalse(self.listing_page.are_cards_present)
self.listing_page.a11y_audit.check_for_accessibility_errors()
def test_cards_a11y(self):
"""Test a11y when program cards are present."""
self.stub_api()
self.auth()
self.listing_page.visit()
self.assertTrue(self.listing_page.is_sidebar_present)
self.assertTrue(self.listing_page.are_cards_present)
self.listing_page.a11y_audit.check_for_accessibility_errors()
...@@ -8,7 +8,7 @@ from uuid import uuid4 ...@@ -8,7 +8,7 @@ from uuid import uuid4
from ...fixtures import PROGRAMS_STUB_URL from ...fixtures import PROGRAMS_STUB_URL
from ...fixtures.config import ConfigModelFixture from ...fixtures.config import ConfigModelFixture
from ...fixtures.programs import ProgramsFixture from ...fixtures.programs import FakeProgram, ProgramsFixture, ProgramsConfigMixin
from ...pages.studio.auto_auth import AutoAuthPage from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.studio.library import LibraryEditPage from ...pages.studio.library import LibraryEditPage
from ...pages.studio.index import DashboardPage, DashboardPageWithPrograms from ...pages.studio.index import DashboardPage, DashboardPageWithPrograms
...@@ -69,7 +69,7 @@ class CreateLibraryTest(WebAppTest): ...@@ -69,7 +69,7 @@ class CreateLibraryTest(WebAppTest):
self.assertTrue(self.dashboard_page.has_library(name=name, org=org, number=number)) self.assertTrue(self.dashboard_page.has_library(name=name, org=org, number=number))
class DashboardProgramsTabTest(WebAppTest): class DashboardProgramsTabTest(ProgramsConfigMixin, WebAppTest):
""" """
Test the programs tab on the studio home page. Test the programs tab on the studio home page.
""" """
...@@ -81,23 +81,6 @@ class DashboardProgramsTabTest(WebAppTest): ...@@ -81,23 +81,6 @@ class DashboardProgramsTabTest(WebAppTest):
self.dashboard_page = DashboardPageWithPrograms(self.browser) self.dashboard_page = DashboardPageWithPrograms(self.browser)
self.auth_page.visit() self.auth_page.visit()
def set_programs_api_configuration(self, is_enabled=False, api_version=1, api_url=PROGRAMS_STUB_URL,
js_path='/js', css_path='/css'):
"""
Dynamically adjusts the programs API config model during tests.
"""
ConfigModelFixture('/config/programs', {
'enabled': is_enabled,
'enable_studio_tab': is_enabled,
'enable_student_dashboard': is_enabled,
'api_version_number': api_version,
'internal_service_url': api_url,
'public_service_url': api_url,
'authoring_app_js_path': js_path,
'authoring_app_css_path': css_path,
'cache_ttl': 0
}).install()
def test_tab_is_disabled(self): def test_tab_is_disabled(self):
""" """
The programs tab and "new program" button should not appear at all The programs tab and "new program" button should not appear at all
...@@ -128,16 +111,24 @@ class DashboardProgramsTabTest(WebAppTest): ...@@ -128,16 +111,24 @@ class DashboardProgramsTabTest(WebAppTest):
via config, and the results of the program list should display when via config, and the results of the program list should display when
the list is nonempty. the list is nonempty.
""" """
test_program_values = [('first program', 'org1'), ('second program', 'org2')] test_program_values = [
FakeProgram(name='first program', status='unpublished', org_key='org1', course_id='foo/bar/baz'),
FakeProgram(name='second program', status='unpublished', org_key='org2', course_id='qux/quux/corge'),
]
ProgramsFixture().install_programs(test_program_values) ProgramsFixture().install_programs(test_program_values)
self.set_programs_api_configuration(True) self.set_programs_api_configuration(True)
self.dashboard_page.visit() self.dashboard_page.visit()
self.assertTrue(self.dashboard_page.is_programs_tab_present()) self.assertTrue(self.dashboard_page.is_programs_tab_present())
self.assertTrue(self.dashboard_page.is_new_program_button_present()) self.assertTrue(self.dashboard_page.is_new_program_button_present())
results = self.dashboard_page.get_program_list()
self.assertEqual(results, test_program_values)
self.assertFalse(self.dashboard_page.is_empty_list_create_button_present()) self.assertFalse(self.dashboard_page.is_empty_list_create_button_present())
results = self.dashboard_page.get_program_list()
expected = [(p.name, p.org_key) for p in test_program_values]
self.assertEqual(results, expected)
def test_tab_requires_staff(self): def test_tab_requires_staff(self):
""" """
The programs tab and "new program" button will not be available, even The programs tab and "new program" button will not be available, even
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment