Source code for pages.web.adoption
"""The book adoption form."""
from time import sleep
from pypom import Region
from selenium.common.exceptions import (ElementNotInteractableException, # NOQA
NoSuchElementException, # NOQA
TimeoutException, # NOQA
WebDriverException) # NOQA
from selenium.webdriver.common.by import By
from pages.web.base import WebBase
from utils.utilities import Utility, go_to_
from utils.web import TechProviders, Web, WebException
ERROR = ' ~ .invalid-message'
[docs]class Adoption(WebBase):
"""The adoption form page."""
URL_TEMPLATE = '/adoption'
_banner_locator = (By.CSS_SELECTOR, '.subhead h1')
_description_locator = (By.CSS_SELECTOR, '.subhead p:first-child')
_drop_down_menu_locator = (By.CSS_SELECTOR, '.proxy-select')
_interest_form_link_locator = (By.CSS_SELECTOR, '[href$=interest]')
_form_root_locator = (By.CSS_SELECTOR, '.role-selector')
_book_selection_locator = (By.CSS_SELECTOR, '.book-checkbox')
_image_locator = (By.CSS_SELECTOR, '.has-image img')
@property
def loaded(self):
"""Wait until the form is found."""
return (
super().loaded and
bool(self.find_elements(*self._drop_down_menu_locator)))
[docs] def is_displayed(self):
"""Return True if the adoption form is displayed."""
form = self.find_elements(*self._drop_down_menu_locator)
if not form:
return False
visibility = self.driver.execute_script(
'return window.getComputedStyle(arguments[0]).visibility;',
form[0])
return visibility == 'visible'
[docs] def go_to_interest(self, link=None):
"""Switch to the interest form."""
locator = link if link else self._interest_form_link_locator
from pages.web.interest import Interest
destination = Interest if not link else Adoption
link = self.wait.until(
lambda _: self.find_element(*locator))
Utility.click_option(self.driver, element=link)
sleep(1.0)
return go_to_(destination(driver=self.driver, base_url=self.base_url))
[docs] def submit_adoption(self,
user_type, first, last, email, phone, school,
books,
tech_providers=None, other_provider=None):
"""Fill out and submit the adoption form.
Args:
user_type (str): the user's role
Web.STUDENT, Web.INSTRUCTOR, Web.ADMINISTRATOR,
Web.LIBRARIAN, Web.DESIGNER, Web.HOMESCHOOL,
Web.ADJUNCT, or Web.OTHER
first (str): the user's first name
last (str): the user's last name
email (str): the user's email address
phone (str): the user's telephone number
school (str): the user's school affiliation
books (dict): a dictionaries of dictionaries for adopted books
Book short name (str): the shortened book title
Adoption status (str): Web.ADOPTED or Web.RECOMMENDED
Students (int): number of students being taught per semester
{
'AP Biology': {
'status': Web.ADOPTED,
'students': 30
},
<book-short-name>: {
'status': <status>,
'students': <students>
}
}
tech_providers (list): a list of technology partners under use
[Web.TechProviders.<provider>]
other_provider (str): a string of unlisted providers to be
used when Web.TechProviders.OTHER is in tech_providers
"""
self.form.select(user_type)
sleep(1.0)
self.form.first_name = first
self.form.last_name = last
self.form.email = email
self.form.phone = phone
self.form.school = school
self.form.next()
sleep(1.0)
user_errors = self.form.get_user_errors
assert(not user_errors), \
'User errors: {issues}'.format(issues=user_errors)
self.wait.until(
lambda _:
Utility.is_image_visible(self.driver,
locator=self._image_locator) and
self.find_element(*self._book_selection_locator))
Utility.scroll_to(self.driver, self._book_selection_locator, shift=-80)
sleep(0.25)
self.form.select_books(books)
sleep(0.5)
self.form.set_using(books)
sleep(0.5)
self.form.next()
sleep(1.0)
book_error = self.form.get_book_error
assert(not book_error), \
'Book error - {book}'.format(book=book_error)
using_errors = self.form.get_using_errors
assert(not using_errors), \
'Using error - {using}'.format(using=using_errors)
self.form.select_tech(tech_providers)
if tech_providers and TechProviders.OTHER in tech_providers:
self.form.other = other_provider
self.form.submit()
sleep(1.0)
return go_to_(AdoptionConfirmation(self.driver, self.base_url))
@property
def form(self):
"""Access the adoption form."""
form_root = self.find_element(*self._form_root_locator)
return self.Form(self, form_root)
[docs] class Form(Region):
"""The adoption form."""
# User role selection
_user_select_locator = (By.CSS_SELECTOR, '.proxy-select')
_user_select_option_locator = (By.CSS_SELECTOR, '.options .option')
_user_selected_role_locator = (By.CSS_SELECTOR, 'span.item')
_user_role_selector = '.options [data-value="{user}"]'
# Student-specific locators
_student_message_locator = (By.CSS_SELECTOR,
'.student-form .text-content')
_go_back_button_locator = (By.CSS_SELECTOR, '.student-form button')
# User information
_first_name_locator = (By.CSS_SELECTOR, '[name=first_name]')
_last_name_locator = (By.CSS_SELECTOR, '[name=last_name]')
_email_locator = (By.CSS_SELECTOR, '[name=email]')
_phone_locator = (By.CSS_SELECTOR, '[name=phone]')
_school_locator = (By.CSS_SELECTOR, '[name=company]')
_first_name_error_locator = (
By.CSS_SELECTOR, _first_name_locator[1] + ERROR)
_last_name_error_locator = (
By.CSS_SELECTOR, _last_name_locator[1] + ERROR)
_email_error_locator = (
By.CSS_SELECTOR, _email_locator[1] + ERROR)
_phone_error_locator = (
By.CSS_SELECTOR, _phone_locator[1] + ERROR)
_school_error_locator = (
By.CSS_SELECTOR, _school_locator[1] + ERROR)
# Book information
_books_locator = (By.CSS_SELECTOR, '[data-book-checkbox]')
_book_selector_error = (
By.CSS_SELECTOR, '.book-selector .invalid-message')
_book_checkbox_locator = (
By.CSS_SELECTOR, '.book-selector .book-checkbox')
# Book usage information
_using_root_locator = (By.CSS_SELECTOR, '.page-2 .how-using')
_using_locator = (By.CSS_SELECTOR, '.how-using > div')
# Technology utilization information
_tech_locator = (By.CSS_SELECTOR, '.book-checkbox')
_other_option_locator = (By.CSS_SELECTOR, '[name*=Other]')
# Form controls
_next_button_locator = (By.CSS_SELECTOR, '.next')
_back_button_locator = (By.CSS_SELECTOR, '.back')
_submit_button_locator = (By.CSS_SELECTOR, '[type=submit]')
@property
def loaded(self):
"""Return True when the form is visible."""
return self.root.is_displayed()
@property
def options(self):
"""Return the option menu responses."""
return self.find_elements(*self._user_select_option_locator)
[docs] def select(self, user_type, retry=0):
"""Select a user type from the user drop down menu."""
sleep(0.33)
for step in range(10):
user = self.find_element(*self._user_select_locator)
Utility.click_option(self.driver, element=user)
sleep(0.5)
is_open = 'open' in (
self.find_element(*self._user_select_locator)
.get_attribute('class'))
if is_open:
break
if step == 9:
raise WebException('User select menu not open')
for step in range(10):
user = self.find_element(
By.CSS_SELECTOR,
self._user_role_selector.format(
user=Web.USER_CONVERSION[user_type]))
Utility.click_option(self.driver, element=user)
sleep(0.5)
is_closed = 'open' not in (
self.find_element(
By.CSS_SELECTOR,
self._user_role_selector.format(
user=Web.USER_CONVERSION[user_type]))
.get_attribute('class'))
if is_closed:
break
if step == 9:
raise WebException('User select menu still open')
sleep(0.5)
selected = (self.find_element(*self._user_selected_role_locator)
.get_attribute('textContent'))
if selected == Web.NO_USER_TYPE and retry <= 2:
self.select(user_type, retry=retry + 1)
elif retry == 3:
raise WebException('No user type selected after 3 tries')
elif selected != user_type:
raise WebException(
f'"{user_type}" not selected; found "{selected}"')
return self
@property
def student_message(self):
"""Return the message content for student users.
Drop the text from the child element first.
"""
message = (self.find_element(*self._student_message_locator)
.get_attribute('innerHTML')
.split('\n')[0]
.split('<')[0])
return message
[docs] def go_back(self, destination=WebBase, url=None):
"""Click the student GO BACK button."""
back_button = self.find_element(*self._go_back_button_locator)
Utility.click_option(self.driver, element=back_button)
if url:
return go_to_(destination(self.driver, url=url))
return go_to_(destination(self.driver))
@property
def first_name(self):
"""Return the first name input box."""
return self.find_element(*self._first_name_locator)
@first_name.setter
def first_name(self, name):
"""Type the first name."""
self._setter_helper(self.first_name, name)
@property
def last_name(self):
"""Return the last name input box."""
return self.find_element(*self._last_name_locator)
@last_name.setter
def last_name(self, name):
"""Type the last name."""
self._setter_helper(self.last_name, name)
@property
def email(self):
"""Return the email input box."""
return self.find_element(*self._email_locator)
@email.setter
def email(self, email):
"""Type the email."""
self._setter_helper(self.email, email)
@property
def phone(self):
"""Return the phone number input box."""
return self.find_element(*self._phone_locator)
@phone.setter
def phone(self, phone_number):
"""Type the phone number."""
self._setter_helper(self.phone, phone_number)
@property
def school(self):
"""Return the school name input box."""
return self.find_element(*self._school_locator)
@school.setter
def school(self, school_name):
"""Type the school name."""
self._setter_helper(self.school, school_name)
def _setter_helper(self, field, value):
r"""Set an input text field value.
:param field: the input box to fill out
:param str value: the value to send to the input field
:type field: \
:py:class:`~selenium.webdriver.remote.webelement.WebElement`
:return: None
"""
sleep(0.25)
for _ in range(20):
try:
field.send_keys(value)
return
except ElementNotInteractableException:
sleep(1)
@property
def school_suggestions(self):
"""Return a list of school name suggestions.
Based on a filter of the text in the school name input box.
"""
return [self.School(self, suggestion)
for suggestion
in self.find_elements(*self._suggestion_locator)]
@property
def get_user_errors(self):
"""Return the error messages."""
errors = {}
first = (self.find_element(*self._first_name_error_locator)
.get_attribute('textContent'))
last = (self.find_element(*self._last_name_error_locator)
.get_attribute('textContent'))
email = (self.find_element(*self._email_error_locator)
.get_attribute('textContent'))
phone = (self.find_element(*self._phone_error_locator)
.get_attribute('textContent'))
school = (self.find_element(*self._school_error_locator)
.get_attribute('textContent'))
if first:
errors['first_name'] = first
if last:
errors['last_name'] = last
if email:
errors['email'] = email
if phone:
errors['phone'] = phone
if school:
errors['school'] = school
return errors
@property
def books(self):
"""Access the book selector checkboxes."""
return [self.Book(self, el)
for el in self.find_elements(*self._books_locator)]
[docs] def select_books(self, book_list):
"""Select the checkboxes for submitted book list."""
self.wait.until(lambda _:
self.find_elements(*self._book_checkbox_locator))
for book in self.books:
if book.title in book_list and not book.checked:
book.select()
sleep(0.25)
return self
@property
def get_book_error(self):
"""Return the book section error message."""
try:
return self.find_element(*self._book_selector_error).text
except WebDriverException:
return ''
@property
def usage(self):
"""Access the usage information section for each selected book."""
return [self.Using(self, el)
for el in self.find_elements(*self._using_locator)]
[docs] def set_using(self, book_list):
"""Select the status and student count for each book."""
for book in self.usage:
Utility.scroll_to(self.driver, element=book.root, shift=-100)
if book.title in book_list:
sleep(0.25)
status = book_list.get(book.title).get('status')
if Web.ADOPTED in status:
book.adopted()
elif Web.RECOMMENDED in status:
book.recommend()
sleep(0.25)
book.students = book_list.get(book.title).get('students')
return self
@property
def get_using_errors(self):
"""Return the usage section error messages."""
errors = {}
for book in self.usage:
if book.title == '' or (
not book.using_error and not book.students_error):
continue
else:
errors[book.title] = (
'Book error: "{book}" / Student error: "{student}"'
.format(book=book.using_error,
student=book.students_error))
return errors
@property
def technology(self):
"""Access the technology provider information sections."""
return [self.Technology(self, el)
for el in self.find_elements(*self._tech_locator)]
[docs] def select_tech(self, providers, other=None):
"""Select the tech partners in use."""
if not providers:
return self
for tech in self.technology:
if tech.company in providers and not tech.checked:
tech.select()
if TechProviders.OTHER in providers:
self.other = other
return self
@property
def other(self):
"""Return the other option input box."""
return self.find_element(*self._other_option_locator)
@other.setter
def other(self, value):
"""Send the other technology provider to the form."""
Utility.scroll_to(self.driver, element=self.other, shift=-80)
self.driver.execute_script(
'arguments[0].value = "{0}";'.format(value),
self.other)
[docs] def next(self):
"""Click the Next button."""
button = self.find_element(*self._next_button_locator)
Utility.scroll_to(self.driver, element=button, shift=-80)
Utility.click_option(self.driver, element=button)
sleep(1.0)
return self
[docs] def back(self):
"""Click the Back button."""
button = self.find_element(*self._back_button_locator)
Utility.scroll_to(self.driver, element=button, shift=-80)
Utility.click_option(self.driver, element=button)
sleep(1.0)
return self
[docs] def submit(self):
"""Click the Submit form button."""
button = self.find_element(*self._submit_button_locator)
Utility.scroll_to(self.driver, element=button, shift=-80)
Utility.click_option(self.driver, element=button)
sleep(1.0)
return self
[docs] class School(Region):
"""A suggested school name."""
@property
def name(self):
"""Return the school name."""
return self.root.text.strip()
[docs] def select(self):
"""Click on the school name to select it."""
Utility.click_option(self.driver, element=self.root)
return self.page
[docs] class Book(Region):
"""A book checkbox option."""
_status_indicator_locator = (By.CSS_SELECTOR, '.book-checkbox')
_image_locator = (By.CSS_SELECTOR, 'img')
_title_locator = (By.CSS_SELECTOR, 'label')
_checkbox_locator = (By.CSS_SELECTOR, '[role=checkbox]')
def __str__(self):
"""Print the book attributes."""
book = ('Title: {title}\nImage: {has_image} - {image}\n'
'Checked: {checked}\n')
return book.format(
title=self.title,
has_image=self.has_image,
image=(self.image.get_attribute('src')
if self.has_image else ''),
checked=self.checked)
@property
def has_image(self):
"""Return True if the book check option has an image."""
return ('has-image' in
self.find_element(*self._status_indicator_locator)
.get_attribute('class'))
@property
def image(self):
"""Return the image element."""
if self.has_image:
return self.find_element(*self._image_locator)
@property
def title(self):
"""Return the book title."""
return self.find_element(*self._title_locator).text
[docs] def select(self):
"""Click the checkbox."""
checkbox = self.find_element(*self._checkbox_locator)
Utility.click_option(self.driver, element=checkbox)
sleep(0.25)
return self.page
@property
def checked(self):
"""Return True if the book option is checked."""
return ('checked' in
self.find_element(*self._status_indicator_locator)
.get_attribute('class'))
[docs] class Using(Region):
"""Additional information concerning each book adoption."""
_title_locator = (By.CSS_SELECTOR, 'div:first-child')
_adopted_locator = (By.CSS_SELECTOR, '[value*=Adoption]')
_recommended_locator = (By.CSS_SELECTOR, '[value*=Recommend]')
_radio_error_locator = (
By.XPATH,
('//label[*[contains(@value,"Recommend")]]'
'/following-sibling::div'))
_students_locator = (By.CSS_SELECTOR, '[type=number]')
_students_error_locator = (
By.CSS_SELECTOR, _students_locator[1] + ERROR)
@property
def title(self):
"""Return the book title."""
return (self.find_element(*self._title_locator).text
.split('How are you using ')[-1]
.split('?')[0])
[docs] def adopted(self):
"""Select the 'Fully adopted' option."""
radio = self.find_element(*self._adopted_locator)
Utility.click_option(self.driver, element=radio)
return self.page
[docs] def recommend(self):
"""Select the 'Recommending the book' option."""
radio = self.find_element(*self._recommended_locator)
Utility.click_option(self.driver, element=radio)
return self.page
@property
def using_error(self):
"""Return the error message, if any, on the radio fields."""
try:
return self.find_element(*self._radio_error_locator).text
except NoSuchElementException:
return ''
@property
def students(self):
"""Return the students input element."""
return self.find_element(*self._students_locator)
@students.setter
def students(self, number):
"""Set the number of students using the selected book."""
sleep(0.25)
for _ in range(20):
try:
self.students.send_keys(number)
return
except ElementNotInteractableException:
sleep(1)
@property
def students_error(self):
"""Return the error message, if any, on the student field."""
try:
return (self.find_element(*self._students_error_locator)
.text)
except NoSuchElementException:
return ''
[docs] class Technology(Region):
"""Technology in use by the instructor or school."""
_name_locator = (By.CSS_SELECTOR, 'label')
_checkbox_locator = (By.CSS_SELECTOR, '.indicator')
@property
def company(self):
"""Return the company or product name."""
return self.find_element(*self._name_locator).text
[docs] def select(self):
"""Click the checkbox."""
self.find_element(*self._checkbox_locator).click()
sleep(1.0)
return self.page
@property
def checked(self):
"""Return True if the company option is checked."""
return 'checked' in self.root.get_attribute('class')
[docs]class AdoptionConfirmation(WebBase):
"""The adoption confirmation page."""
URL_TEMPLATE = '/adoption-confirmation'
_confirmation_locator = (By.CLASS_NAME, 'adoption-confirmation')
_report_another_locator = (By.CSS_SELECTOR, '.outlined')
_survey_locator = (By.CSS_SELECTOR, '.survey-request')
@property
def loaded(self):
"""Wait until the confirmation is displayed."""
return self.find_element(*self._confirmation_locator).is_displayed()
[docs] def is_displayed(self):
"""Return True if the 'Report another ...' link is displayed."""
return self.another.is_displayed()
@property
def another(self):
"""Return the 'Report another ...' link."""
return self.find_element(*self._report_another_locator)
[docs] def report_another(self):
"""Click on the 'Report another ...' link."""
Utility.click_option(self.driver, element=self.another)
return go_to_(Adoption(self.driver, self.base_url))
@property
def survey_available(self):
"""Return True if a survey is available."""
return Utility.has_children(self.find_element(*self._survey_locator))