"""Break the signup process out of the base."""
from time import sleep
from urllib.parse import urlparse
from pypom import Region
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from pages.accounts.base import AccountsBase
from pages.accounts.home import AccountsHome
from utils.accounts import Accounts
from utils.email import GmailReader, GuerrillaMail, RestMail
from utils.utilities import Utility, go_to_
ERROR = ' ~ .errors .alert'
[docs]class Signup(AccountsBase):
"""Signup process."""
URL_TEMPLATE = '/signup'
[docs] def account_signup(
self, email, password=None,
role=Accounts.STUDENT, provider=Accounts.RESTMAIL,
destination=None, base_url=None, **kwargs):
"""Single signup entry point."""
import pprint
pprint.PrettyPrinter(indent=2, width=140).pprint(kwargs)
# branching prep
non_student_role = role != Accounts.STUDENT
instructor = role == Accounts.INSTRUCTOR
# select the user type and initial email for verification
self.sign_up.role = role
self.sign_up.email = email
self.sign_up.next()
error = self.sign_up.email_error
assert(not error), '{0}'.format(error)
if non_student_role and not email.endswith('edu'):
self.sign_up.next()
# verify the email using the assigned pin number
not_verified = True
pause = 0.25
while not_verified:
email_password = None
if provider == Accounts.RESTMAIL:
account_name = email[:email.rfind('@')]
mailer = RestMail(account_name)
elif provider == Accounts.GOOGLE:
pin = (GmailReader(email[0:7])
.read_mail().sort_mail()
.latest.get_pin)
email_password = kwargs.get('email_password')
elif provider == Accounts.GUERRILLA_MAIL:
mailer = GuerrillaMail(self.driver)
else:
from utils.email import EmailVerificationError
raise EmailVerificationError(
'{0} is not an accepted email provider'.format(provider))
if provider != Accounts.GOOGLE:
pin = self._get_pin(
page=mailer,
provider=provider,
return_url=self.seed_url + '/verify_email',
email=email,
password=email_password)
if not pin:
raise ValueError('PIN not found')
self.pin_verification.clear_pin()
self.pin_verification.verify_pin = pin
self.pin_verification.confirm()
sleep(pause)
error = self.pin_verification.pin_error
if not error:
not_verified = False
assert(not error), '{0}'.format(error)
# set the account password or social login
if 'social' not in kwargs:
# use a password
self.password.password = password
self.password.confirmation = password
self.password.submit()
errors = self.password.password_errors
assert(not errors), '{0}'.format(' '.join(errors))
elif kwargs.get('social') == Accounts.FACEBOOK:
# use Facebook
(self.password.use_social_login()
.user_facebook.log_in(kwargs.get('social_login'),
kwargs.get('social_password')))
sleep(3)
else:
# use Google
(self.password.use_social_login()
.use_google.log_in(kwargs.get('social_login'),
kwargs.get('social_password')))
sleep(3)
self.wait.until(
lambda _: 'accounts' in urlparse(self.driver.current_url).netloc)
# enter the first page profile information
sleep(2)
if 'social' not in kwargs:
_, first, last, _ = kwargs.get('name')
self.profile.first_name = first
self.profile.last_name = last
if non_student_role:
self.profile.phone_number = kwargs.get('phone')
self.profile.school_name = kwargs.get('school')
if non_student_role:
self.profile.webpage = kwargs.get('webpage')
use = kwargs.get('use')
self.profile.using_openstax(use)
self.profile.next()
# enter the second page courseware information
if non_student_role:
subjects = kwargs.get('subjects', {})
subjects_to_select = []
for subject, name in Accounts.SUBJECTS:
if name in subjects:
subjects_to_select.append(name)
if subjects_to_select:
self.courseware.select_subjects(subjects_to_select)
self.courseware.set_using(subjects)
if instructor and use == Accounts.RECOMMENDED:
self.courseware.students = kwargs.get('students')
if not kwargs.get('news'):
self.courseware.no_newsletter()
self.courseware.agree_to_policies()
self.courseware.create_account()
errors = self.profile.profile_errors
assert(not errors), '{0}'.format(' '.join(errors))
error = self.courseware.book_error
assert(not error), '{0}'.format(error)
# register for a confirmation upon approval
if non_student_role:
if kwargs.get('access_notice'):
self.instructor_access.receive_instructor_access_notice()
self.instructor_access.ok()
# return to submitted destination
if destination:
return go_to_(destination(self.driver, base_url=base_url))
# or the user's new profile
from pages.accounts.profile import Profile
return go_to_(Profile(self.driver, self.base_url))
def _get_pin(self, page, provider, return_url, email=None, password=None):
"""Retrieve a signup pin."""
if 'restmail' in provider:
box = page.wait_for_mail()
return box[-1].pin
else:
page.open()
if 'google' in provider:
page = page.login.go(email, password)
WebDriverWait(page.driver, 60.0).until(
lambda _: page.emails[0].has_pin and page.emails[0].is_new)
sleep(5.0)
pin = page.emails[0].get_pin
page.driver.get(return_url)
sleep(1.0)
return pin
@property
def sign_up(self):
"""Access the signup page elements."""
return self.Signup(self)
@property
def pin_verification(self):
"""Access the pin verification."""
return self.Pin(self)
@property
def password(self):
"""Access the password fields."""
return self.Password(self)
@property
def profile(self):
"""Access the profile fields."""
return self.Profile(self)
@property
def courseware(self):
"""Access the courseware books."""
return self.Courseware(self)
@property
def instructor_access(self):
"""Access the approval confirmation fields."""
return self.Approval(self)
[docs] class Signup(Pagination):
"""Basic user data."""
_user_type_locator = (By.CSS_SELECTOR, '#signup_role')
_options_locator = (
By.CSS_SELECTOR, _user_type_locator[1] + ' option:not([disabled])')
_email_parent_locator = (By.CSS_SELECTOR, '.email-input-group')
_email_locator = (By.CSS_SELECTOR, '#signup_email')
_email_error_locator = (By.CSS_SELECTOR, _email_locator[1] + ERROR)
_warning_locator = (By.CSS_SELECTOR, '.warning')
_sign_in_locator = (By.CSS_SELECTOR, '[href$=login]')
@property
def role(self):
"""Return the role select parent."""
return self.find_element(*self._user_type_locator)
@role.setter
def role(self, signup_role):
"""Select a user role."""
options = [role.text
for role in self.find_elements(*self._options_locator)]
assert(signup_role in options), \
'"{0}" not found in the role options'.format(signup_role)
Utility.select(self.driver, self._user_type_locator, signup_role)
return self.page
@property
def email_box_is_visible(self):
"""Return True if the email input box is visible."""
return self.driver.execute_script(
'return window.getComputedStyle(arguments[0]).height!="0px";',
self.find_element(*self._email_parent_locator))
@property
def email(self):
"""Return the email input box."""
return self.find_element(*self._email_locator)
@email.setter
def email(self, email):
"""Enter the email address."""
self.email.send_keys(email)
return self.page
@property
def email_error(self):
"""Return the email entry error."""
try:
return self.find_element(*self._email_error_locator).text
except WebDriverException:
return ''
@property
def warning(self):
"""Return the warning text."""
return self.find_element(*self._warning_locator).text
@property
def warning_is_present(self):
"""Return True if the warning is displayed."""
return self.driver.execute_script(
'return window.getComputedStyle(arguments[0]).height!="auto";',
self.find_element(*self._warning_locator))
[docs] def log_in(self):
"""Return to the login home page."""
link = self.find_element(*self._sign_in_locator)
Utility.click_option(self.driver, element=link)
return go_to_(AccountsHome(self.driver, self.page.base_url))
[docs] class Pin(Pagination):
"""Pin verification."""
_pin_locator = (By.CSS_SELECTOR, '#pin_pin')
_pin_error_locator = (By.CSS_SELECTOR, '.alert')
_email_change_locator = (By.CSS_SELECTOR, '[href$=signup]')
@property
def verify_pin(self):
"""Return the pin verification input box."""
return self.find_element(*self._pin_locator)
@verify_pin.setter
def verify_pin(self, pin):
"""Enter the verification code."""
self.verify_pin.send_keys(pin)
return self.page
[docs] def edit_email(self):
"""Return to the user role selection to enter a new email."""
link = self.find_element(*self._email_change_locator)
Utility.click_option(self.driver, element=link)
return self.page
@property
def pin_error(self):
"""Return the pin entry error message."""
try:
return self.find_element(*self._pin_error_locator).text
except WebDriverException:
return ''
[docs] def clear_pin(self):
"""Clear the pin field for Chrome and Firefox."""
Utility.clear_field(self.selenium, self.verify_pin)
[docs] def confirm(self):
"""Click the 'Confirm' button."""
return self.next()
[docs] class Password(Pagination):
"""Password assignment."""
_password_locator = (By.CSS_SELECTOR, '#signup_password')
_password_conf_locator = (
By.CSS_SELECTOR, '#signup_password_confirmation')
_password_error_locator = (By.CSS_SELECTOR, '.alert')
_multi_error_locator = (By.CSS_SELECTOR, '.alert li')
_use_social_locator = (By.CSS_SELECTOR, '[href$=social]')
@property
def password(self):
"""Return the password input field."""
return self.find_element(*self._password_locator)
@password.setter
def password(self, password):
"""Enter the password."""
self.password.send_keys(password)
return self.page
@property
def confirmation(self):
"""Return the password confirmation input field."""
return self.find_element(*self._password_conf_locator)
@confirmation.setter
def confirmation(self, password):
"""Enter the password in the confirmation box."""
self.confirmation.send_keys(password)
return self.page
@property
def password_errors(self):
"""Return the password errors."""
try:
issue = self.find_element(*self._password_error_locator)
except WebDriverException:
return []
errors = self.find_elements(*self._multi_error_locator)
if not errors:
return [issue.text]
return list([error.text for error in errors])
[docs] def submit(self):
"""Click the 'Submit' button."""
return self.next()
[docs] def use_social_login(self):
"""Go to the social login setup."""
link = self.find_element(*self._use_social_locator)
Utility.click_option(self.driver, element=link)
return Signup.SocialLogin(self.page)
[docs] class Profile(Pagination):
"""User details."""
_first_name_locator = (By.CSS_SELECTOR, '#profile_first_name')
_first_name_error_locator = (
By.CSS_SELECTOR, _first_name_locator[1] + ERROR)
_last_name_locator = (By.CSS_SELECTOR, '#profile_last_name')
_last_name_error_locator = (
By.CSS_SELECTOR, _last_name_locator[1] + ERROR)
_phone_locator = (By.CSS_SELECTOR, '#profile_phone_number')
_phone_error_locator = (By.CSS_SELECTOR, _phone_locator[1] + ERROR)
_school_locator = (By.CSS_SELECTOR, '#profile_school')
_school_error_locator = (By.CSS_SELECTOR, _school_locator[1] + ERROR)
_webpage_locator = (By.CSS_SELECTOR, '#profile_url')
_webpage_error_locator = (By.CSS_SELECTOR, _webpage_locator[1] + ERROR)
_adopted_locator = (
By.CSS_SELECTOR, '#profile_using_openstax_confirmed_adoption_won')
_not_using_locator = (
By.CSS_SELECTOR, '#profile_using_openstax_not_using')
_continue_locator = (By.CSS_SELECTOR, '[data-bind*=nextPage]')
@property
def first_name(self):
"""Return the first name input field."""
return self.find_element(*self._first_name_locator)
@first_name.setter
def first_name(self, name):
"""Enter the user's first name."""
self.first_name.send_keys(name)
return self.page
@property
def last_name(self):
"""Return the last name input field."""
return self.find_element(*self._last_name_locator)
@last_name.setter
def last_name(self, name):
"""Enter the user's last name."""
self.last_name.send_keys(name)
return self.page
@property
def phone_number(self):
"""Return the telephone number input field."""
return self.find_element(*self._phone_locator)
@phone_number.setter
def phone_number(self, number):
"""Enter the user's telephone number."""
self.phone_number.send_keys(number)
return self.page
@property
def school_name(self):
"""Return the school name input field."""
return self.find_element(*self._school_locator)
@school_name.setter
def school_name(self, name):
"""Enter the school name."""
self.school_name.send_keys(name)
return self.page
@property
def webpage(self):
"""Return the faculty verification webpage field."""
return self.find_element(*self._webpage_locator)
@webpage.setter
def webpage(self, url):
"""Enter the webpage URL."""
self.webpage.send_keys(url)
return self.page
[docs] def using_openstax(self, method):
"""Select the current using state."""
if method == Accounts.ADOPTED:
option = self.find_element(*self._adopted_locator)
elif method == Accounts.NOT_USING:
option = self.find_element(*self._not_using_locator)
Utility.click_option(self.driver, element=option)
return self.page
@property
def profile_errors(self):
"""Return a list of errors."""
errors = []
try:
first = self.find_element(*self._first_name_error_locator).text
errors.append('{0}: {1}'.format('First name', first))
except WebDriverException:
pass
try:
last = self.find_element(*self._last_name_error_locator).text
errors.append('{0}: {1}'.format('Last name', last))
except WebDriverException:
pass
try:
phone = self.find_element(*self._phone_error_locator).text
errors.append('{0}: {1}'.format('Phone number', phone))
except WebDriverException:
pass
try:
school = self.find_element(*self._school_error_locator).text
errors.append('{0}: {1}'.format('School name', school))
except WebDriverException:
pass
try:
url = self.find_element(*self._webpage_error_locator).text
errors.append('{0}: {1}'.format('Webpage', url))
except WebDriverException:
pass
return errors
[docs] class SocialLogin(Region):
"""Sign up using a social app profile."""
_facebook_button_locator = (By.CSS_SELECTOR, '#facebook-login-button')
_google_button_locator = (By.CSS_SELECTOR, '#google-login-button')
_go_to_password_setup_locator = (By.CSS_SELECTOR, '[href$=password]')
@property
def use_facebook(self):
"""Use Facebook to log in."""
Utility.click_option(
self.driver, locator=self._facebook_button_locator)
from pages.facebook.home import Facebook
return Facebook(self.driver)
@property
def use_google(self):
"""Use Google to log in."""
Utility.click_option(
self.driver, locator=self._google_button_locator)
from pages.google.home import Google
return Google(self.driver)
@property
def use_a_password(self):
"""Use a non-social log in."""
Utility.click_option(
self.driver, locator=self._go_to_password_setup_locator)
return self.page
[docs] class Courseware(Pagination):
"""Book details."""
_adopted_book_selector = '#how_using_book_{book_code}_adoption_won'
_recommend_book_selector = '#how_using_book_{book_code}_will_recommend'
_student_number_selector = '[type=number][name*={book_code}]'
_book_locator = (By.CSS_SELECTOR, '.book-checkbox')
_book_error_locator = (By.CSS_SELECTOR, '.alert')
_number_students_locator = (By.CSS_SELECTOR, '#profile_num_students')
_newsletter_locator = (By.CSS_SELECTOR, '#profile_newsletter')
_policy_agreement_locator = (By.CSS_SELECTOR, '#profile_i_agree')
_terms_locator = (By.CSS_SELECTOR, '[href*=terms]:nth-child(3)')
_privacy_locator = (By.CSS_SELECTOR, '[href*=terms]:nth-child(4)')
_back_button_locator = (By.CSS_SELECTOR, '[data-bind*=prevPage]')
@property
def books(self):
"""Return the list of available books."""
return [self.Book(self, book)
for book in self.find_elements(*self._book_locator)]
[docs] def select_subjects(self, subject_list):
"""Mark each interested or adopted book."""
for book in self.books:
if book.title in subject_list:
book.select()
return self.page
[docs] def set_using(self, subject_list):
"""Mark the adoption status and students for each book."""
for subject in subject_list:
code = Accounts.get_book_code(subject)
status = subject_list.get(subject).get('status')
students = subject_list.get(subject).get('students')
if status == Accounts.ADOPTED:
selector = (self._adopted_book_selector
.format(book_code=code))
else:
selector = (self._recommend_book_selector
.format(book_code=code))
button = self.find_element(By.CSS_SELECTOR, selector)
Utility.click_option(self.driver, element=button)
self.find_element(
By.CSS_SELECTOR,
self._student_number_selector.format(book_code=code)
).send_keys(students)
sleep(0.5)
return self.page
@property
def students(self):
"""Return the general student count input box."""
return self.find_element(*self._number_students_locator)
@students.setter
def students(self, total):
"""Enter the number of students taught."""
self.students.send_keys(total)
return self.page
@property
def newsletter(self):
"""Return the newsletter checkbox."""
return self.find_element(*self._newsletter_locator)
[docs] def no_newsletter(self):
"""Uncheck the newsletter box."""
if self.driver.execute_script('return arguments[0].checked;',
self.newsletter):
Utility.click_option(self.driver, element=self.newsletter)
return self.page
@property
def agreement(self):
"""Return the 'I agree' checkbox."""
return self.find_element(*self._policy_agreement_locator)
[docs] def agree_to_policies(self):
"""Check the 'I agree' checkbox."""
if not self.driver.execute_script('return arguments[0].checked;',
self.agreement):
Utility.click_option(self.driver, element=self.agreement)
return self.page
[docs] def view_terms(self):
"""Open the Terms of Use."""
link = self.find_element(*self._terms_locator)
Utility.click_option(self.driver, element=link)
return self.page
[docs] def view_privacy_policy(self):
"""Open the Privacy Policy."""
link = self.find_element(*self._privacy_locator)
Utility.click_option(self.driver, element=link)
return self.page
@property
def policy(self):
"""Access the policy modal."""
modal_root = self.driver.execute_script(
'return document.querySelector("#terms_dialog");')
return self.Modal(self, modal_root)
[docs] def back(self):
"""Return to the previous page."""
button = self.find_element(*self._back_button_locator)
Utility.click_option(self.driver, element=button)
return self.page
[docs] def create_account(self):
"""Click the 'Create Account' button."""
return self.next()
@property
def book_error(self):
"""Return the book error."""
try:
return self.find_element(*self._book_error_locator).text
except WebDriverException:
return ''
[docs] class Book(Region):
"""An OpenStax book."""
_title_locator = (By.CSS_SELECTOR, 'label')
_image_locator = (By.CSS_SELECTOR, 'img')
_checkbox_locator = (By.CSS_SELECTOR, '.indicator')
@property
def title(self):
"""Return the book title."""
return self.find_element(*self._title_locator).text
@property
def image(self):
"""Return the image element."""
return self.find_element(*self._image_locator)
@property
def checkbox(self):
"""Return the checkbox element."""
return self.find_element(*self._checkbox_locator)
[docs] def select(self):
"""Select the book."""
Utility.click_option(self.driver, element=self.checkbox)
return self.page.page
@property
def is_checked(self):
"""Return True if the box is checked."""
return 'checked' in self.root.get_attribute('class')
[docs] class Modal(Region):
"""The Terms of Use and Privacy Policy display window."""
_content_locator = (By.CSS_SELECTOR, '.modal-body')
_close_locator = (By.CSS_SELECTOR, 'button')
@property
def content(self):
"""Return the modal content text."""
return self.find_element(*self._content_locator).text
[docs] def close(self):
"""Close the modal."""
button = self.find_element(*self._close_locator)
Utility.click_option(self.driver, element=button)
return self.page.page
[docs] class Approval(Pagination):
"""Acceptance email."""
_approval_email_locator = (By.CSS_SELECTOR, '[type=checkbox]')
@property
def approval(self):
"""Return the email request upon approval checkbox."""
return self.find_element(*self._approval_email_locator)
[docs] def receive_instructor_access_notice(self):
"""Click the checkbox to receive notice."""
if not self.driver.execute_script('return arguments[0].checked;',
self.approval):
Utility.click_option(self.driver, element=self.approval)
return self.page
[docs] def ok(self):
"""Click the OK button."""
return self.next()