Commit 04e66be7 by Will Daly

Added Selenium page objects for LMS and studio

parent 3cf75198
import os
# Get the URL of the instance under test
BASE_URL = os.environ.get('test_url', '')
from bok_choy.page_object import PageObject
from ..lms import BASE_URL
class CourseAboutPage(PageObject):
"""
Course about page (with registration button)
"""
@property
def name(self):
return "lms.course_about"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, course_id=None):
"""
URL for the about page of a course.
Course ID is currently of the form "edx/999/2013_Spring"
but this format could change.
"""
if course_id is None:
raise NotImplemented("Must provide a course ID to access about page")
return BASE_URL + "/courses/" + course_id + "about"
def is_browser_on_page(self):
return self.is_css_present('section.course-info')
def register(self):
"""
Register for the course on the page.
"""
self.css_click('a.register')
self.ui.wait_for_page('lms.register')
from bok_choy.page_object import PageObject
from ..lms import BASE_URL
class CourseInfoPage(PageObject):
"""
Course info.
"""
@property
def name(self):
return "lms.course_info"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, course_id=None):
"""
Go directly to the course info page for `course_id`.
(e.g. "edX/Open_DemoX/edx_demo_course")
"""
return BASE_URL + "/courses/" + course_id + "/info"
def is_browser_on_page(self):
return self.is_css_present('section.updates')
def num_updates(self):
"""
Return the number of updates on the page.
"""
return self.css_count('section.updates ol li')
def handout_links(self):
"""
Return a list of handout assets links.
"""
return self.css_map('section.handouts ol li a', lambda el: el['href'])
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise, fulfill_after
from ..lms import BASE_URL
class CourseNavPage(PageObject):
"""
Navigate sections and sequences in the courseware.
"""
@property
def name(self):
return "lms.course_nav"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, **kwargs):
"""
Since course navigation appears on multiple pages,
it doesn't have a particular URL.
"""
raise NotImplemented
def is_browser_on_page(self):
return self.is_css_present('section.course-index')
@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.
"""
# Dict to store the result
nav_dict = dict()
section_titles = self._section_titles()
# Get the section titles for each chapter
for sec_index in range(len(section_titles)):
sec_title = section_titles[sec_index]
if len(section_titles) < 1:
self.warning("Could not find subsections for '{0}'".format(sec_title))
else:
# Add one to convert list index (starts at 0) to CSS index (starts at 1)
nav_dict[sec_title] = self._subsection_titles(sec_index + 1)
return nav_dict
@property
def sequence_items(self):
"""
Return a list of sequence items on the page.
Sequence items are one level below subsections in the course nav.
Example return value:
['Chemical Bonds Video', 'Practice Problems', 'Homework']
"""
seq_css = 'ol#sequence-list>li>a>p'
return self.css_map(seq_css, lambda el: el.html.strip().split('\n')[0])
def go_to_section(self, section_title, subsection_title):
"""
Go to the section 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")
"""
# For test stability, disable JQuery animations (opening / closing menus)
self.disable_jquery_animations()
# Get the section by index
try:
sec_index = self._section_titles().index(section_title)
except ValueError:
self.warning("Could not find section '{0}'".format(section_title))
return
# Click the section to ensure it's open (no harm in clicking twice if it's already open)
# Add one to convert from list index to CSS index
section_css = 'nav>div.chapter:nth-of-type({0})>h3>a'.format(sec_index + 1)
self.css_click(section_css)
# Get the subsection by index
try:
subsec_index = self._subsection_titles(sec_index + 1).index(subsection_title)
except ValueError:
msg = "Could not find subsection '{0}' in section '{1}'".format(subsection_title, section_title)
self.warning(msg)
return
# Convert list indices (start at zero) to CSS indices (start at 1)
subsection_css = "nav>div.chapter:nth-of-type({0})>ul>li:nth-of-type({1})>a".format(
sec_index + 1, subsec_index + 1
)
# Click the subsection and ensure that the page finishes reloading
with fulfill_after(self._on_section_promise(section_title, subsection_title)):
self.css_click(subsection_css)
def go_to_sequential(self, sequential_title):
"""
Within a section/subsection, navigate to the sequential with `sequential_title`.
"""
# Get the index of the item in the sequence
all_items = self.sequence_items
try:
seq_index = all_items.index(sequential_title)
except ValueError:
msg = "Could not find sequential '{0}'. Available sequentials: [{1}]".format(
sequential_title, ", ".join(all_items)
)
self.warning(msg)
else:
# Click on the sequence item at the correct index
# Convert the list index (starts at 0) to a CSS index (starts at 1)
seq_css = "ol#sequence-list>li:nth-of-type({0})>a".format(seq_index + 1)
self.css_click(seq_css)
def _section_titles(self):
"""
Return a list of all section titles on the page.
"""
chapter_css = 'nav>div.chapter>h3>a'
return self.css_map(chapter_css, lambda el: el.text.strip())
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 1).
"""
# Retrieve the subsection title for the section
# Add one to the list index to get the CSS index, which starts at one
subsection_css = 'nav>div.chapter:nth-of-type({0})>ul>li>a>p:nth-of-type(1)'.format(section_index)
# If the element is visible, we can get its text directly
# Otherwise, we need to get the HTML
# It *would* make sense to always get the HTML, but unfortunately
# the open tab has some child <span> tags that we don't want.
return self.css_map(
subsection_css,
lambda el: el.text.strip().split('\n')[0] if el.visible else el.html.strip()
)
def _on_section_promise(self, section_title, subsection_title):
"""
Return a `Promise` that is fulfilled when the user is on
the correct section and subsection.
"""
desc = "currently at section '{0}' and subsection '{1}'".format(section_title, subsection_title)
return EmptyPromise(
lambda: self._is_on_section(section_title, subsection_title), desc
)
def _is_on_section(self, section_title, subsection_title):
"""
Return a boolean indicating whether the user is on the section and subsection
with the specified titles.
This assumes that the currently expanded section is the one we're on
That's true right after we click the section/subsection, but not true in general
(the user could go to a section, then expand another tab).
"""
current_section_list = self.css_text('nav>div.chapter.is-open>h3>a')
current_subsection_list = self.css_text('nav>div.chapter.is-open li.active>a>p')
if len(current_section_list) == 0:
self.warning("Could not find the current section")
return False
elif len(current_subsection_list) == 0:
self.warning("Could not find current subsection")
return False
else:
return (
current_section_list[0].strip() == section_title and
current_subsection_list[0].strip().split('\n')[0] == subsection_title
)
from bok_choy.page_object import PageObject
from ..lms import BASE_URL
class DashboardPage(PageObject):
"""
Student dashboard, where the student can view
courses she/he has registered for.
"""
@property
def name(self):
return "lms.dashboard"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, **kwargs):
return BASE_URL + "/dashboard"
def is_browser_on_page(self):
return self.is_css_present('section.my-courses')
def available_courses(self):
"""
Return list of the names of available courses (e.g. "999 edX Demonstration Course")
"""
return self.css_text('section.info > hgroup > h3 > a')
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.css_click(link_css)
else:
msg = "No links found for course {0}".format(course_id)
self.warning(msg)
def _link_css(self, course_id):
# Get the link hrefs for all courses
all_links = self.css_map('a.enter-course', lambda el: el['href'])
# 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
from bok_choy.page_object import PageObject
from bok_choy.promise import BrokenPromise
from ..lms import BASE_URL
class FindCoursesPage(PageObject):
"""
Find courses page (main page of the LMS).
"""
@property
def name(self):
return "lms.find_courses"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
return BASE_URL
def is_browser_on_page(self):
return self.browser.title == "edX"
def course_id_list(self):
"""
Retrieve the list of available course IDs
on the page.
"""
return self.css_map('article.course', lambda el: el['id'])
def go_to_course(self, course_id):
"""
Navigate to the course with `course_id`.
Currently the course id has the form
edx/999/2013_Spring, but this could change.
"""
# Try clicking the link directly
try:
css = 'a[href="/courses/{0}/about"]'.format(course_id)
# In most browsers, there are multiple links
# that match this selector, most without text
# In IE 10, only the second one works.
# In IE 9, there is only one link
if self.css_count(css) > 1:
index = 1
else:
index = 0
self.css_click(css + ":nth-of-type({0})".format(index))
# Chrome gives an error that another element would receive the click.
# So click higher up in the DOM
except BrokenPromise:
# We need to escape forward slashes in the course_id
# to create a valid CSS selector
course_id = course_id.replace('/', '\/')
self.css_click('article.course#{0}'.format(course_id))
# Ensure that we end up on the next page
self.ui.wait_for_page('lms.course_about')
from bok_choy.page_object import PageObject
from ..lms import BASE_URL
class InfoPage(PageObject):
"""
Info pages for the main site.
These are basically static pages, so we use one page
object to represent them all.
"""
# Dictionary mapping section names to URL paths
SECTION_PATH = {
'about': '/about',
'faq': '/faq',
'press': '/press',
'contact': '/contact',
'terms': '/tos',
'privacy': '/privacy',
'honor': '/honor',
}
# Dictionary mapping URLs to expected css selector
EXPECTED_CSS = {
'/about': 'section.vision',
'/faq': 'section.faq',
'/press': 'section.press',
'/contact': 'section.contact',
'/tos': 'section.tos',
'/privacy': 'section.privacy-policy',
'/honor': 'section.honor-code',
}
@property
def name(self):
return "lms.info"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, section=None):
return BASE_URL + self.SECTION_PATH[section]
def is_browser_on_page(self):
# Find the appropriate css based on the URL
for url_path, css_sel in self.EXPECTED_CSS.iteritems():
if self.browser.url.endswith(url_path):
return self.is_css_present(css_sel)
# Could not find the CSS based on the URL
return False
@classmethod
def sections(cls):
return cls.SECTION_PATH.keys()
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise, fulfill_after
from ..lms import BASE_URL
class LoginPage(PageObject):
"""
Login page for the LMS.
"""
@property
def name(self):
return "lms.login"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
return BASE_URL + "/login"
def is_browser_on_page(self):
return any([
'log in' in title.lower()
for title in self.css_text('span.title-super')
])
def login(self, email, password):
"""
Attempt to log in using `email` and `password`.
"""
# Ensure that we make it to another page
on_next_page = EmptyPromise(
lambda: "login" not in self.browser.url,
"redirected from the login page"
)
with fulfill_after(on_next_page):
self.css_fill('input#email', email)
self.css_fill('input#password', password)
self.css_click('button#submit')
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise, fulfill_after, fulfill_before
class OpenResponsePage(PageObject):
"""
Open-ended response in the courseware.
"""
@property
def name(self):
return "lms.open_response"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
"""
Open-response isn't associated with a particular URL.
"""
raise NotImplemented
def is_browser_on_page(self):
return self.is_css_present('section.xmodule_CombinedOpenEndedModule')
@property
def assessment_type(self):
"""
Return the type of assessment currently active.
Options are "self", "ai", or "peer"
"""
labels = self.css_text('section#combined-open-ended-status>div.statusitem-current')
if len(labels) < 1:
self.warning("Could not find assessment type label")
# Provide some tolerance to UI changes
label_compare = labels[0].lower().strip()
if 'self' in label_compare:
return 'self'
elif 'ai' in label_compare:
return 'ai'
elif 'peer' in label_compare:
return 'peer'
else:
raise ValueError("Unexpected assessment type: '{0}'".format(label))
@property
def prompt(self):
"""
Return an HTML string representing the essay prompt.
"""
prompt_css = "section.open-ended-child>div.prompt"
prompts = self.css_map(prompt_css, lambda el: el.html.strip())
if len(prompts) == 0:
self.warning("Could not find essay prompt on page.")
return ""
elif len(prompts) > 1:
self.warning("Multiple essay prompts found on page; using the first one.")
return prompts[0]
@property
def has_rubric(self):
"""
Return a boolean indicating whether the rubric is available.
"""
return self.is_css_present('div.rubric')
@property
def rubric_categories(self):
"""
Return a list of categories available in the essay rubric.
Example:
["Writing Applications", "Language Conventions"]
The rubric is not always visible; if it's not available,
this will return an empty list.
"""
return self.css_text('span.rubric-category')
@property
def rubric_feedback(self):
"""
Return a list of correct/incorrect feedback for each rubric category (e.g. from self-assessment).
Example: ['correct', 'incorrect']
If no feedback is available, returns an empty list.
If feedback could not be interpreted (unexpected CSS class),
the list will contain a `None` item.
"""
# Get the green checkmark / red x labels
# We need to filter out the similar-looking CSS classes
# for the rubric items that are NOT marked correct/incorrect
feedback_css = 'div.rubric-label>label'
labels = filter(
lambda el_class: el_class != 'rubric-elements-info',
self.css_map(feedback_css, lambda el: el['class'])
)
# Map CSS classes on the labels to correct/incorrect
def map_feedback(css_class):
if 'choicegroup_incorrect' in css_class:
return 'incorrect'
elif 'choicegroup_correct' in css_class:
return 'correct'
else:
return None
return map(map_feedback, labels)
@property
def alert_message(self):
"""
Alert message displayed to the user.
"""
alerts = self.css_text("div.open-ended-alert")
if len(alerts) < 1:
return ""
else:
return alerts[0]
@property
def grader_status(self):
"""
Status message from the grader.
If not present, return an empty string.
"""
status_list = self.css_text('div.grader-status')
if len(status_list) < 1:
self.warning("No grader status found")
return ""
elif len(status_list) > 1:
self.warning("Multiple grader statuses found; returning the first one")
return status_list[0]
def set_response(self, response_str):
"""
Input a response to the prompt.
"""
input_css = "textarea.short-form-response"
self.css_fill(input_css, response_str)
def save_response(self):
"""
Save the response for later submission.
"""
status_msg_shown = EmptyPromise(
lambda: 'save' in self.alert_message.lower(),
"Status message saved"
)
with fulfill_after(status_msg_shown):
self.css_click('input.save-button')
def submit_response(self):
"""
Submit a response for grading.
"""
with fulfill_after(self._submitted_promise(self.assessment_type)):
with self.handle_alert():
self.css_click('input.submit-button')
def submit_self_assessment(self, scores):
"""
Submit a self-assessment rubric.
`scores` is a list of scores (0 to max score) for each category in the rubric.
"""
# Warn if we have the wrong number of scores
num_categories = len(self.rubric_categories)
if len(scores) != num_categories:
msg = "Recieved {0} scores but there are {1} rubric categories".format(
len(scores), num_categories
)
self.warning(msg)
# Set the score for each category
for score_index in range(len(scores)):
# Check that we have the enough radio buttons
category_css = "div.rubric>ul.rubric-list:nth-of-type({0})".format(score_index + 1)
if scores[score_index] > self.css_count(category_css + ' input.score-selection'):
msg = "Tried to select score {0} but there are only {1} options".format(score_num, len(inputs))
self.warning(msg)
# Check the radio button at the correct index
else:
input_css = (category_css +
">li.rubric-list-item:nth-of-type({0}) input.score-selection".format(
scores[score_index] + 1)
)
self.css_check(input_css)
# Wait for the button to become enabled
button_css = 'input.submit-button'
button_enabled = EmptyPromise(
lambda: all(self.css_map(button_css, lambda el: not el['disabled'])),
"Submit button enabled"
)
# Submit the assessment
with fulfill_before(button_enabled):
self.css_click(button_css)
def _submitted_promise(self, assessment_type):
"""
Return a `Promise` that the next step is visible after submitting.
This will vary based on the type of assessment.
`assessment_type` is either 'self', 'ai', or 'peer'
"""
if assessment_type == 'self':
return EmptyPromise(lambda: self.has_rubric, "Rubric has appeared")
elif assessment_type == 'ai':
return EmptyPromise(
lambda: self.grader_status != 'Unanswered',
"Problem status is no longer 'unanswered'"
)
elif assessment_type == 'peer':
return EmptyPromise(lambda: False, "Peer assessment not yet implemented")
else:
self.warning("Unrecognized assessment type '{0}'".format(assessment_type))
return EmptyPromise(lambda: True, "Unrecognized assessment type")
from bok_choy.page_object import PageObject
from ..lms import BASE_URL
class ProgressPage(PageObject):
"""
Student progress page.
"""
@property
def name(self):
return "lms.progress"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, course_id=None):
return BASE_URL + "/courses/" + course_id + "/progress"
def is_browser_on_page(self):
has_course_info = self.is_css_present('section.course-info')
has_graph = self.is_css_present('div#grade-detail-graph')
return has_course_info and has_graph
def scores(self, chapter, section):
"""
Return a list of (points, max_points) tuples representing the scores
for the section.
Example:
section_scores('Week 1', 'Lesson 1', 2) --> [(2, 4), (0, 1)]
Returns `None` if no such chapter and section can be found.
"""
# Find the index of the section in the chapter
chapter_index = self._chapter_index(chapter)
if chapter_index is None:
return None
section_index = self._section_index(chapter_index, section)
if section_index is None:
return None
# Retrieve the scores for the section
return self._section_scores(chapter_index, section_index)
def _chapter_index(self, title):
"""
Return the CSS index of the chapter with `title`.
Returns `None` if it cannot find such a chapter.
"""
chapter_css = 'ol.chapters li h2'
chapter_titles = self.css_map(chapter_css, lambda el: el.text.lower().strip())
try:
# CSS indices are 1-indexed, so add one to the list index
return chapter_titles.index(title.lower()) + 1
except ValueError:
self.warning("Could not find chapter '{0}'".format(title))
return None
def _section_index(self, chapter_index, title):
"""
Return the CSS index of the section with `title` in the chapter at `chapter_index`.
Returns `None` if it can't find such a section.
"""
# This is a hideous CSS selector that means:
# Get the links containing the section titles in `chapter_index`.
# The link text is the section title.
section_css = 'ol.chapters>li:nth-of-type({0}) ol.sections li h3 a'.format(chapter_index)
section_titles = self.css_map(section_css, lambda el: el.text.lower().strip())
# The section titles also contain "n of m possible points" on the second line
# We have to remove this to find the right title
section_titles = [title.split('\n')[0] for title in section_titles]
# Some links are blank, so remove them
section_titles = [title for title in section_titles if title]
try:
# CSS indices are 1-indexed, so add one to the list index
return section_titles.index(title.lower()) + 1
except ValueError:
self.warning("Could not find section '{0}'".format(title))
return None
def _section_scores(self, chapter_index, section_index):
"""
Return a list of `(points, max_points)` tuples representing
the scores in the specified chapter and section.
`chapter_index` and `section_index` start at 1.
"""
# This is CSS selector means:
# Get the scores for the chapter at `chapter_index` and the section at `section_index`
# Example text of the retrieved elements: "0/1"
score_css = "ol.chapters>li:nth-of-type({0}) ol.sections>li:nth-of-type({1}) section.scores>ol>li".format(
chapter_index, section_index
)
text_scores = self.css_text(score_css)
# Convert text scores to tuples of (points, max_points)
return [tuple(map(int, score.split('/'))) for score in text_scores]
from bok_choy.page_object import PageObject
from ..lms import BASE_URL
class RegisterPage(PageObject):
"""
Registration page (create a new account)
"""
@property
def name(self):
return "lms.register"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, course_id=None):
"""
URL for the registration page of a course.
Course ID is currently of the form "edx/999/2013_Spring"
but this format could change.
"""
if course_id is None:
raise NotImplemented("Must provide a course ID to access about page")
return BASE_URL + "/register?course_id=" + course_id + "&enrollment_action=enroll"
def is_browser_on_page(self):
return any([
'register' in title.lower()
for title in self.css_text('span.title-sub')
])
def provide_info(self, credentials):
"""
Fill in registration info.
`credentials` is a `TestCredential` object.
"""
self.css_fill('input#email', credentials.email)
self.css_fill('input#password', credentials.password)
self.css_fill('input#username', credentials.username)
self.css_fill('input#name', credentials.full_name)
self.css_check('input#tos-yes')
self.css_check('input#honorcode-yes')
def submit(self):
"""
Submit registration info to create an account.
"""
self.css_click('button#submit')
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise, fulfill_after
from ..lms import BASE_URL
class TabNavPage(PageObject):
"""
High-level tab navigation.
"""
@property
def name(self):
return "lms.tab_nav"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self, **kwargs):
"""
Since tab navigation appears on multiple pages,
it doesn't have a particular URL.
"""
raise NotImplemented
def is_browser_on_page(self):
return self.is_css_present('ol.course-tabs')
def go_to_tab(self, tab_name):
"""
Navigate to the tab `tab_name`.
"""
if tab_name not in ['Courseware', 'Course Info', 'Discussion', 'Wiki', 'Progress']:
self.warning("'{0}' is not a valid tab name".format(tab_name))
# The only identifier for individual tabs is the link href
# so we find the tab with `tab_name` in its text.
tab_css = self._tab_css(tab_name)
with fulfill_after(self._is_on_tab_promise(tab_name)):
if tab_css is not None:
self.css_click(tab_css)
else:
self.warning("No tabs found for '{0}'".format(tab_name))
def _tab_css(self, tab_name):
"""
Return the CSS to click for `tab_name`.
"""
all_tabs = self.css_text('ol.course-tabs li a')
try:
tab_index = all_tabs.index(tab_name)
except ValueError:
return None
else:
return 'ol.course-tabs li:nth-of-type({0}) a'.format(tab_index + 1)
def _is_on_tab_promise(self, tab_name):
"""
Return a `Promise` that the user is on the tab `tab_name`.
"""
return EmptyPromise(
lambda: self._is_on_tab(tab_name),
"{0} is the current tab".format(tab_name)
)
def _is_on_tab(self, tab_name):
"""
Return a boolean indicating whether the current tab is `tab_name`.
"""
current_tab_list = self.css_text('ol.course-tabs>li>a.active')
if len(current_tab_list) == 0:
self.warning("Could not find current tab")
return False
else:
return (current_tab_list[0].strip().split('\n')[0] == tab_name)
import time
from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise, fulfill_after
from ..lms import BASE_URL
class VideoPage(PageObject):
"""
Video player in the courseware.
"""
@property
def name(self):
return "lms.video"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
"""
Video players aren't associated with a particular URL.
"""
raise NotImplemented
def is_browser_on_page(self):
return self.is_css_present('section.xmodule_VideoModule')
@property
def elapsed_time(self):
"""
Amount of time elapsed since the start of the video, in seconds.
"""
elapsed, _ = self._video_time()
return elapsed
@property
def duration(self):
"""
Total duration of the video, in seconds.
"""
_, duration = self._video_time()
return duration
@property
def is_playing(self):
"""
Return a boolean indicating whether the video is playing.
"""
return self.is_css_present('a.video_control') and self.is_css_present('a.video_control.pause')
@property
def is_paused(self):
"""
Return a boolean indicating whether the video is paused.
"""
return self.is_css_present('a.video_control') and self.is_css_present('a.video_control.play')
def play(self):
"""
Start playing the video.
"""
with fulfill_after(
EmptyPromise(lambda: self.is_playing, "Video is playing")
):
self.css_click('a.video_control.play')
def pause(self):
"""
Pause the video.
"""
with fulfill_after(
EmptyPromise(lambda: self.is_paused, "Video is paused")
):
self.css_click('a.video_control.pause')
def _video_time(self):
"""
Return a tuple `(elapsed_time, duration)`, each in seconds.
"""
# The full time has the form "0:32 / 3:14"
all_times = self.css_text('div.vidtime')
if len(all_times) == 0:
self.warning('Could not find video time')
else:
full_time = all_times[0]
# Split the time at the " / ", to get ["0:32", "3:14"]
elapsed_str, duration_str = full_time.split(' / ')
# Convert each string to seconds
return (self._parse_time_str(elapsed_str), self._parse_time_str(duration_str))
def _parse_time_str(self, time_str):
"""
Parse a string of the form 1:23 into seconds (int).
"""
time_obj = time.strptime(time_str, '%M:%S')
return time_obj.tm_min * 60 + time_obj.tm_sec
import os
# Get the URL of the instance under test
BASE_URL = os.environ.get('test_url', '')
from bok_choy.page_object import PageObject
from ..studio import BASE_URL
class HowitworksPage(PageObject):
"""
Home page for Studio when not logged in.
"""
@property
def name(self):
return "studio.howitworks"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
return BASE_URL + "/howitworks"
def is_browser_on_page(self):
return self.browser.title == 'Welcome | edX Studio'
from bok_choy.page_object import PageObject
from ..studio import BASE_URL
class LoginPage(PageObject):
"""
Login page for Studio.
"""
@property
def name(self):
return "studio.login"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
return BASE_URL + "/signin"
def is_browser_on_page(self):
return self.browser.title == 'Sign In | edX Studio'
def login(self, email, password):
"""
Attempt to log in using `email` and `password`.
"""
self.css_fill('input#email', email)
self.css_fill('input#password', password)
self.css_click('button#submit')
from bok_choy.page_object import PageObject
from ..studio import BASE_URL
class SignupPage(PageObject):
"""
Signup page for Studio.
"""
@property
def name(self):
return "studio.signup"
@property
def requirejs(self):
return []
@property
def js_globals(self):
return []
def url(self):
return BASE_URL + "/signup"
def is_browser_on_page(self):
return self.browser.title == 'Sign Up | edX Studio'
......@@ -21,3 +21,4 @@
-e git+https://github.com/edx/js-test-tool.git@v0.1.4#egg=js_test_tool
-e git+https://github.com/edx/django-waffle.git@823a102e48#egg=django-waffle
-e git+https://github.com/edx/event-tracking.git@f0211d702d#egg=event-tracking
-e git+https://github.com/edx/bok-choy.git@bc6f1adbe439618162079f1004b2b3db3b6f8916#egg=bok_choy
#!/usr/bin/env python
"""
Install Selenium page objects for acceptance and end-to-end tests.
"""
from setuptools import setup
VERSION = '0.0.1'
DESCRIPTION = "Selenium page objects for edx-platform"
setup(
name='edx-selenium-pages',
version=VERSION,
author='edX',
url='http://github.com/edx/edx-platform',
description=DESCRIPTION,
license='AGPL',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Software Development :: Testing',
'Topic :: Software Development :: Quality Assurance'
],
packages=['edxapp_selenium_pages']
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment