"""The Tutor reference view of the textbook."""
from time import sleep
from pypom import Page, Region
from selenium.webdriver.common.by import By
from regions.tutor.print_preview import PrintPreview
from utils.utilities import Utility, go_to_
[docs]class ReferenceBook(Page):
"""The complete textbook reading experience."""
_nav_bar_locator = (By.CSS_SELECTOR, '.tutor-navbar')
_table_of_contents_locator = (By.CSS_SELECTOR, '.book-menu')
_content_locator = (By.CSS_SELECTOR, '.book-page-wrapper')
_highlights_summary_locator = (By.CSS_SELECTOR, '.notes-summary')
@property
def nav(self):
"""Access the reference book navigation bar.
:return: the reference book navigation bar region
:rtype: :py:class:`~ReferenceBook.Nav`
"""
nav_root = self.find_element(*self._nav_bar_locator)
return self.Nav(self, nav_root)
@property
def table_of_contents(self):
"""Access the table of contents.
:return: the table of contents for the book
:rtype: :py:class:`~ReferenceBook.TableOfContents`
"""
toc_root = self.find_element(*self._table_of_contents_locator)
return self.TableOfContents(self, toc_root)
@property
def content(self):
"""Access the currently selected section's book content.
:return: the current section content
:rtype: :py:class:`~ReferenceBook.Content`
"""
content_root = self.find_element(*self._content_locator)
return self.Content(self, content_root)
@property
def highlights(self):
"""Access the highlighting summary page.
:return: the highlight/annotation summary page
:rtype: :py:class:`~ReferenceBook.Highlights`
"""
annotation_root = self.find_element(*self._highlights_summary_locator)
return self.Highlights(self, annotation_root)
[docs] class Nav(Region):
"""The reference book navigation controls."""
_menu_toggle_locator = (By.CSS_SELECTOR, '.book-menu-toggle')
_section_title_locator = (By.CSS_SELECTOR, '.section-title span')
_notes_toggle_locator = (By.CSS_SELECTOR, '.note-summary-toggle')
_teacher_content_locator = (By.CSS_SELECTOR, '.teacher-content-toggle')
_pop_over_content_selector = '.popover-body'
@property
def menu_toggle(self):
r"""Return the table of contents toggle button.
:return: the open/close toggle for the table of contents
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
return self.find_element(*self._menu_toggle_locator)
@property
def menu_is_open(self):
"""Return True if the ToC is open.
:return: ``True`` if the table of contents is currently displayed,
else ``False``
:rtype: bool
"""
return 'open' in self.menu_toggle.get_attribute('class')
@property
def section_title(self):
"""Return the currently displayed section title.
:return: the title for the currently selected section
:rtype: str
"""
return self.find_element(*self._section_title_locator).text
@property
def notes_summary_toggle(self):
r"""Return the highlighting summary toggle button.
:return: the open/close toggle for the highlighting summary
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
return self.find_element(*self._notes_toggle_locator)
@property
def note_summary_is_open(self):
"""Return True if the highlight summary page is visible.
:return: ``True`` if the highlighting summary page is visible, else
``False``
:rtype: bool
"""
return 'active' in self.notes_summary_toggle.get_attribute('class')
[docs] def click_notes_summary_toggle(self):
"""Click on the highlight summary page toggle.
:return: the reference book
:rtype: :py:class:`ReferenceBook`
"""
Utility.click_option(self.driver,
element=self.notes_summary_toggle)
return self.page
@property
def is_teacher(self):
"""Return True if the instructor toggle is available.
:return: ``True`` if the user is an instructor, else ``False``
:rtype: bool
"""
return bool(self.find_elements(*self._teacher_content_locator))
@property
def teacher_content_toggle(self):
r"""Return the instructor content display toggle.
:return: the activate/deactivate toggle for instructor content or
None for a student
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
if self.is_teacher:
return self.find_element(*self._teacher_content_locator)
return None
[docs] def show_teacher_content(self):
"""Click on the teacher content toggle.
:return: the reference book
:rtype: :py:class:`ReferenceBook`
"""
toggle = self.teacher_content_toggle
if toggle:
Utility.click_option(self.driver, element=toggle)
return self.page
@property
def popover_content(self):
"""Return the pop over text if found or a blank string.
:return: the content of a pop over box or a blank string if no pop
over is present
:rtype: str
"""
if self.is_teacher:
script = 'return document.querySelector("arguments[0]");'
content = self.driver.execute_script(
script, self._pop_over_content_selector)
if content:
return content.text
return ''
[docs] class TableOfContents(Region):
"""The reference book table of contents."""
_chapter_locator = (By.CSS_SELECTOR, 'ul[data-depth="1"]')
@property
def chapters(self):
"""Access the individual book chapters.
:return: a list of available book chapters
:rtype: list(:py:class:`~ReferenceBook.TableOfContents.Chapter`)
"""
return [self.Chapter(self, chapter)
for chapter
in self.find_elements(*self._chapter_locator)]
[docs] class Chapter(Region):
"""A single book chapter list in the table of contents."""
_title_locator = (
By.CSS_SELECTOR, '.chapter-section-title span:last-child')
_chapter_number_locator = (By.CSS_SELECTOR, '.section-number')
_section_locator = (By.CSS_SELECTOR, 'li:not(:first-child)')
@property
def title(self):
"""Return the chapter title.
:return: the book chapter title
:rtype: str
"""
return self.find_element(*self._title_locator).text
@property
def number(self):
"""Return the chapter number.
:return: the book chapter number
:rtype: str
"""
return self.find_element(*self._chapter_number_locator).text
@property
def sections(self):
r"""Access the chapter sections.
:return: a list of sections within the chapter
:rtype: list(:py:class: \
`~ReferenceBook.TableOfContents.Chapter.Section`)
"""
return [self.Section(self, section)
for section
in self.find_elements(*self._section_locator)]
[docs] class Section(Region):
"""A single book section including unnumbered pieces."""
_title_locator = (By.CSS_SELECTOR, 'span:not(.section-number)')
_display_line_locator = (By.CSS_SELECTOR, 'span')
_link_locator = (By.CSS_SELECTOR, 'a')
@property
def section_id(self):
"""Return the section identification.
:return: the section identification
:rtype: str
"""
return self.root.get_attribute('data-section')
@property
def title(self):
"""Return the section title.
:return: the section title
:rtype: str
"""
return self.find_element(*self._title_locator).text
@property
def display_line(self):
"""Return the entire section line text.
:return: the merged section number and section title text
as displayed
:rtype: str
"""
return (' '.join([
segment.text
for segment
in self.find_elements(*self._display_line_locator)]))
[docs] def view_section(self):
"""Click the section link.
:return: the reference book with the selected section in
the content region
:rtype: :py:class:`ReferenceBook`
"""
link = self.find_element(*self._link_locator)
Utility.click_option(self.driver, element=link)
return self.page.page.page
[docs] class Content(Region):
"""The selected section's content."""
_next_page_locator = (By.CSS_SELECTOR, '.next')
_previous_page_locator = (By.CSS_SELECTOR, '.prev')
_content_locator = (By.CSS_SELECTOR, '#paged-content')
_page_data_locator = (By.CSS_SELECTOR, '.ecosystem-info')
@property
def next_page_arrow(self):
r"""Return the next page arrow button.
:return: the next page button
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
return self.find_element(*self._next_page_locator)
[docs] def next(self):
"""Click on the next page button.
:return: the reference book with the next section in the content
region if not at the book's end
:rtype: :py:class:`ReferenceBook`
"""
Utility.click_option(self.driver, element=self.next_page_arrow)
return self.page
@property
def previous_page_arrow(self):
r"""Return the previous page arrow button.
:return: the previous page button
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
return self.find_element(*self._previous_page_locator)
[docs] def previous(self):
"""Click on the previous page button.
:return: the reference book with the previous section in the
content region if not at the book's beginning
:rtype: :py:class:`ReferenceBook`
"""
Utility.click_option(self.driver, element=self.previous_page_arrow)
return self.page
@property
def content(self):
"""Return the HTML book content.
:return: the full HTML markup of the current section content
:rtype: str
"""
return (self.find_element(*self._content_locator)
.get_attribute('innerHTML'))
@property
def page_data(self):
"""Return the ecosystem page data.
:return: the current section ecosystem data
:rtype: str
"""
return self.find_element(*self._page_data_locator).text
[docs] class Highlights(Region):
"""The highlighting summary page overlay."""
_description_locator = (
By.CSS_SELECTOR, '.notes > h4 , .notes > h3 , .notes > p')
_sections_toggle_locator = (By.CSS_SELECTOR, '.dropdown-toggle')
_dropdown_menu_option_locator = (
By.CSS_SELECTOR, '.multi-selection-option')
_print_preview_locator = (By.CSS_SELECTOR, '.print-btn')
_note_sections_locator = (By.CSS_SELECTOR, '.notes .section')
@property
def description(self):
"""Return the page description if no notes are available.
:return: the empty highlighting summary text
:rtype: str
"""
return '\n'.join(
[line.text
for line
in self.find_elements(*self._description_locator)])
@property
def drop_down(self):
r"""Return the section selector drop down menu.
:return: the book section multi-select drop down
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
return self.find_element(*self._sections_toggle_locator)
@property
def menu_is_open(self):
"""Return True if the section drop down menu is open.
:return: ``True`` if the multi-select drop down menu is open, else
``False``
:rtype: bool
"""
return \
self.drop_down.get_attribute('aria-expanded').lower() == 'true'
@property
def options(self):
"""Return the list of sections with highlights.
:return: the list of sections with highlights and/or annotations
:rtype: list(:py:class:`~ReferenceBook.Highlights.DisplaySection`)
"""
return [self.DisplaySection(self, section)
for section
in self.find_elements(*self._dropdown_menu_option_locator)]
[docs] def show_sections(self, sections=[], show_all=False):
"""Select sections by section number.
:param sections: a list of book section highlights to display
:param bool show_all: (optional) select all available sections
:type sections: list(str)
:return: the reference book with the highlighting summary page open
:rtype: :py:class:`ReferenceBook`
"""
for section in self.options:
if not self.menu_is_open:
self.menu_toggle()
option_is_checked = section.is_checked
if show_all and not option_is_checked:
section.select()
else:
option = section.section
if ((option in sections and not option_is_checked) or
(option not in sections and option_is_checked)):
section.select()
sleep(0.25)
return self.page
[docs] def show_print_preview(self):
"""Click on the 'Print this page' button.
:return: the print preview pop up window
:rtype: :py:class:`PrintPreview`
"""
button = self.find_element(*self._print_preview_locator)
Utility.switch_to(self.driver, element=button)
return PrintPreview(self.driver)
@property
def notes(self):
"""Access each displayed highlight or annotation.
:return: a list of displayed highlights and annotations
:rtype: list(:py:class:`ReferenceBook.Highlights.Note`)
"""
return [self.Note(self, note)
for note
in self.find_elements(*self._note_sections_locator)]
[docs] class Note(Region):
"""An individual highlight or annotation."""
_content_locator = (By.CSS_SELECTOR, '.note-content')
_note_locator = (By.CSS_SELECTOR, '.plain-text')
_edit_button_locator = (By.CSS_SELECTOR, '[title=Edit]')
_note_box_locator = (By.CSS_SELECTOR, '.edit-box textarea')
_view_button_locator = (By.CSS_SELECTOR, '.controls a')
_delete_button_locator = (By.CSS_SELECTOR, '[title=Delete]')
_delete_confirm_locator = (By.CSS_SELECTOR, '.btn-primary')
_save_edit_button_locator = (By.CSS_SELECTOR, '[title=Save]')
_cancel_edit_button_locator = (By.CSS_SELECTOR, '[title*=Cancel]')
_pop_over_content_selector = '.popover'
@property
def content(self):
"""Return the full HTML content text.
:return: the highlighted content with full markup
:rtype: str
"""
return (self.find_element(*self._content_locator)
.get_attribute('innerHTML'))
@property
def note(self):
"""Return the associated note, if found.
:return: the highlight's associated note
:rtype: str
"""
return self.find_element(*self._note_locator).text
[docs] def edit(self, text=''):
"""Edit the highlight note.
:param str text: new text for the highlight's note
:return: the reference book with the highlighting summary page
open
:rtype: :py:class:`ReferenceBook`
"""
button = self.find_element(*self._edit_button_locator)
Utility.click_option(self.driver, element=button)
if text:
self.note_box.send_keys(text)
sleep(0.1)
self.save_edit()
sleep(0.25)
return self.page.page
@property
def note_box(self):
r"""Return the note edit box.
:return: the highlight's note editing box
:rtype: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
"""
return self.find_element(*self._note_box_locator)
[docs] def save_edit(self):
"""Click on the checkmark confirm button.
:return: the reference book with the highlighting summary page
open
:rtype: :py:class:`ReferenceBook`
"""
button = self.find_element(*self._save_edit_button_locator)
Utility.click_option(self.driver, element=button)
return self.page.page
[docs] def cancel_edit(self):
"""Click on the X cancelation button.
:return: the reference book with the highlighting summary page
open
:rtype: :py:class:`ReferenceBook`
"""
button = self.find_element(*self._cancel_edit_button_locator)
Utility.click_option(self.driver, element=button)
return self.page.page
[docs] def view(self):
"""Click on the view in book link.
:return: the reference book with the requested book section
displayed in the content region
:rtype: :py:class:`ReferenceBook`
"""
button = self.find_element(*self._view_button_locator)
Utility.switch_to(self.driver, element=button)
return go_to_(
ReferenceBook(self.driver, self.page.page.base_url))
[docs] def delete(self):
"""Delete the highlight.
:return: the reference book with the highlighting summary page
open
:rtype: :py:class:`ReferenceBook`
"""
button = self.find_element(*self._delete_button_locator)
script = 'return document.querySelector("arguments[0]");'
Utility.click_option(self.driver, element=button)
pop_up = self.driver.execute_script(
script, self._pop_over_content_selector)
confirm = pop_up.find_element(*self._delete_confirm_locator)
Utility.click_option(self.driver, element=confirm)
sleep(0.25)
return self.page.page