Source code for pages.tutor.performance
"""The instructor's course settings page."""
from time import sleep
from pypom import Region
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from pages.tutor.base import TutorBase
from regions.tutor.tooltip import Tooltip
from utils.tutor import TutorException
from utils.utilities import Utility, go_to_
[docs]class BookSection(Region):
"""A book chapter or book section performance listing."""
_book_number_locator = (By.CSS_SELECTOR, '.number')
_title_locator = (By.CSS_SELECTOR, '.title')
_practice_button_locator = (By.CSS_SELECTOR, 'button')
_progress_bar_locator = (By.CSS_SELECTOR, '.progress-bar , .no-data')
_clue_stats_locator = (By.CSS_SELECTOR, '.clue li')
_question_count_locator = (By.CSS_SELECTOR, '.count')
@property
def number(self):
"""Return the chapter or section number.
:return: the book chapter number or the individual section number
:rtype: str
"""
return self.find_element(*self._book_number_locator).text
@property
def title(self):
"""Return the chapter or section title.
:return: the book chapter title or the individual section title
:rtype: str
"""
return self.find_element(*self._title_locator).text
[docs] def practice(self):
"""Click on the progress bar or work more button.
:return: a new practice session for a chapter, section, or the weakest
topics or return the performance forecast for instructors
:rtype: :py:class:`~pages.tutor.practice.Practice` or
:py:class:`PerformanceForecast`
"""
try:
button = self.find_element(*self._practice_button_locator)
Utility.click_option(self.driver, element=button)
sleep(1)
from pages.tutor.practice import Practice
try:
url = self.page.page.base_url
except AttributeError:
url = self.page.page.page.base_url
return go_to_(Practice(self.driver, base_url=url))
except NoSuchElementException:
# teachers don't have practice button on their forecast
return self.page.page
@property
def progress(self):
"""Return the progress value or no data statement.
:return: the rounded CLUe value from 0 to 100 or the 'Not enough' data
message
:rtype: str
"""
progress = self.find_element(*self._progress_bar_locator)
clue = progress.get_attribute('aria-valuenow')
if not clue:
# not enough data to return a CLUe
return progress.text
return clue
@property
def stats(self):
"""Return the debugging CLUe data.
:return: the CLUe data as key: value pairs
:rtype: dict(str, str)
"""
data = self.find_elements(*self._clue_stats_locator)
group = {}
for pair in data:
key, value = self.driver.execute_script(
'return arguments[0].textContent;', pair).split(': ')
group[key] = value
return group
@property
def count(self):
"""Return the worked assessments text.
:return: the worked assessments for students or the the number of
students and their worked assessments for teachers
:rtype: str
"""
return self.find_element(*self._question_count_locator).text
[docs]class PerformanceForecast(TutorBase):
"""The performance forecast page."""
_go_back_button_locator = (By.CSS_SELECTOR, '.info a')
_group_forecast_locator = (By.CSS_SELECTOR, '.guide-group')
_guide_locator = (By.CSS_SELECTOR, '.guide-group-key')
_joyride_root_selector = (By.CSS_SELECTOR, '.joyride')
_loading_message_locator = (
By.CSS_SELECTOR, '.is-loading , .loading-animation')
_no_data_locator = (By.CSS_SELECTOR, '.no-data-message')
_page_title_locator = (By.CSS_SELECTOR, '.guide-group-title')
_section_tab_locator = (By.CSS_SELECTOR, '[role=tab]')
[docs] def back_to_scores(self):
"""Click the 'Back to Scores' button.
:return: the scores page
:rtype: :py:class:`~pages.tutor.scores.Scores`
"""
self.go_back()
from pages.tutor.scores import Scores
return go_to_(Scores(self.driver, base_url=self.base_url))
[docs] def clear_training_wheels(self) -> None:
"""Clear any joyride modals.
:return: None
"""
try:
while Utility.has_children(
self.find_element(*self._joyride_root_selector)):
tooltip = Tooltip(self, self.find_element(
*self._joyride_root_selector))
tooltip.next()
sleep(1)
except NoSuchElementException:
sleep(0.5)
@property
def forecast(self):
"""Access the performance forecast rows.
:return: the forecast panels
:rtype: :py:class:`~PerformanceForecast.Guide`
:raise :py:class:`~utils.tutor.TutorException`: if the guide is not
found
"""
try:
forecast = self.find_element(*self._group_forecast_locator)
return self.Guide(self, forecast)
except NoSuchElementException:
raise TutorException('Performance guide not found; is there data?')
[docs] def go_back(self):
"""Return to the previous page.
Clicking on the 'Back to ...' button returns the user to the previously
visited Tutor page, which is often the student's course page or the
teacher's calendar.
:return: None
"""
button = self.find_element(*self._go_back_button_locator)
Utility.click_option(self.driver, element=button)
@property
def guide(self):
"""Access the colored bar guide.
:return: the list of possible bar colors and their associated keys
:rtype: list(:py:class:`~PerformanceForecast.Bar`)
"""
return [self.Bar(self, bar)
for bar in self.find_elements(*self._guide_locator)]
@property
def loaded(self) -> bool:
"""Return True when the performance forecast is loaded.
:return: ``True`` if no loading messages or elements are found
:rtype: bool
"""
return not self.find_elements(*self._loading_message_locator)
@property
def no_data(self):
"""Return the 'no questions worked' message, if found.
:return: the 'no questions worked' message if found, otherwise an empty
string
:rtype: str
"""
message = self.find_elements(*self._no_data_locator)
return message[0].text if message else ''
@property
def section_tabs(self):
"""Access the section or period tabs.
:return: the list of available sections or periods
:rtype: list(:py:class:`PerformanceForecast.Section`)
"""
return [self.Section(self, tab)
for tab in self.find_elements(*self._section_tab_locator)]
@property
def title(self):
"""Return the page title.
:return: the page title
:rtype: str
"""
return self.find_element(*self._page_title_locator).text
[docs] def view_section(self, name):
"""View a course section by its name.
:return: the performance forecast for the requested section or period
:rtype: :py:class:`PerformanceForecast`
:raises :py:class:`utils.tutor.TutorException`: if the name doesn't
match an available course section
"""
for tab in self.section_tabs:
if tab.name == name:
tab.select()
return self.page
raise TutorException('"{0}" does not match any active section')
[docs] class Bar(Region):
"""A progress bar."""
_bar_locator = (By.CSS_SELECTOR, '.progress-bar')
_key_locator = (By.CSS_SELECTOR, '.title')
@property
def color(self):
"""Return the background color for a bar.
:return: the background color value
:rtype: str
"""
bar = self.find_element(*self._bar_locator)
script = ('return window.getComputedStyle(arguments[0])'
'.backgroundColor')
return self.driver.execute_script(script, bar)
@property
def key(self):
"""Return the key text.
:return: the text description for a bar color
:rtype: str
"""
return self.find_element(*self._key_locator).text
[docs] class Guide(Region):
"""The performance guide weakest guide and chapters."""
_weakest_section_locator = (By.CSS_SELECTOR, '.weaker')
_chapter_locator = (By.CSS_SELECTOR, '.chapter-panel:not(.weaker)')
@property
def weakest(self):
"""Access the weakest row.
:return: the weakest row
:rtype: :py:class:`~PerformanceForecast.Guide.Weakest`
"""
row_root = self.find_element(*self._weakest_section_locator)
return self.Weakest(self, row_root)
@property
def chapters(self):
"""Access the chapter rows.
:return: the list of chapter rows
:rtype: list(:py:class:`~PerformanceForecast.Guide.Chapter`)
"""
return [self.Chapter(self, row)
for row in self.find_elements(*self._chapter_locator)]
[docs] class Weakest(Region):
"""The sections with the worst performance."""
_title_locator = (By.CSS_SELECTOR, '.chapter .title')
_explanation_locator = (By.CSS_SELECTOR, '.explanation p')
_practice_all_button_locator = (By.CSS_SELECTOR, '.weakest')
_lacking_data_locator = (By.CSS_SELECTOR, '.lacking-data')
_section_locator = (By.CSS_SELECTOR, '.section')
@property
def title(self):
"""Return the row title.
:return: the row title
:rtype: str
"""
return self.find_element(*self._title_locator).text
@property
def explanation(self):
"""Return the explanation text for the 'Weakest Areas'.
:return: the Weakest Areas explanation
:rtype: str
"""
return ' '.join(list([
line.text
for line
in self.find_elements(*self._explanation_locator)]))
[docs] def practice_all(self):
"""Click the 'Practice All' button to start a practice session.
:return: a practice session for the weakest book sections or
the performance forecast if there isn't enough data to
determine the weakest book sections or the user is a
teacher
:rtype: :py:class:`~pages.tutor.practice.Practice` or
:py:class:`PerformanceForecast`
"""
try:
button = self.find_element(
*self._practice_all_button_locator)
Utility.click_option(self.driver, element=button)
sleep(1)
from pages.tutor.practice import Practice
return go_to_(
Practice(self.driver,
base_url=self.page.page.base_url))
except NoSuchElementException:
return self.page.page
@property
def lack_data(self):
"""Return the "haven't worked enough problems" message.
:return: the "haven't worked enough problems" message, if
found, else an empty string
:rtype: str
"""
message = self.find_elements(*self._lacking_data_locator)
return message[0].text if message else ''
@property
def sections(self):
"""Access the row's book section groups.
:return: the list of poorest performing book sections
:rtype: list(:py:class:`BookSection`)
"""
return [BookSection(self, section)
for section
in self.find_elements(*self._section_locator)]
[docs] class Chapter(Region):
"""An individual book chapter row."""
_chapter_locator = (By.CSS_SELECTOR, '.chapter')
_section_locator = (By.CSS_SELECTOR, '.section')
@property
def chapter(self):
"""Access the chapter performance data.
:return: the chapter performance data
:rtype: :py:class:`BookSection`
"""
chapter_root = self.find_element(*self._chapter_locator)
return BookSection(self, chapter_root)
@property
def sections(self):
"""Access the chapter sections performance data.
:return: the list of assigned chapter sections performance data
:rtype: list(:py:class:`BookSection`)
"""
return [BookSection(self, section)
for section
in self.find_elements(*self._section_locator)]
[docs] class Section(Region):
"""A course section or period tab."""
_name_locator = (By.CSS_SELECTOR, 'span')
_select_tab_locator = (By.CSS_SELECTOR, 'a')
@property
def name(self):
"""Return the section or period name.
:return: the section or period name
:rtype: str
"""
return self.find_element(*self._name_locator).text
[docs] def select(self):
"""Click on the section tab.
:return: the performance forecast with the selected section active
:rtype: :py:class:`PerformanceForecast`
"""
button = self.find_element(*self._select_tab_locator)
Utility.click_option(self.driver, element=button)
sleep(0.5)
return self.page
@property
def selected(self):
"""Return True if the tab is currently active.
:return: ``True`` if the tab is active, ``False`` otherwise
:rtype: bool
"""
return 'active' in self.root.get_attribute('class')