Add/Remove Bookmark button to each unit in LMS courseware.
TNL-1957
This commit is contained in:
@@ -7,7 +7,7 @@ from .course_page import CoursePage
|
||||
|
||||
class BookmarksPage(CoursePage):
|
||||
"""
|
||||
Coursware Bookmarks Page.
|
||||
Courseware Bookmarks Page.
|
||||
"""
|
||||
url = None
|
||||
url_path = "courseware/"
|
||||
@@ -23,10 +23,11 @@ class BookmarksPage(CoursePage):
|
||||
""" Check if bookmarks button is visible """
|
||||
return self.q(css=self.BOOKMARKS_BUTTON_SELECTOR).visible
|
||||
|
||||
def click_bookmarks_button(self):
|
||||
def click_bookmarks_button(self, wait_for_results=True):
|
||||
""" Click on Bookmarks button """
|
||||
self.q(css=self.BOOKMARKS_BUTTON_SELECTOR).first.click()
|
||||
EmptyPromise(self.results_present, "Bookmarks results present").fulfill()
|
||||
if wait_for_results:
|
||||
EmptyPromise(self.results_present, "Bookmarks results present").fulfill()
|
||||
|
||||
def results_present(self):
|
||||
""" Check if bookmarks results are present """
|
||||
@@ -53,9 +54,9 @@ class BookmarksPage(CoursePage):
|
||||
breadcrumbs = self.q(css=self.BOOKMARKED_BREADCRUMBS).text
|
||||
return [breadcrumb.replace('\n', '').split('-') for breadcrumb in breadcrumbs]
|
||||
|
||||
def click_bookmark(self, index):
|
||||
def click_bookmarked_block(self, index):
|
||||
"""
|
||||
Click on bookmark at index `index`
|
||||
Click on bookmarked block at index `index`
|
||||
|
||||
Arguments:
|
||||
index (int): bookmark index in the list
|
||||
|
||||
@@ -193,13 +193,13 @@ class CourseNavPage(PageObject):
|
||||
)
|
||||
|
||||
# Regular expression to remove HTML span tags from a string
|
||||
REMOVE_SPAN_TAG_RE = re.compile(r'<span.+/span>')
|
||||
REMOVE_SPAN_TAG_RE = re.compile(r'</span>(.+)<span')
|
||||
|
||||
def _clean_seq_titles(self, element):
|
||||
"""
|
||||
Clean HTML of sequence titles, stripping out span tags and returning the first line.
|
||||
"""
|
||||
return self.REMOVE_SPAN_TAG_RE.sub('', element.get_attribute('innerHTML')).strip().split('\n')[0]
|
||||
return self.REMOVE_SPAN_TAG_RE.search(element.get_attribute('innerHTML')).groups()[0].strip()
|
||||
|
||||
def go_to_sequential_position(self, sequential_position):
|
||||
"""
|
||||
|
||||
@@ -3,6 +3,7 @@ Courseware page.
|
||||
"""
|
||||
|
||||
from .course_page import CoursePage
|
||||
from bok_choy.promise import EmptyPromise
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
|
||||
|
||||
@@ -177,6 +178,32 @@ class CoursewarePage(CoursePage):
|
||||
attribute_value = lambda el: el.get_attribute('data-id')
|
||||
return self.q(css='#sequence-list a').filter(get_active).map(attribute_value).results[0]
|
||||
|
||||
@property
|
||||
def breadcrumb(self):
|
||||
""" Return the course tree breadcrumb shown above the sequential bar """
|
||||
return [part.strip() for part in self.q(css='.path').text[0].split('>')]
|
||||
|
||||
def bookmark_button_visible(self):
|
||||
""" Check if bookmark button is visible """
|
||||
EmptyPromise(lambda: self.q(css='.bookmark-button').visible, "Bookmark button visible").fulfill()
|
||||
return True
|
||||
|
||||
@property
|
||||
def bookmark_button_state(self):
|
||||
""" Return `bookmarked` if button is in bookmarked state else '' """
|
||||
return 'bookmarked' if self.q(css='.bookmark-button.bookmarked').present else ''
|
||||
|
||||
@property
|
||||
def bookmark_icon_visible(self):
|
||||
""" Check if bookmark icon is visible on active sequence nav item """
|
||||
return self.q(css='.active .bookmark-icon').visible
|
||||
|
||||
def click_bookmark_unit_button(self):
|
||||
""" Bookmark a unit by clicking on Bookmark button """
|
||||
previous_state = self.bookmark_button_state
|
||||
self.q(css='.bookmark-button').first.click()
|
||||
EmptyPromise(lambda: self.bookmark_button_state != previous_state, "Bookmark button toggled").fulfill()
|
||||
|
||||
|
||||
class CoursewareSequentialTabPage(CoursePage):
|
||||
"""
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
"""
|
||||
End-to-end tests for the courseware unit bookmarks.
|
||||
"""
|
||||
import json
|
||||
import requests
|
||||
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.bookmarks import BookmarksPage
|
||||
from ...pages.lms.courseware import CoursewarePage
|
||||
from ...pages.lms.course_nav import CourseNavPage
|
||||
from ...pages.studio.overview import CourseOutlinePage
|
||||
from ...pages.common.logout import LogoutPage
|
||||
|
||||
from ...fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from ...fixtures import LMS_BASE_URL
|
||||
from ..helpers import EventsTestMixin, UniqueCourseTest, is_404_page
|
||||
|
||||
|
||||
@@ -22,30 +20,29 @@ class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest):
|
||||
"""
|
||||
USERNAME = "STUDENT"
|
||||
EMAIL = "student@example.com"
|
||||
COURSE_TREE_INFO = [
|
||||
['TestSection1', 'TestSubsection1', 'TestProblem1'],
|
||||
['TestSection2', 'TestSubsection2', 'TestProblem2']
|
||||
]
|
||||
|
||||
def create_course_fixture(self):
|
||||
""" Create course fixture """
|
||||
def create_course_fixture(self, num_chapters):
|
||||
"""
|
||||
Create course fixture
|
||||
|
||||
Arguments:
|
||||
num_chapters: number of chapters to create
|
||||
"""
|
||||
self.course_fixture = CourseFixture( # pylint: disable=attribute-defined-outside-init
|
||||
self.course_info['org'], self.course_info['number'],
|
||||
self.course_info['run'], self.course_info['display_name']
|
||||
)
|
||||
|
||||
self.course_fixture.add_children(
|
||||
XBlockFixtureDesc('chapter', self.COURSE_TREE_INFO[0][0]).add_children(
|
||||
XBlockFixtureDesc('sequential', self.COURSE_TREE_INFO[0][1]).add_children(
|
||||
XBlockFixtureDesc('problem', self.COURSE_TREE_INFO[0][2])
|
||||
xblocks = []
|
||||
for index in range(num_chapters):
|
||||
xblocks += [
|
||||
XBlockFixtureDesc('chapter', 'TestSection{}'.format(index)).add_children(
|
||||
XBlockFixtureDesc('sequential', 'TestSubsection{}'.format(index)).add_children(
|
||||
XBlockFixtureDesc('vertical', 'TestVertical{}'.format(index))
|
||||
)
|
||||
)
|
||||
),
|
||||
XBlockFixtureDesc('chapter', self.COURSE_TREE_INFO[1][0]).add_children(
|
||||
XBlockFixtureDesc('sequential', self.COURSE_TREE_INFO[1][1]).add_children(
|
||||
XBlockFixtureDesc('problem', self.COURSE_TREE_INFO[1][2])
|
||||
)
|
||||
)
|
||||
).install()
|
||||
]
|
||||
self.course_fixture.add_children(*xblocks).install()
|
||||
|
||||
|
||||
class BookmarksTest(BookmarksTestMixin):
|
||||
@@ -66,35 +63,64 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
self.create_course_fixture()
|
||||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||||
self.bookmarks_page = BookmarksPage(self.browser, self.course_id)
|
||||
self.course_nav = CourseNavPage(self.browser)
|
||||
|
||||
def _test_setup(self, num_chapters=2):
|
||||
"""
|
||||
Setup test settings.
|
||||
|
||||
Arguments:
|
||||
num_chapters: number of chapters to create in course
|
||||
"""
|
||||
self.create_course_fixture(num_chapters)
|
||||
|
||||
# Auto-auth register for the course.
|
||||
AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id).visit()
|
||||
|
||||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||||
self.courseware_page.visit()
|
||||
self.bookmarks = BookmarksPage(self.browser, self.course_id)
|
||||
|
||||
# Use auto-auth to retrieve the session for a logged in user
|
||||
self.session = requests.Session()
|
||||
response = self.session.get(LMS_BASE_URL + "/auto_auth?username=STUDENT&email=student@example.com")
|
||||
self.assertTrue(response.ok, "Failed to get session info")
|
||||
def _bookmark_unit(self, index):
|
||||
"""
|
||||
Bookmark a unit
|
||||
|
||||
def _bookmark_unit(self, course_id, usage_id):
|
||||
""" Bookmark a single unit """
|
||||
csrftoken = self.session.cookies['csrftoken']
|
||||
headers = {'Content-type': 'application/json', "X-CSRFToken": csrftoken}
|
||||
url = LMS_BASE_URL + "/api/bookmarks/v0/bookmarks/?course_id=" + course_id + '&fields=path'
|
||||
data = json.dumps({'usage_id': usage_id})
|
||||
Arguments:
|
||||
index: unit index to bookmark
|
||||
"""
|
||||
self.course_nav.go_to_section('TestSection{}'.format(index), 'TestSubsection{}'.format(index))
|
||||
self.courseware_page.click_bookmark_unit_button()
|
||||
|
||||
response = self.session.post(url, data=data, headers=headers, cookies=self.session.cookies)
|
||||
response = json.loads(response.text)
|
||||
self.assertTrue(response['usage_id'] == usage_id, "Failed to bookmark unit")
|
||||
def _bookmark_units(self, num_units):
|
||||
"""
|
||||
Bookmark first `num_units` units by visiting them
|
||||
|
||||
def _bookmarks_blocks(self, xblocks):
|
||||
""" Bookmark all units in a course """
|
||||
for xblock in xblocks:
|
||||
self._bookmark_unit(self.course_id, usage_id=xblock.locator)
|
||||
Arguments:
|
||||
num_units(int): Number of units to bookmarks
|
||||
"""
|
||||
for index in range(num_units):
|
||||
self._bookmark_unit(index)
|
||||
|
||||
def _breadcrumb(self, num_units):
|
||||
"""
|
||||
Creates breadcrumbs for the first `num_units`
|
||||
|
||||
Arguments:
|
||||
num_units(int): Number of units for which we want to create breadcrumbs
|
||||
|
||||
Returns:
|
||||
list of breadcrumbs
|
||||
"""
|
||||
breadcrumbs = []
|
||||
for index in range(num_units):
|
||||
breadcrumbs.append(
|
||||
[
|
||||
'TestSection{}'.format(index),
|
||||
'TestSubsection{}'.format(index),
|
||||
'TestVertical{}'.format(index)
|
||||
]
|
||||
)
|
||||
return breadcrumbs
|
||||
|
||||
def _delete_section(self, index):
|
||||
""" Delete a section at index `index` """
|
||||
@@ -119,6 +145,39 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
self.courseware_page.visit()
|
||||
self.courseware_page.wait_for_page()
|
||||
|
||||
def _toggle_bookmark_and_verify(self, bookmark_icon_state, bookmark_button_state, bookmarked_count):
|
||||
"""
|
||||
Bookmark/Un-Bookmark a unit and then verify
|
||||
"""
|
||||
self.assertTrue(self.courseware_page.bookmark_button_visible)
|
||||
self.courseware_page.click_bookmark_unit_button()
|
||||
self.assertEqual(self.courseware_page.bookmark_icon_visible, bookmark_icon_state)
|
||||
self.assertEqual(self.courseware_page.bookmark_button_state, bookmark_button_state)
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
self.assertEqual(self.bookmarks_page.count(), bookmarked_count)
|
||||
|
||||
def test_bookmark_button(self):
|
||||
"""
|
||||
Scenario: Bookmark unit button toggles correctly
|
||||
|
||||
Given that I am a registered user
|
||||
And I visit my courseware page
|
||||
For first 2 units
|
||||
I visit the unit
|
||||
And I can see the Bookmark button
|
||||
When I click on Bookmark button
|
||||
Then unit should be bookmarked
|
||||
Then I click again on the bookmark button
|
||||
And I should see a unit un-bookmarked
|
||||
"""
|
||||
self._test_setup()
|
||||
for index in range(2):
|
||||
self.course_nav.go_to_section('TestSection{}'.format(index), 'TestSubsection{}'.format(index))
|
||||
|
||||
self._toggle_bookmark_and_verify(True, 'bookmarked', 1)
|
||||
self.bookmarks_page.click_bookmarks_button(False)
|
||||
self._toggle_bookmark_and_verify(False, '', 0)
|
||||
|
||||
def test_empty_bookmarks_list(self):
|
||||
"""
|
||||
Scenario: An empty bookmarks list is shown if there are no bookmarked units.
|
||||
@@ -130,15 +189,16 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
Then I should see an empty bookmarks list
|
||||
And empty bookmarks list content is correct
|
||||
"""
|
||||
self.assertTrue(self.bookmarks.bookmarks_button_visible())
|
||||
self.bookmarks.click_bookmarks_button()
|
||||
self.assertEqual(self.bookmarks.results_header_text(), 'MY BOOKMARKS')
|
||||
self.assertEqual(self.bookmarks.empty_header_text(), 'You have not bookmarked any courseware pages yet.')
|
||||
self._test_setup()
|
||||
self.assertTrue(self.bookmarks_page.bookmarks_button_visible())
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
self.assertEqual(self.bookmarks_page.results_header_text(), 'MY BOOKMARKS')
|
||||
self.assertEqual(self.bookmarks_page.empty_header_text(), 'You have not bookmarked any courseware pages yet.')
|
||||
|
||||
empty_list_text = ("Use bookmarks to help you easily return to courseware pages. To bookmark a page, "
|
||||
"select Bookmark in the upper right corner of that page. To see a list of all your "
|
||||
"bookmarks, select Bookmarks in the upper left corner of any courseware page.")
|
||||
self.assertEqual(self.bookmarks.empty_list_text(), empty_list_text)
|
||||
self.assertEqual(self.bookmarks_page.empty_list_text(), empty_list_text)
|
||||
|
||||
def test_bookmarks_list(self):
|
||||
"""
|
||||
@@ -160,27 +220,30 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
# discarded by the current version of MySQL we are using due to the
|
||||
# lack of support. Due to which order of bookmarked units will be
|
||||
# incorrect.
|
||||
xblocks = self.course_fixture.get_nested_xblocks(category="problem")
|
||||
self._bookmarks_blocks(xblocks)
|
||||
self._test_setup()
|
||||
self._bookmark_units(2)
|
||||
|
||||
self.bookmarks.click_bookmarks_button()
|
||||
self.assertTrue(self.bookmarks.results_present())
|
||||
self.assertEqual(self.bookmarks.results_header_text(), 'MY BOOKMARKS')
|
||||
self.assertEqual(self.bookmarks.count(), 2)
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
self.assertTrue(self.bookmarks_page.results_present())
|
||||
self.assertEqual(self.bookmarks_page.results_header_text(), 'MY BOOKMARKS')
|
||||
self.assertEqual(self.bookmarks_page.count(), 2)
|
||||
|
||||
bookmarked_breadcrumbs = self.bookmarks.breadcrumbs()
|
||||
bookmarked_breadcrumbs = self.bookmarks_page.breadcrumbs()
|
||||
|
||||
# Verify bookmarked breadcrumbs
|
||||
self.assertItemsEqual(bookmarked_breadcrumbs, self.COURSE_TREE_INFO)
|
||||
breadcrumbs = self._breadcrumb(2)
|
||||
self.assertItemsEqual(bookmarked_breadcrumbs, breadcrumbs)
|
||||
|
||||
# get usage ids for units
|
||||
xblocks = self.course_fixture.get_nested_xblocks(category="vertical")
|
||||
xblock_usage_ids = [xblock.locator for xblock in xblocks]
|
||||
# Verify link navigation
|
||||
for index in range(2):
|
||||
self.bookmarks.click_bookmark(index)
|
||||
self.bookmarks_page.click_bookmarked_block(index)
|
||||
self.courseware_page.wait_for_page()
|
||||
self.assertTrue(self.courseware_page.active_usage_id() in xblock_usage_ids)
|
||||
self.courseware_page.visit().wait_for_page()
|
||||
self.bookmarks.click_bookmarks_button()
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
|
||||
def test_unreachable_bookmark(self):
|
||||
"""
|
||||
@@ -195,13 +258,34 @@ class BookmarksTest(BookmarksTestMixin):
|
||||
When I click on deleted bookmark
|
||||
Then I should navigated to 404 page
|
||||
"""
|
||||
self._bookmarks_blocks(self.course_fixture.get_nested_xblocks(category="problem"))
|
||||
|
||||
self._test_setup()
|
||||
self._bookmark_units(2)
|
||||
self._delete_section(0)
|
||||
|
||||
self.bookmarks.click_bookmarks_button()
|
||||
self.assertTrue(self.bookmarks.results_present())
|
||||
self.assertEqual(self.bookmarks.count(), 2)
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
self.assertTrue(self.bookmarks_page.results_present())
|
||||
self.assertEqual(self.bookmarks_page.count(), 2)
|
||||
|
||||
self.bookmarks.click_bookmark(0)
|
||||
self.bookmarks_page.click_bookmarked_block(1)
|
||||
self.assertTrue(is_404_page(self.browser))
|
||||
|
||||
def test_page_size_limit(self):
|
||||
"""
|
||||
Scenario: We can get more bookmarks if page size is greater than default page size.
|
||||
Note:
|
||||
* Current Bookmarks API page_size value is 10.
|
||||
* page_size value in bookmarks client side is set to 500.
|
||||
|
||||
Given that I am a registered user
|
||||
And I visit my courseware page
|
||||
And I have bookmarked all the units available
|
||||
Then I click on Bookmarks button
|
||||
And I should see a bookmarked list
|
||||
And bookmark list contains 11 bookmarked items
|
||||
"""
|
||||
self._test_setup(11)
|
||||
self._bookmark_units(11)
|
||||
|
||||
self.bookmarks_page.click_bookmarks_button()
|
||||
self.assertTrue(self.bookmarks_page.results_present())
|
||||
self.assertEqual(self.bookmarks_page.count(), 11)
|
||||
|
||||
@@ -29,6 +29,7 @@ class CoursewareTest(UniqueCourseTest):
|
||||
super(CoursewareTest, self).setUp()
|
||||
|
||||
self.courseware_page = CoursewarePage(self.browser, self.course_id)
|
||||
self.course_nav = CourseNavPage(self.browser)
|
||||
|
||||
self.course_outline = CourseOutlinePage(
|
||||
self.browser,
|
||||
@@ -38,12 +39,12 @@ class CoursewareTest(UniqueCourseTest):
|
||||
)
|
||||
|
||||
# Install a course with sections/problems, tabs, updates, and handouts
|
||||
course_fix = CourseFixture(
|
||||
self.course_fix = CourseFixture(
|
||||
self.course_info['org'], self.course_info['number'],
|
||||
self.course_info['run'], self.course_info['display_name']
|
||||
)
|
||||
|
||||
course_fix.add_children(
|
||||
self.course_fix.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section 1').add_children(
|
||||
XBlockFixtureDesc('sequential', 'Test Subsection 1').add_children(
|
||||
XBlockFixtureDesc('problem', 'Test Problem 1')
|
||||
@@ -67,6 +68,18 @@ class CoursewareTest(UniqueCourseTest):
|
||||
self.problem_page = ProblemPage(self.browser)
|
||||
self.assertEqual(self.problem_page.problem_name, 'TEST PROBLEM 1')
|
||||
|
||||
def _change_problem_release_date_in_studio(self):
|
||||
"""
|
||||
|
||||
"""
|
||||
self.course_outline.q(css=".subsection-header-actions .configure-button").first.click()
|
||||
self.course_outline.q(css="#start_date").fill("01/01/2030")
|
||||
self.course_outline.q(css=".action-save").first.click()
|
||||
|
||||
def _create_breadcrumb(self, index):
|
||||
""" Create breadcrumb """
|
||||
return ['Test Section {}'.format(index), 'Test Subsection {}'.format(index), 'Test Problem {}'.format(index)]
|
||||
|
||||
def _auto_auth(self, username, email, staff):
|
||||
"""
|
||||
Logout and login with given credentials.
|
||||
@@ -92,6 +105,9 @@ class CoursewareTest(UniqueCourseTest):
|
||||
# Set release date for subsection in future.
|
||||
self.course_outline.change_problem_release_date_in_studio()
|
||||
|
||||
# Wait for 2 seconds to save new date.
|
||||
time.sleep(2)
|
||||
|
||||
# Logout and login as a student.
|
||||
LogoutPage(self.browser).visit()
|
||||
self._auto_auth(self.USERNAME, self.EMAIL, False)
|
||||
@@ -246,6 +262,23 @@ class ProctoredExamTest(UniqueCourseTest):
|
||||
self.courseware_page.start_timed_exam()
|
||||
self.assertTrue(self.courseware_page.is_timer_bar_present)
|
||||
|
||||
def test_course_tree_breadcrumb(self):
|
||||
"""
|
||||
Scenario: Correct course tree breadcrumb is shown.
|
||||
|
||||
Given that I am a registered user
|
||||
And I visit my courseware page
|
||||
Then I should see correct course tree breadcrumb
|
||||
"""
|
||||
self.courseware_page.visit()
|
||||
|
||||
xblocks = self.course_fix.get_nested_xblocks(category="problem")
|
||||
for index in range(1, len(xblocks) + 1):
|
||||
self.course_nav.go_to_section('Test Section {}'.format(index), 'Test Subsection {}'.format(index))
|
||||
courseware_page_breadcrumb = self.courseware_page.breadcrumb
|
||||
expected_breadcrumb = self._create_breadcrumb(index)
|
||||
self.assertEqual(courseware_page_breadcrumb, expected_breadcrumb)
|
||||
|
||||
def test_time_allotted_field_is_not_visible_with_none_exam(self):
|
||||
"""
|
||||
Given that I am a staff member
|
||||
|
||||
Reference in New Issue
Block a user