Skip to content
Snippets Groups Projects
Unverified Commit 437cf0c7 authored by Feanil Patel's avatar Feanil Patel Committed by GitHub
Browse files

Merge pull request #24432 from edly-io/ziafazal/bd-18-bockchoy-tests-removal

[BD-18] Remove bockchoy tests
parents d26b8bf4 3d651103
No related branches found
Tags release-2020-07-16-11.23
No related merge requests found
Showing
with 25 additions and 4132 deletions
"""
Tools for creating edxnotes content fixture data.
"""
import json
import factory
import requests
from common.test.acceptance.fixtures import EDXNOTES_STUB_URL
class Range(factory.Factory):
class Meta(object):
model = dict
start = "/div[1]/p[1]"
end = "/div[1]/p[1]"
startOffset = 0
endOffset = 8
class Note(factory.Factory):
class Meta(object):
model = dict
user = "dummy-user"
usage_id = "dummy-usage-id"
course_id = "dummy-course-id"
text = "dummy note text"
quote = "dummy note quote"
ranges = [Range()]
class EdxNotesFixtureError(Exception):
"""
Error occurred while installing a edxnote fixture.
"""
pass
class EdxNotesFixture(object):
notes = []
def create_notes(self, notes_list):
self.notes = notes_list
return self
def install(self):
"""
Push the data to the stub EdxNotes service.
"""
response = requests.post(
'{}/create_notes'.format(EDXNOTES_STUB_URL),
data=json.dumps(self.notes)
)
if not response.ok:
raise EdxNotesFixtureError(
u"Could not create notes {0}. Status was {1}".format(
json.dumps(self.notes), response.status_code
)
)
return self
def cleanup(self):
"""
Cleanup the stub EdxNotes service.
"""
self.notes = []
response = requests.put('{}/cleanup'.format(EDXNOTES_STUB_URL))
if not response.ok:
raise EdxNotesFixtureError(
u"Could not cleanup EdxNotes service {0}. Status was {1}".format(
json.dumps(self.notes), response.status_code
)
)
return self
"""
Fixture to configure XQueue response.
"""
import json
import requests
from common.test.acceptance.fixtures import XQUEUE_STUB_URL
class XQueueResponseFixtureError(Exception):
"""
Error occurred while configuring the stub XQueue.
"""
pass
class XQueueResponseFixture(object):
"""
Configure the XQueue stub's response to submissions.
"""
def __init__(self, pattern, response_dict):
"""
Configure XQueue stub to POST `response_dict` (a dictionary)
back to the LMS when it receives a submission that contains the string
`pattern`.
Remember that there is one XQueue stub shared by all the tests;
if possible, you should have tests use unique queue names
to avoid conflict between tests running in parallel.
"""
self._pattern = pattern
self._response_dict = response_dict
def install(self):
"""
Configure the stub via HTTP.
"""
url = XQUEUE_STUB_URL + "/set_config"
# Configure the stub to respond to submissions to our queue
payload = {self._pattern: json.dumps(self._response_dict)}
response = requests.put(url, data=payload)
if not response.ok:
raise XQueueResponseFixtureError(
u"Could not configure XQueue stub for queue '{1}'. Status code: {2}".format(
self._pattern, self._response_dict))
"""
Common mixin for paginated UIs.
"""
import six
from selenium.webdriver.common.keys import Keys
class PaginatedUIMixin(object):
"""Common methods used for paginated UI."""
PAGINATION_FOOTER_CSS = 'nav.bottom'
PAGE_NUMBER_INPUT_CSS = 'input#page-number-input'
NEXT_PAGE_BUTTON_CSS = 'button.next-page-link'
PREVIOUS_PAGE_BUTTON_CSS = 'button.previous-page-link'
PAGINATION_HEADER_TEXT_CSS = 'div.search-tools'
CURRENT_PAGE_NUMBER_CSS = 'span.current-page'
TOTAL_PAGES_CSS = 'span.total-pages'
def get_pagination_header_text(self):
"""Return the text showing which items the user is currently viewing."""
return self.q(css=self.PAGINATION_HEADER_TEXT_CSS).text[0]
def pagination_controls_visible(self):
"""Return true if the pagination controls in the footer are visible."""
footer_nav = self.q(css=self.PAGINATION_FOOTER_CSS).results[0]
# The footer element itself is non-generic, so check above it
footer_el = footer_nav.find_element_by_xpath('..')
return 'hidden' not in footer_el.get_attribute('class').split()
def get_current_page_number(self):
"""Return the the current page number."""
return int(self.q(css=self.CURRENT_PAGE_NUMBER_CSS).text[0])
@property
def get_total_pages(self):
"""Returns the total page value"""
return int(self.q(css=self.TOTAL_PAGES_CSS).text[0])
def go_to_page(self, page_number):
"""Go to the given page_number in the paginated list results."""
self.q(css=self.PAGE_NUMBER_INPUT_CSS).results[0].send_keys(six.text_type(page_number), Keys.ENTER)
self.wait_for_ajax()
def press_next_page_button(self):
"""Press the next page button in the paginated list results."""
self.q(css=self.NEXT_PAGE_BUTTON_CSS).click()
self.wait_for_ajax()
def press_previous_page_button(self):
"""Press the previous page button in the paginated list results."""
self.q(css=self.PREVIOUS_PAGE_BUTTON_CSS).click()
self.wait_for_ajax()
def is_next_page_button_enabled(self):
"""Return whether the 'next page' button can be clicked."""
return self.is_enabled(self.NEXT_PAGE_BUTTON_CSS)
def is_previous_page_button_enabled(self):
"""Return whether the 'previous page' button can be clicked."""
return self.is_enabled(self.PREVIOUS_PAGE_BUTTON_CSS)
def is_enabled(self, css):
"""Return whether the given element is not disabled."""
return 'is-disabled' not in self.q(css=css).attrs('class')[0]
@property
def footer_visible(self):
""" Return True if footer is visible else False"""
return self.q(css='.pagination.bottom').visible
......@@ -4,63 +4,11 @@ Utility methods common to Studio and the LMS.
import six
from bok_choy.promise import BrokenPromise
from selenium.webdriver.common.action_chains import ActionChains
from common.test.acceptance.pages.lms.create_mode import ModeCreationPage
from common.test.acceptance.pages.lms.track_selection import TrackSelectionPage
from common.test.acceptance.tests.helpers import disable_animations
def sync_on_notification(page, style='default', wait_for_hide=False):
"""
Sync on notifications but do not raise errors.
A BrokenPromise in the wait_for probably means that we missed it.
We should just swallow this error and not raise it for reasons including:
* We are not specifically testing this functionality
* This functionality is covered by unit tests
* This verification method is prone to flakiness
and browser version dependencies
See classes in edx-platform:
lms/static/sass/elements/_system-feedback.scss
"""
hiding_class = 'is-hiding'
shown_class = 'is-shown'
def notification_has_class(style, el_class):
"""
Return a boolean representing whether
the notification has the class applied.
"""
if style == 'mini':
css_string = '.wrapper-notification-mini.{}'
else:
css_string = '.wrapper-notification-confirmation.{}'
return page.q(css=css_string.format(el_class)).present
# Wait for the notification to show.
# This notification appears very quickly and maybe missed. Don't raise an error.
try:
page.wait_for(
lambda: notification_has_class(style, shown_class),
'Notification should have been shown.',
timeout=5
)
except BrokenPromise as _err:
pass
# Now wait for it to hide.
# This is not required for web page interaction, so not really needed.
if wait_for_hide:
page.wait_for(
lambda: notification_has_class(style, hiding_class),
'Notification should have hidden.'
)
def click_css(page, css, source_index=0, require_notification=True):
def click_css(page, css, source_index=0):
"""
Click the button/link with the given css and index on the specified page (subclass of PageObject).
......@@ -80,9 +28,6 @@ def click_css(page, css, source_index=0, require_notification=True):
# Click on the element in the browser
page.q(css=css).filter(_is_visible).nth(source_index).click()
if require_notification:
sync_on_notification(page)
# Some buttons trigger ajax posts
# (e.g. .add-missing-groups-button as configured in split_test_author_view.js)
# so after you click anything wait for the ajax call to finish
......@@ -99,46 +44,3 @@ def confirm_prompt(page, cancel=False, require_notification=None):
page.wait_for_element_visibility(confirmation_button_css, 'Confirmation button is visible')
require_notification = (not cancel) if require_notification is None else require_notification
click_css(page, confirmation_button_css, require_notification=require_notification)
def hover(browser, element):
"""
Hover over an element.
"""
ActionChains(browser).move_to_element(element).perform()
def enroll_user_track(browser, course_id, track):
"""
Utility method to enroll a user in the audit or verified user track. Creates and connects to the
necessary pages. Selects the track and handles payment for verified.
Supported tracks are 'verified' or 'audit'.
"""
track_selection = TrackSelectionPage(browser, course_id)
# Select track
track_selection.visit()
track_selection.enroll(track)
def add_enrollment_course_modes(browser, course_id, tracks):
"""
Add the specified array of tracks to the given course.
Supported tracks are `verified` and `audit` (all others will be ignored),
and display names assigned are `Verified` and `Audit`, respectively.
"""
for track in tracks:
if track == 'audit':
# Add an audit mode to the course
ModeCreationPage(
browser,
course_id, mode_slug='audit',
mode_display_name='Audit'
).visit()
elif track == 'verified':
# Add a verified mode to the course
ModeCreationPage(
browser, course_id, mode_slug='verified',
mode_display_name='Verified', min_price=10
).visit()
"""
Pages object for the Django's /admin/ views.
"""
from bok_choy.page_object import PageObject
from common.test.acceptance.pages.lms import BASE_URL
class ChangeUserAdminPage(PageObject):
"""
Change user page in Django's admin.
"""
def __init__(self, browser, user_pk):
super(ChangeUserAdminPage, self).__init__(browser)
self.user_pk = user_pk
@property
def url(self):
"""
Returns the page URL for the page based on self.user_pk.
"""
return u'{base}/admin/auth/user/{user_pk}/'.format(
base=BASE_URL,
user_pk=self.user_pk,
)
@property
def username(self):
"""
Reads the read-only username.
"""
return self.q(css='.field-username .readonly').text[0]
@property
def first_name_element(self):
"""
Selects the first name element.
"""
return self.q(css='[name="first_name"]')
@property
def first_name(self):
"""
Reads the first name value from the input field.
"""
return self.first_name_element.attrs('value')[0]
@property
def submit_element(self):
"""
Gets the "Save" submit element.
Note that there are multiple submit elements in the change view.
"""
return self.q(css='input.default[type="submit"]')
def submit(self):
"""
Submits the form.
"""
self.submit_element.click()
def change_first_name(self, first_name):
"""
Changes the first name and submits the form.
Args:
first_name: The first name as unicode.
"""
self.first_name_element.fill(first_name)
self.submit()
def is_browser_on_page(self):
"""
Returns True if the browser is currently on the right page.
"""
return self.q(css='#user_form').present
"""
Courseware Boomarks
"""
from common.test.acceptance.pages.common.paging import PaginatedUIMixin
from common.test.acceptance.pages.lms.course_page import CoursePage
class BookmarksPage(CoursePage, PaginatedUIMixin):
"""
Courseware Bookmarks Page.
"""
url_path = "bookmarks"
BOOKMARKS_BUTTON_SELECTOR = '.bookmarks-list-button'
BOOKMARKS_ELEMENT_SELECTOR = '#my-bookmarks'
BOOKMARKED_ITEMS_SELECTOR = '.bookmarks-results-list .bookmarks-results-list-item'
BOOKMARKED_BREADCRUMBS = BOOKMARKED_ITEMS_SELECTOR + ' .list-item-breadcrumbtrail'
def is_browser_on_page(self):
""" Verify if we are on correct page """
return self.q(css=self.BOOKMARKS_ELEMENT_SELECTOR).present
def bookmarks_button_visible(self):
""" Check if bookmarks button is visible """
return self.q(css=self.BOOKMARKS_BUTTON_SELECTOR).visible
def results_present(self):
""" Check if bookmarks results are present """
return self.q(css=self.BOOKMARKS_ELEMENT_SELECTOR).present
def results_header_text(self):
""" Returns the bookmarks results header text """
return self.q(css='.bookmarks-results-header').text[0]
def empty_header_text(self):
""" Returns the bookmarks empty header text """
return self.q(css='.bookmarks-empty-header').text[0]
def empty_list_text(self):
""" Returns the bookmarks empty list text """
return self.q(css='.bookmarks-empty-detail-title').text[0]
def count(self):
""" Returns the total number of bookmarks in the list """
return len(self.q(css=self.BOOKMARKED_ITEMS_SELECTOR).results)
def breadcrumbs(self):
""" Return list of breadcrumbs for all bookmarks """
breadcrumbs = self.q(css=self.BOOKMARKED_BREADCRUMBS).text
return [breadcrumb.replace('\n', '').split('-') for breadcrumb in breadcrumbs]
def click_bookmarked_block(self, index):
"""
Click on bookmarked block at index `index`
Arguments:
index (int): bookmark index in the list
"""
self.q(css=self.BOOKMARKED_ITEMS_SELECTOR).nth(index).click()
# -*- coding: utf-8 -*-
"""
Module for Certificates pages.
"""
from bok_choy.page_object import PageObject
from common.test.acceptance.pages.lms import BASE_URL
class CertificatePage(PageObject):
"""
Certificate web view page.
"""
url_path = "certificates"
def __init__(self, browser, user_id, course_id):
"""Initialize the page.
Arguments:
browser (Browser): The browser instance.
user_id: id of the user whom certificate is awarded
course_id: course key of the course where certificate is awarded
"""
super(CertificatePage, self).__init__(browser)
self.user_id = user_id
self.course_id = course_id
def is_browser_on_page(self):
""" Checks if certificate web view page is being viewed """
return self.q(css='section.about-accomplishments').present
@property
def url(self):
"""
Construct a URL to the page
"""
return BASE_URL + "/" + self.url_path + "/user/" + self.user_id + "/course/" + self.course_id
@property
def accomplishment_banner(self):
"""
returns accomplishment banner.
"""
return self.q(css='section.banner-user')
@property
def add_to_linkedin_profile_button(self):
"""
returns add to LinkedIn profile button
"""
return self.q(css='button.action-linkedin-profile')
@property
def add_to_facebook_profile_button(self):
"""
returns Facebook share button
"""
return self.q(css='button.action-share-facebook')
# -*- coding: utf-8 -*-
"""
Mixins for completion.
"""
class CompletionOnViewMixin(object):
"""
Methods for testing completion on view.
"""
def xblock_components_mark_completed_on_view_value(self):
"""
Return the xblock components data-mark-completed-on-view-after-delay value.
"""
return self.q(css=self.xblock_component_selector).attrs('data-mark-completed-on-view-after-delay')
def wait_for_xblock_component_to_be_marked_completed_on_view(self, index=0):
"""
Wait for xblock component to be marked completed on view.
Arguments
index (int): index of block to wait on. (default is 0)
"""
self.wait_for(lambda: (self.xblock_components_mark_completed_on_view_value()[index] == '0'),
'Waiting for xblock to be marked completed on view.')
"""
Course about page (with registration button)
"""
from common.test.acceptance.pages.lms.course_page import CoursePage
from common.test.acceptance.pages.lms.login_and_register import RegisterPage
class CourseAboutPage(CoursePage):
"""
Course about page (with registration button)
"""
url_path = "about"
def is_browser_on_page(self):
return self.q(css='section.course-info').present
def register(self):
"""
Navigate to the registration page.
Waits for the registration page to load, then
returns the registration page object.
"""
self.q(css='a.register').first.click()
registration_page = RegisterPage(self.browser, self.course_id)
registration_page.wait_for_page()
return registration_page
def enroll_in_course(self):
"""
Click on enroll button
"""
self.wait_for_element_visibility('.register', 'Enroll button is present')
self.q(css='.register').first.click()
......@@ -3,15 +3,7 @@ LMS Course Home page object
"""
from collections import OrderedDict
from bok_choy.page_object import PageObject
from bok_choy.promise import BrokenPromise
from six import text_type
from .bookmarks import BookmarksPage
from .course_page import CoursePage
from .courseware import CoursewarePage
from .staff_view import StaffPreviewPage
......@@ -30,316 +22,6 @@ class CourseHomePage(CoursePage):
def __init__(self, browser, course_id):
super(CourseHomePage, self).__init__(browser, course_id)
self.course_id = course_id
self.outline = CourseOutlinePage(browser, self)
self.preview = StaffPreviewPage(browser, self)
# TODO: TNL-6546: Remove the following
self.course_outline_page = False
def select_course_goal(self):
""" Click on a course goal in a message """
self.q(css='button.goal-option').first.click()
self.wait_for_ajax()
def is_course_goal_success_message_shown(self):
""" Verifies course goal success message appears. """
return self.q(css='.success-message').present
def is_course_goal_update_field_shown(self):
""" Verifies course goal success message appears. """
return self.q(css='.current-goal-container').visible
def is_course_goal_update_icon_shown(self, valid=True):
""" Verifies course goal success or error icon appears. """
correct_icon = 'check' if valid else 'close'
return self.q(css='.fa-{icon}'.format(icon=correct_icon)).present
def click_bookmarks_button(self):
""" Click on Bookmarks button """
self.q(css='.bookmarks-list-button').first.click()
bookmarks_page = BookmarksPage(self.browser, self.course_id)
bookmarks_page.visit()
def resume_course_from_header(self):
"""
Navigate to courseware using Resume Course button in the header.
"""
self.q(css=self.HEADER_RESUME_COURSE_SELECTOR).first.click()
courseware_page = CoursewarePage(self.browser, self.course_id)
courseware_page.wait_for_page()
def search_for_term(self, search_term):
"""
Search within a class for a particular term.
"""
self.q(css='.search-form > .search-input').fill(search_term)
self.q(css='.search-form .search-button').click()
return CourseSearchResultsPage(self.browser, self.course_id)
class CourseOutlinePage(PageObject):
"""
Course outline fragment of page.
"""
url = None
def __init__(self, browser, parent_page):
super(CourseOutlinePage, self).__init__(browser)
self.parent_page = parent_page
self._section_selector = '.outline-item.section'
self._subsection_selector = '.subsection.accordion'
def is_browser_on_page(self):
return self.parent_page.is_browser_on_page
@property
def sections(self):
"""
Return a dictionary representation of sections and subsections.
Example:
{
'Introduction': ['Course Overview'],
'Week 1': ['Lesson 1', 'Lesson 2', 'Homework']
'Final Exam': ['Final Exam']
}
You can use these titles in `go_to_section` to navigate to the section.
"""
return self._get_outline_structure_as_dictionary()
@property
def num_sections(self):
"""
Return the number of sections
"""
return len(self._get_sections_as_selenium_webelements())
@property
def num_subsections(self, section_title=None):
"""
Return the number of subsections.
Arguments:
section_title: The section for which to return the number of
subsections. If None, default to the first section.
"""
if section_title:
section_index = self._section_title_to_index(section_title)
if not section_index:
return
else:
section_index = 0
sections = self._get_sections_as_selenium_webelements()
subsections = self._get_subsections(sections[section_index])
return len(subsections)
@property
def num_units(self):
"""
Return the number of units in the first subsection.
This method returns the number of units in the horizontal navigation
bar; not the course outline.
"""
return len(self.q(css='.sequence-list-wrapper ol li'))
def go_to_section(self, section_title, subsection_title):
"""
Go to the section/subsection in the courseware.
Every section must have at least one subsection, so specify
both the section and subsection title.
Example:
go_to_section("Week 1", "Lesson 1")
"""
subsection_webelements = self._get_subsections_as_selenium_webelements()
subsection_titles = [self._get_outline_element_title(sub_webel)
for sub_webel in subsection_webelements]
try:
subsection_index = subsection_titles.index(text_type(subsection_title))
except ValueError:
raise ValueError(u"Could not find subsection '{0}' in section '{1}'".format(
subsection_title, section_title
))
target_subsection = subsection_webelements[subsection_index]
units = self._get_units(target_subsection)
# Click the subsection's first problem and ensure that the page finishes
# reloading
units[0].location_once_scrolled_into_view # pylint: disable=W0104
units[0].click()
self._wait_for_course_section(section_title, subsection_title)
def go_to_section_by_index(self, section_index, subsection_index):
"""
Go to the section/subsection in the courseware.
Every section must have at least one subsection, so specify both the
section and subsection indices.
Arguments:
section_index: A 0-based index of the section to navigate to.
subsection_index: A 0-based index of the subsection to navigate to.
"""
try:
section_title = self._section_titles()[section_index]
except IndexError:
raise ValueError(u"Section index '{0}' is out of range.".format(section_index))
try:
subsection_title = self._subsection_titles(section_index)[subsection_index]
except IndexError:
raise ValueError(u"Subsection index '{0}' in section index '{1}' is out of range.".format(
subsection_index, section_index
))
self.go_to_section(section_title, subsection_title)
def _section_title_to_index(self, section_title):
"""
Get the section title index given the section title.
"""
try:
section_index = self._section_titles().index(section_title)
except ValueError:
raise ValueError(u"Could not find section '{0}'".format(section_title))
return section_index
def resume_course_from_outline(self):
"""
Navigate to courseware using Resume Course button in the header.
"""
self.q(css='.btn.btn-primary.action-resume-course').results[0].click()
courseware_page = CoursewarePage(self.browser, self.parent_page.course_id)
courseware_page.wait_for_page()
def _section_titles(self):
"""
Return a list of all section titles on the page.
"""
outline_sections = self._get_sections_as_selenium_webelements()
section_titles = [self._get_outline_element_title(section) for section in outline_sections]
return section_titles
def _subsection_titles(self, section_index):
"""
Return a list of all subsection titles on the page
for the section at index `section_index` (starts at 0).
"""
outline_sections = self._get_sections_as_selenium_webelements()
target_section = outline_sections[section_index]
target_subsections = self._get_subsections(target_section)
subsection_titles = [self._get_outline_element_title(subsection)
for subsection in target_subsections]
return subsection_titles
def _wait_for_course_section(self, section_title, subsection_title):
"""
Ensures the user navigates to the course content page with the correct section and
subsection.
"""
courseware_page = CoursewarePage(self.browser, self.parent_page.course_id)
courseware_page.wait_for_page()
# TODO: TNL-6546: Remove this if/visit_course_outline_page
if self.parent_page.course_outline_page:
courseware_page.nav.visit_course_outline_page()
self.wait_for(
promise_check_func=lambda: courseware_page.nav.is_on_section(
section_title, subsection_title),
description=u"Waiting for course page with section '{0}' and subsection '{1}'".format(
section_title, subsection_title)
)
def _get_outline_structure_as_dictionary(self):
'''
Implements self.sections().
'''
outline_dict = OrderedDict()
try:
outline_sections = self._get_sections_as_selenium_webelements()
except BrokenPromise:
outline_sections = []
for section in outline_sections:
subsections = self._get_subsections(section)
section_title = self._get_outline_element_title(section)
subsection_titles = [self._get_outline_element_title(subsection)
for subsection in subsections]
outline_dict[section_title] = subsection_titles
return outline_dict
@staticmethod
def _is_html_element_aria_expanded(html_element):
return html_element.get_attribute('aria-expanded') == u'true'
@staticmethod
def _get_outline_element_title(outline_element):
return outline_element.text.split('\n')[0]
def _get_subsections(self, section):
self._expand_all_outline_folds()
return section.find_elements_by_css_selector(self._subsection_selector)
def _get_units(self, subsection):
self._expand_all_outline_folds()
return subsection.find_elements_by_tag_name('a')
def _get_sections_as_selenium_webelements(self):
self._expand_all_outline_folds()
return self.q(css=self._section_selector).results
def _get_subsections_as_selenium_webelements(self):
self._expand_all_outline_folds()
return self.q(css=self._subsection_selector).results
def get_subsection_due_date(self, index=0):
"""
Get the due date for the given index sub-section on the LMS outline.
"""
results = self.q(css='div.details > span.subtitle > span.subtitle-name').results
return results[index].text if results else None
def _expand_all_outline_folds(self):
'''
Expands all parts of the collapsible outline.
'''
expand_button_search_results = self.q(
css='#expand-collapse-outline-all-button'
).results
if not expand_button_search_results:
return
expand_button = expand_button_search_results[0]
if not self._is_html_element_aria_expanded(expand_button):
expand_button.click()
class CourseSearchResultsPage(CoursePage):
"""
Course search page
"""
# url = "courses/{course_id}/search/?query={query_string}"
def is_browser_on_page(self):
return self.q(css='.page-content > .search-results').present
def __init__(self, browser, course_id):
super(CourseSearchResultsPage, self).__init__(browser, course_id)
self.course_id = course_id
@property
def search_results(self):
return self.q(css='.search-results-item')
"""
Course info page.
"""
from common.test.acceptance.pages.lms.course_page import CoursePage
class CourseInfoPage(CoursePage):
"""
Course info.
"""
url_path = "info"
def is_browser_on_page(self):
return self.q(css='section.updates').present
@property
def num_updates(self):
"""
Return the number of updates on the page.
"""
return len(self.q(css='.updates .updates-article').results)
@property
def handout_links(self):
"""
Return a list of handout assets links.
"""
return self.q(css='section.handouts ol li a').map(lambda el: el.get_attribute('href')).results
This diff is collapsed.
"""
Courseware search
"""
from common.test.acceptance.pages.lms.course_page import CoursePage
class CoursewareSearchPage(CoursePage):
"""
Coursware page featuring a search form
"""
url_path = "courseware/"
search_bar_selector = '#courseware-search-bar'
search_results_selector = '.courseware-results'
@property
def search_results(self):
""" search results list showing """
return self.q(css=self.search_results_selector)
def is_browser_on_page(self):
""" did we find the search bar in the UI """
return self.q(css=self.search_bar_selector).present
def enter_search_term(self, text):
""" enter the search term into the box """
self.q(css=self.search_bar_selector + ' input[type="text"]').fill(text)
def search(self):
""" execute the search """
self.q(css=self.search_bar_selector + ' [type="submit"]').click()
self.wait_for_ajax()
self.wait_for_element_visibility(self.search_results_selector, 'Search results are visible')
def search_for_term(self, text):
"""
Fill input and do search
"""
self.enter_search_term(text)
self.search()
def clear_search(self):
"""
Clear search bar after search.
"""
self.q(css=self.search_bar_selector + ' .cancel-button').click()
self.wait_for_element_visibility('#course-content', 'Search bar is cleared')
"""Mode creation page (used to add modes to courses during testing)."""
import re
import six.moves.urllib.parse
from bok_choy.page_object import PageObject
from common.test.acceptance.pages.lms import BASE_URL
class ModeCreationPage(PageObject):
"""The mode creation page.
When allowed by the Django settings file, visiting this page allows modes to be
created for an existing course.
"""
def __init__(self, browser, course_id, mode_slug=None, mode_display_name=None, min_price=None,
suggested_prices=None, currency=None, sku=None):
"""The mode creation page is an endpoint for HTTP GET requests.
By default, it will create an 'honor' mode for the given course with display name
'Honor Code', a minimum price of 0, no suggested prices, and using USD as the currency.
Arguments:
browser (Browser): The browser instance.
course_id (unicode): The ID of the course for which modes are to be created.
Keyword Arguments:
mode_slug (str): The mode to add, either 'honor', 'verified', or 'professional'
mode_display_name (str): Describes the new course mode
min_price (int): The minimum price a user must pay to enroll in the new course mode
suggested_prices (str): Comma-separated prices to suggest to the user.
currency (str): The currency in which to list prices.
sku (str): The product SKU value.
"""
super(ModeCreationPage, self).__init__(browser)
self._course_id = course_id
self._parameters = {}
if mode_slug is not None:
self._parameters['mode_slug'] = mode_slug
if mode_display_name is not None:
self._parameters['mode_display_name'] = mode_display_name
if min_price is not None:
self._parameters['min_price'] = min_price
if suggested_prices is not None:
self._parameters['suggested_prices'] = suggested_prices
if currency is not None:
self._parameters['currency'] = currency
if sku is not None:
self._parameters['sku'] = sku
@property
def url(self):
"""Construct the mode creation URL."""
url = '{base}/course_modes/create_mode/{course_id}/'.format(
base=BASE_URL,
course_id=self._course_id
)
query_string = six.moves.urllib.parse.urlencode(self._parameters)
if query_string:
url += '?' + query_string
return url
def is_browser_on_page(self):
message = self.q(css='BODY').text[0]
match = re.search(r'Mode ([^$]+) created for ([^$]+).$', message)
return True if match else False
......@@ -5,8 +5,6 @@ Student dashboard page.
from bok_choy.page_object import PageObject
from opaque_keys.edx.keys import CourseKey
from six.moves import range
from common.test.acceptance.pages.lms import BASE_URL
......@@ -21,223 +19,8 @@ class DashboardPage(PageObject):
def is_browser_on_page(self):
return self.q(css='.my-courses').present
@property
def current_courses_text(self):
"""
This is the title label for the section of the student dashboard that
shows all the courses that the student is enrolled in.
The string displayed is defined in lms/templates/dashboard.html.
"""
text_items = self.q(css='#my-courses').text
if len(text_items) > 0:
return text_items[0]
else:
return ""
@property
def available_courses(self):
"""
Return list of the names of available courses (e.g. "999 edX Demonstration Course")
"""
def _get_course_name(el):
return el.text
return self.q(css='h3.course-title > a').map(_get_course_name).results
@property
def banner_text(self):
"""
Return the text of the banner on top of the page, or None if
the banner is not present.
"""
message = self.q(css='div.wrapper-msg')
if message.present:
return message.text[0]
return None
def get_enrollment_mode(self, course_name):
"""Get the enrollment mode for a given course on the dashboard.
Arguments:
course_name (str): The name of the course whose mode should be retrieved.
Returns:
String, indicating the enrollment mode for the course corresponding to
the provided course name.
Raises:
Exception, if no course with the provided name is found on the dashboard.
"""
# Filter elements by course name, only returning the relevant course item
course_listing = self.q(css=".course").filter(lambda el: course_name in el.text).results
if course_listing:
# There should only be one course listing for the provided course name.
# Since 'ENABLE_VERIFIED_CERTIFICATES' is true in the Bok Choy settings, we
# can expect two classes to be present on <article> elements, one being 'course'
# and the other being the enrollment mode.
enrollment_mode = course_listing[0].get_attribute('class').split('course ')[1]
else:
raise Exception(u"No course named {} was found on the dashboard".format(course_name))
return enrollment_mode
def view_course(self, course_id):
"""
Go to the course with `course_id` (e.g. edx/Open_DemoX/edx_demo_course)
"""
link_css = self._link_css(course_id)
if link_css is not None:
self.q(css=link_css).first.click()
else:
msg = u"No links found for course {0}".format(course_id)
self.warning(msg)
def _link_css(self, course_id):
"""
Return a CSS selector for the link to the course with `course_id`.
"""
# Get the link hrefs for all courses
all_links = self.q(css='a.enter-course').map(lambda el: el.get_attribute('href')).results
# Search for the first link that matches the course id
link_index = None
for index in range(len(all_links)):
if course_id in all_links[index]:
link_index = index
break
if link_index is not None:
return "a.enter-course:nth-of-type({0})".format(link_index + 1)
else:
return None
def view_course_unenroll_dialog_message(self, course_id):
"""
Go to the course unenroll dialog message for `course_id` (e.g. edx/Open_DemoX/edx_demo_course)
"""
div_index = self.get_course_actions_link_css(course_id)
button_link_css = "#actions-dropdown-link-{}".format(div_index)
unenroll_css = "#unenroll-{}".format(div_index)
if button_link_css is not None:
self.q(css=button_link_css).first.click()
self.wait_for_element_visibility(unenroll_css, 'Unenroll message dialog is visible.')
self.q(css=unenroll_css).first.click()
self.wait_for_ajax()
return {
'track-info': self.q(css='#track-info').html,
'refund-info': self.q(css='#refund-info').html
}
else:
msg = u"No links found for course {0}".format(course_id)
self.warning(msg)
def get_course_actions_link_css(self, course_id):
"""
Return a index for unenroll button with `course_id`.
"""
# Get the link hrefs for all courses
all_divs = self.q(css='div.wrapper-action-more').map(lambda el: el.get_attribute('data-course-key')).results
# Search for the first link that matches the course id
div_index = None
for index in range(len(all_divs)):
if course_id in all_divs[index]:
div_index = index
break
return div_index
def pre_requisite_message_displayed(self):
"""
Verify if pre-requisite course messages are being displayed.
"""
return self.q(css='div.prerequisites > .tip').visible
def get_course_listings(self):
"""Retrieve the list of course DOM elements"""
return self.q(css='ul.listing-courses')
def get_course_social_sharing_widget(self, widget_name):
""" Retrieves the specified social sharing widget by its classification """
return self.q(css='a.action-{}'.format(widget_name))
def get_profile_img(self):
""" Retrieves the user's profile image """
return self.q(css='img.user-image-frame')
def get_courses(self):
"""
Get all courses shown in the dashboard
"""
return self.q(css='ul.listing-courses .course-item')
def get_course_date(self):
"""
Get course date of the first course from dashboard
"""
return self.q(css='ul.listing-courses .course-item:first-of-type .info-date-block').first.text[0]
def click_username_dropdown(self):
"""
Click username dropdown.
"""
self.q(css='.toggle-user-dropdown').first.click()
@property
def username_dropdown_link_text(self):
"""
Return list username dropdown links.
"""
return self.q(css='.dropdown-user-menu a').text
@property
def tabs_link_text(self):
"""
Return the text of all the tabs on the dashboard.
"""
return self.q(css='.nav-tab a').text
def click_my_profile_link(self):
"""
Click on `Profile` link.
"""
self.q(css='.nav-tab a').nth(1).click()
def click_account_settings_link(self):
"""
Click on `Account` link.
"""
self.q(css='.dropdown-user-menu a').nth(1).click()
@property
def language_selector(self):
"""
return language selector
"""
self.wait_for_element_visibility(
'#settings-language-value',
'Language selector element is available'
)
return self.q(css='#settings-language-value')
def is_course_present(self, course_id):
"""
Checks whether course is present or not.
Arguments:
course_id(str): The unique course id.
Returns:
bool: True if the course is present.
"""
course_number = CourseKey.from_string(course_id).course
return self.q(
css='#actions-dropdown-link-0[data-course-number="{}"]'.format(
course_number
)
).present
"""
Course discovery page.
"""
from bok_choy.page_object import PageObject
from common.test.acceptance.pages.lms import BASE_URL
class CourseDiscoveryPage(PageObject):
"""
Find courses page (main page of the LMS).
"""
url = BASE_URL + "/courses"
form = "#discovery-form"
def is_browser_on_page(self):
"""
Loading indicator must be present, but not visible
"""
loading_css = "#loading-indicator"
courses_css = '.courses-listing'
return self.q(css=courses_css).visible \
and self.q(css=loading_css).present \
and not self.q(css=loading_css).visible
@property
def result_items(self):
"""
Return search result items.
"""
return self.q(css=".courses-list .courses-listing-item")
@property
def clear_button(self):
"""
Clear all button.
"""
return self.q(css="#clear-all-filters")
def search(self, string):
"""
Search and wait for ajax.
"""
self.q(css=self.form + ' input[type="text"]').fill(string)
self.q(css=self.form + ' [type="submit"]').click()
self.wait_for_ajax()
def clear_search(self):
"""
Clear search results.
"""
self.clear_button.click()
self.wait_for_ajax()
def click_course(self, course_id):
"""
Click on the course
Args:
course_id(string): ID of the course which is to be clicked
"""
self.q(css='.courses-listing-item a').filter(lambda el: course_id in el.get_attribute('href')).click()
This diff is collapsed.
"""
LMS edxnotes page
"""
from bok_choy.page_object import PageLoadError, PageObject, unguarded
from bok_choy.promise import BrokenPromise, EmptyPromise
from selenium.webdriver.common.action_chains import ActionChains
from common.test.acceptance.pages.common.paging import PaginatedUIMixin
from common.test.acceptance.pages.lms.course_page import CoursePage
from common.test.acceptance.tests.helpers import disable_animations
class NoteChild(PageObject):
url = None
BODY_SELECTOR = None
def __init__(self, browser, item_id):
super(NoteChild, self).__init__(browser)
self.item_id = item_id
def is_browser_on_page(self):
return self.q(css="{}#{}".format(self.BODY_SELECTOR, self.item_id)).present
def _bounded_selector(self, selector):
"""
Return `selector`, but limited to this particular `NoteChild` context
"""
return u"{}#{} {}".format(
self.BODY_SELECTOR,
self.item_id,
selector,
)
def _get_element_text(self, selector):
element = self.q(css=self._bounded_selector(selector)).first
if element:
return element.text[0]
else:
return None
class EdxNotesChapterGroup(NoteChild):
"""
Helper class that works with chapter (section) grouping of notes in the Course Structure view on the Note page.
"""
BODY_SELECTOR = ".note-group"
@property
def title(self):
return self._get_element_text(".course-title")
@property
def subtitles(self):
return [section.title for section in self.children]
@property
def children(self):
children = self.q(css=self._bounded_selector('.note-section'))
return [EdxNotesSubsectionGroup(self.browser, child.get_attribute("id")) for child in children]
class EdxNotesGroupMixin(object):
"""
Helper mixin that works with note groups (used for subsection and tag groupings).
"""
@property
def title(self):
return self._get_element_text(self.TITLE_SELECTOR)
@property
def children(self):
children = self.q(css=self._bounded_selector('.note'))
return [EdxNotesPageItem(self.browser, child.get_attribute("id")) for child in children]
@property
def notes(self):
return [section.text for section in self.children]
class EdxNotesSubsectionGroup(NoteChild, EdxNotesGroupMixin):
"""
Helper class that works with subsection grouping of notes in the Course Structure view on the Note page.
"""
BODY_SELECTOR = ".note-section"
TITLE_SELECTOR = ".course-subtitle"
class EdxNotesTagsGroup(NoteChild, EdxNotesGroupMixin):
"""
Helper class that works with tags grouping of notes in the Tags view on the Note page.
"""
BODY_SELECTOR = ".note-group"
TITLE_SELECTOR = ".tags-title"
def scrolled_to_top(self, group_index):
"""
Returns True if the group with supplied group)index is scrolled near the top of the page
(expects 10 px padding).
The group_index must be supplied because JQuery must be used to get this information, and it
does not have access to the bounded selector.
"""
title_selector = "$('" + self.TITLE_SELECTOR + "')[" + str(group_index) + "]"
top_script = "return " + title_selector + ".getBoundingClientRect().top;"
EmptyPromise(
lambda: 8 < self.browser.execute_script(top_script) < 12,
u"Expected tag title '{}' to scroll to top, but was at location {}".format(
self.title, self.browser.execute_script(top_script)
)
).fulfill()
# Now also verify that focus has moved to this title (for screen readers):
active_script = "return " + title_selector + " === document.activeElement;"
return self.browser.execute_script(active_script)
class EdxNotesPageItem(NoteChild):
"""
Helper class that works with note items on Note page of the course.
"""
BODY_SELECTOR = ".note"
UNIT_LINK_SELECTOR = "a.reference-unit-link"
TAG_SELECTOR = "span.reference-tags"
def go_to_unit(self, unit_page=None):
self.q(css=self._bounded_selector(self.UNIT_LINK_SELECTOR)).click()
if unit_page is not None:
unit_page.wait_for_page()
@property
def unit_name(self):
return self._get_element_text(self.UNIT_LINK_SELECTOR)
@property
def text(self):
return self._get_element_text(".note-comment-p")
@property
def quote(self):
return self._get_element_text(".note-excerpt")
@property
def time_updated(self):
return self._get_element_text(".reference-updated-date")
@property
def tags(self):
""" The tags associated with this note. """
tag_links = self.q(css=self._bounded_selector(self.TAG_SELECTOR))
if len(tag_links) == 0:
return None
return[tag_link.text for tag_link in tag_links]
def go_to_tag(self, tag_name):
""" Clicks a tag associated with the note to change to the tags view (and scroll to the tag group). """
self.q(css=self._bounded_selector(self.TAG_SELECTOR)).filter(lambda el: tag_name in el.text).click()
class EdxNotesPageView(PageObject):
"""
Base class for EdxNotes views: Recent Activity, Location in Course, Search Results.
"""
url = None
BODY_SELECTOR = ".tab-panel"
TAB_SELECTOR = ".tab"
CHILD_SELECTOR = ".note"
CHILD_CLASS = EdxNotesPageItem
@unguarded
def visit(self):
"""
Open the page containing this page object in the browser.
Raises:
PageLoadError: The page did not load successfully.
Returns:
PageObject
"""
self.q(css=self.TAB_SELECTOR).first.click()
try:
return self.wait_for_page()
except BrokenPromise:
raise PageLoadError(u"Timed out waiting to load page '{!r}'".format(self))
def is_browser_on_page(self):
return all([
self.q(css="{}".format(self.BODY_SELECTOR)).present,
self.q(css="{}.is-active".format(self.TAB_SELECTOR)).present,
not self.q(css=".ui-loading").visible,
])
@property
def is_closable(self):
"""
Indicates if tab is closable or not.
"""
return self.q(css=u"{} .action-close".format(self.TAB_SELECTOR)).present
def close(self):
"""
Closes the tab.
"""
self.q(css=u"{} .action-close".format(self.TAB_SELECTOR)).first.click()
@property
def children(self):
"""
Returns all notes on the page.
"""
children = self.q(css=self.CHILD_SELECTOR)
return [self.CHILD_CLASS(self.browser, child.get_attribute("id")) for child in children]
class RecentActivityView(EdxNotesPageView):
"""
Helper class for Recent Activity view.
"""
BODY_SELECTOR = "#recent-panel"
TAB_SELECTOR = ".tab#view-recent-activity"
class CourseStructureView(EdxNotesPageView):
"""
Helper class for Location in Course view.
"""
BODY_SELECTOR = "#structure-panel"
TAB_SELECTOR = ".tab#view-course-structure"
CHILD_SELECTOR = ".note-group"
CHILD_CLASS = EdxNotesChapterGroup
class TagsView(EdxNotesPageView):
"""
Helper class for Tags view.
"""
BODY_SELECTOR = "#tags-panel"
TAB_SELECTOR = ".tab#view-tags"
CHILD_SELECTOR = ".note-group"
CHILD_CLASS = EdxNotesTagsGroup
class SearchResultsView(EdxNotesPageView):
"""
Helper class for Search Results view.
"""
BODY_SELECTOR = "#search-results-panel"
TAB_SELECTOR = ".tab#view-search-results"
class EdxNotesPage(CoursePage, PaginatedUIMixin):
"""
EdxNotes page.
"""
url_path = "edxnotes/"
MAPPING = {
"recent": RecentActivityView,
"structure": CourseStructureView,
"tags": TagsView,
"search": SearchResultsView,
}
def __init__(self, *args, **kwargs):
super(EdxNotesPage, self).__init__(*args, **kwargs)
self.current_view = self.MAPPING["recent"](self.browser)
def is_browser_on_page(self):
return self.q(css=".wrapper-student-notes .note-group").visible
def switch_to_tab(self, tab_name):
"""
Switches to the appropriate tab `tab_name(str)`.
"""
self.current_view = self.MAPPING[tab_name](self.browser)
self.current_view.visit()
def close_tab(self):
"""
Closes the current view.
"""
self.current_view.close()
self.current_view = self.MAPPING["recent"](self.browser)
def search(self, text):
"""
Runs search with `text(str)` query.
"""
self.q(css="#search-notes-form #search-notes-input").first.fill(text)
self.q(css='#search-notes-form .search-notes-submit').first.click()
# Frontend will automatically switch to Search results tab when search
# is running, so the view also needs to be changed.
self.current_view = self.MAPPING["search"](self.browser)
if text.strip():
self.current_view.wait_for_page()
@property
def tabs(self):
"""
Returns all tabs on the page.
"""
tabs = self.q(css=".tabs .tab-label")
if tabs:
return [x.replace("Current tab\n", "") for x in tabs.text]
else:
return None
@property
def is_error_visible(self):
"""
Indicates whether error message is visible or not.
"""
return self.q(css=".inline-error").visible
@property
def error_text(self):
"""
Returns error message.
"""
element = self.q(css=".inline-error").first
if element and self.is_error_visible:
return element.text[0]
else:
return None
@property
def notes(self):
"""
Returns all notes on the page.
"""
children = self.q(css='.note')
return [EdxNotesPageItem(self.browser, child.get_attribute("id")) for child in children]
@property
def chapter_groups(self):
"""
Returns all chapter groups on the page.
"""
children = self.q(css='.note-group')
return [EdxNotesChapterGroup(self.browser, child.get_attribute("id")) for child in children]
@property
def subsection_groups(self):
"""
Returns all subsection groups on the page.
"""
children = self.q(css='.note-section')
return [EdxNotesSubsectionGroup(self.browser, child.get_attribute("id")) for child in children]
@property
def tag_groups(self):
"""
Returns all tag groups on the page.
"""
children = self.q(css='.note-group')
return [EdxNotesTagsGroup(self.browser, child.get_attribute("id")) for child in children]
def count(self):
""" Returns the total number of notes in the list """
return len(self.q(css='div.wrapper-note-excerpts').results)
class EdxNoteHighlight(NoteChild):
"""
Helper class that works with notes.
"""
BODY_SELECTOR = ""
ADDER_SELECTOR = ".annotator-adder"
VIEWER_SELECTOR = ".annotator-viewer"
EDITOR_SELECTOR = ".annotator-editor"
NOTE_SELECTOR = ".annotator-note"
def __init__(self, browser, element, parent_id):
super(EdxNoteHighlight, self).__init__(browser, parent_id)
self.element = element
self.item_id = parent_id
disable_animations(self)
@property
def is_visible(self):
"""
Returns True if the note is visible.
"""
viewer_is_visible = self.q(css=self._bounded_selector(self.VIEWER_SELECTOR)).visible
editor_is_visible = self.q(css=self._bounded_selector(self.EDITOR_SELECTOR)).visible
return viewer_is_visible or editor_is_visible
def wait_for_adder_visibility(self):
"""
Waiting for visibility of note adder button.
"""
self.wait_for_element_visibility(
self._bounded_selector(self.ADDER_SELECTOR), "Adder is visible."
)
def wait_for_viewer_visibility(self):
"""
Waiting for visibility of note viewer.
"""
self.wait_for_element_visibility(
self._bounded_selector(self.VIEWER_SELECTOR), "Note Viewer is visible."
)
def wait_for_editor_visibility(self):
"""
Waiting for visibility of note editor.
"""
self.wait_for_element_visibility(
self._bounded_selector(self.EDITOR_SELECTOR), "Note Editor is visible."
)
def wait_for_notes_invisibility(self, text="Notes are hidden"):
"""
Waiting for invisibility of all notes.
"""
selector = self._bounded_selector(".annotator-outer")
self.wait_for_element_invisibility(selector, text)
def select_and_click_adder(self):
"""
Creates selection for the element and clicks `add note` button.
"""
ActionChains(self.browser).double_click(self.element).perform()
self.wait_for_adder_visibility()
self.q(css=self._bounded_selector(self.ADDER_SELECTOR)).first.click()
self.wait_for_editor_visibility()
return self
def click_on_highlight(self):
"""
Clicks on the highlighted text.
"""
ActionChains(self.browser).move_to_element(self.element).click().perform()
return self
def click_on_viewer(self):
"""
Clicks on the note viewer.
"""
self.q(css=self.NOTE_SELECTOR).first.click()
return self
def show(self):
"""
Hover over highlighted text -> shows note.
"""
ActionChains(self.browser).move_to_element(self.element).perform()
self.wait_for_viewer_visibility()
return self
def cancel(self):
"""
Clicks cancel button.
"""
self.q(css=self._bounded_selector(".annotator-close")).first.click()
self.wait_for_notes_invisibility("Note is canceled.")
return self
def save(self):
"""
Clicks save button.
"""
self.q(css=self._bounded_selector(".annotator-save")).first.click()
self.wait_for_notes_invisibility("Note is saved.")
self.wait_for_ajax()
return self
def remove(self):
"""
Clicks delete button.
"""
self.q(css=self._bounded_selector(".annotator-delete")).first.click()
self.wait_for_notes_invisibility("Note is removed.")
self.wait_for_ajax()
return self
def edit(self):
"""
Clicks edit button.
"""
self.q(css=self._bounded_selector(".annotator-edit")).first.click()
self.wait_for_editor_visibility()
return self
@property
def text(self):
"""
Returns text of the note.
"""
self.show()
element = self.q(css=self._bounded_selector(".annotator-annotation > div.annotator-note"))
if element:
text = element.text[0].strip()
else:
text = None
self.cancel()
return text
@text.setter
def text(self, value):
"""
Sets text for the note.
"""
self.q(css=self._bounded_selector(".annotator-item textarea")).first.fill(value)
@property
def tags(self):
"""
Returns the tags associated with the note.
Tags are returned as a list of strings, with each tag as an individual string.
"""
tag_text = []
self.show()
tags = self.q(css=self._bounded_selector(".annotator-annotation > div.annotator-tags > span.annotator-tag"))
if tags:
for tag in tags:
tag_text.append(tag.text)
self.cancel()
return tag_text
@tags.setter
def tags(self, tags):
"""
Sets tags for the note. Tags should be supplied as a list of strings, with each tag as an individual string.
"""
self.q(css=self._bounded_selector(".annotator-item input")).first.fill(" ".join(tags))
def has_sr_label(self, sr_index, field_index, expected_text):
"""
Returns true iff a screen reader label (of index sr_index) exists for the annotator field with
the specified field_index and text.
"""
label_exists = False
EmptyPromise(
lambda: len(self.q(css=self._bounded_selector("li.annotator-item > label.sr"))) > sr_index,
u"Expected more than '{}' sr labels".format(sr_index)
).fulfill()
annotator_field_label = self.q(css=self._bounded_selector("li.annotator-item > label.sr"))[sr_index]
for_attrib_correct = annotator_field_label.get_attribute("for") == "annotator-field-" + str(field_index)
if for_attrib_correct and (annotator_field_label.text == expected_text):
label_exists = True
self.q(css="body").first.click()
self.wait_for_notes_invisibility()
return label_exists
# -*- coding: utf-8 -*-
"""
LMS index (home) page.
"""
from bok_choy.page_object import PageObject
from common.test.acceptance.pages.lms import BASE_URL
BANNER_SELECTOR = 'section.home header div.outer-wrapper div.title .heading-group h1'
INTRO_VIDEO_SELECTOR = 'div.play-intro'
VIDEO_MODAL_SELECTOR = 'section#video-modal.modal.home-page-video-modal.video-modal'
class IndexPage(PageObject):
"""
LMS index (home) page, the default landing page for Open edX users when they are not logged in
"""
url = "{base}/".format(base=BASE_URL)
def is_browser_on_page(self):
"""
Returns a browser query object representing the video modal element
"""
element = self.q(css=BANNER_SELECTOR)
return element.visible and element.text[0].startswith("Welcome to ")
@property
def banner_element(self):
"""
Returns a browser query object representing the video modal element
"""
return self.q(css=BANNER_SELECTOR)
@property
def intro_video_element(self):
"""
Returns a browser query object representing the video modal element
"""
return self.q(css=INTRO_VIDEO_SELECTOR)
@property
def video_modal_element(self):
"""
Returns a browser query object representing the video modal element
"""
return self.q(css=VIDEO_MODAL_SELECTOR)
@property
def footer_links(self):
"""Return a list of the text of the links in the page footer."""
return self.q(css='.nav-colophon a').attrs('text')
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment