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')
# Get the URL of the EdxNotes service stub used in the test
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')
......@@ -9,7 +9,7 @@ from lazy import lazy
from . import LMS_BASE_URL
class ConfigModelFixureError(Exception):
class ConfigModelFixtureError(Exception):
"""
Error occurred while configuring the stub XQueue.
"""
......@@ -41,7 +41,7 @@ class ConfigModelFixture(object):
)
if not response.ok:
raise ConfigModelFixureError(
raise ConfigModelFixtureError(
"Could not configure url '{}'. response: {} - {}".format(
self._api_base,
response,
......@@ -53,7 +53,7 @@ class ConfigModelFixture(object):
def session_cookies(self):
"""
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()}
......@@ -92,4 +92,4 @@ class ConfigModelFixture(object):
else:
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.
"""
from collections import namedtuple
import json
import factory
import requests
from . import PROGRAMS_STUB_URL
from .config import ConfigModelFixture
FakeProgram = namedtuple('FakeProgram', ['name', 'status', 'org_key', 'course_id'])
class Program(factory.Factory):
......@@ -17,12 +22,14 @@ class Program(factory.Factory):
model = dict
id = factory.Sequence(lambda n: n) # pylint: disable=invalid-name
name = "dummy-program-name"
subtitle = "dummy-program-subtitle"
category = "xseries"
status = "unpublished"
name = 'dummy-program-name'
subtitle = 'dummy-program-subtitle'
category = 'xseries'
status = 'unpublished'
marketing_slug = factory.Sequence(lambda n: 'slug-{}'.format(n)) # pylint: disable=unnecessary-lambda
organizations = []
course_codes = []
banner_image_urls = {}
class Organization(factory.Factory):
......@@ -32,8 +39,30 @@ class Organization(factory.Factory):
class Meta(object):
model = dict
key = "dummyX"
display_name = "dummy-org-display-name"
key = 'dummyX'
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):
......@@ -41,16 +70,24 @@ class ProgramsFixture(object):
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.
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 = []
for program_name, org_key in program_values:
org = Organization(key=org_key)
program = Program(name=program_name, organizations=[org])
for program in fake_programs:
run_mode = RunMode(course_key=program.course_id)
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)
api_result = {'results': programs}
......@@ -59,3 +96,24 @@ class ProgramsFixture(object):
'{}/set_config'.format(PROGRAMS_STUB_URL),
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
from ...fixtures import PROGRAMS_STUB_URL
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.library import LibraryEditPage
from ...pages.studio.index import DashboardPage, DashboardPageWithPrograms
......@@ -69,7 +69,7 @@ class CreateLibraryTest(WebAppTest):
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.
"""
......@@ -81,23 +81,6 @@ class DashboardProgramsTabTest(WebAppTest):
self.dashboard_page = DashboardPageWithPrograms(self.browser)
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):
"""
The programs tab and "new program" button should not appear at all
......@@ -128,16 +111,24 @@ class DashboardProgramsTabTest(WebAppTest):
via config, and the results of the program list should display when
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)
self.set_programs_api_configuration(True)
self.dashboard_page.visit()
self.assertTrue(self.dashboard_page.is_programs_tab_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())
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):
"""
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