Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
d88d8612
Commit
d88d8612
authored
Mar 22, 2014
by
Jay Zoldak
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3017 from edx/zoldak/bok-choy-version-upgrade
Update edx-platform page objects and tests
parents
a00007b8
bb8800d4
Hide whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
353 additions
and
299 deletions
+353
-299
AUTHORS
+2
-0
common/test/acceptance/pages/lms/course_about.py
+3
-2
common/test/acceptance/pages/lms/course_info.py
+3
-3
common/test/acceptance/pages/lms/course_nav.py
+19
-20
common/test/acceptance/pages/lms/dashboard.py
+16
-8
common/test/acceptance/pages/lms/discussion_single_thread.py
+45
-39
common/test/acceptance/pages/lms/find_courses.py
+2
-1
common/test/acceptance/pages/lms/login.py
+18
-2
common/test/acceptance/pages/lms/open_response.py
+17
-18
common/test/acceptance/pages/lms/peer_calibrate.py
+13
-7
common/test/acceptance/pages/lms/peer_confirm.py
+9
-4
common/test/acceptance/pages/lms/peer_grade.py
+11
-6
common/test/acceptance/pages/lms/progress.py
+6
-5
common/test/acceptance/pages/lms/register.py
+8
-8
common/test/acceptance/pages/lms/rubric.py
+30
-15
common/test/acceptance/pages/lms/tab_nav.py
+13
-12
common/test/acceptance/pages/lms/video.py
+10
-10
common/test/acceptance/pages/studio/asset_index.py
+1
-1
common/test/acceptance/pages/studio/auto_auth.py
+1
-1
common/test/acceptance/pages/studio/checklists.py
+1
-1
common/test/acceptance/pages/studio/container.py
+7
-5
common/test/acceptance/pages/studio/course_import.py
+1
-1
common/test/acceptance/pages/studio/course_info.py
+1
-1
common/test/acceptance/pages/studio/edit_subsection.py
+1
-1
common/test/acceptance/pages/studio/edit_tabs.py
+1
-1
common/test/acceptance/pages/studio/export.py
+1
-1
common/test/acceptance/pages/studio/howitworks.py
+1
-1
common/test/acceptance/pages/studio/index.py
+1
-1
common/test/acceptance/pages/studio/login.py
+8
-9
common/test/acceptance/pages/studio/manage_users.py
+1
-1
common/test/acceptance/pages/studio/overview.py
+20
-12
common/test/acceptance/pages/studio/settings.py
+1
-1
common/test/acceptance/pages/studio/settings_advanced.py
+1
-1
common/test/acceptance/pages/studio/settings_graders.py
+1
-1
common/test/acceptance/pages/studio/signup.py
+1
-1
common/test/acceptance/pages/studio/textbooks.py
+1
-1
common/test/acceptance/pages/studio/unit.py
+22
-20
common/test/acceptance/pages/xblock/acid.py
+7
-5
common/test/acceptance/tests/test_discussion.py
+3
-2
common/test/acceptance/tests/test_lms.py
+23
-51
common/test/acceptance/tests/test_ora.py
+16
-10
common/test/acceptance/tests/test_studio.py
+5
-8
requirements/edx/github.txt
+1
-1
No files found.
AUTHORS
View file @
d88d8612
...
@@ -135,3 +135,5 @@ David Glance <david.glance@gmail.com>
...
@@ -135,3 +135,5 @@ David Glance <david.glance@gmail.com>
Nimisha Asthagiri <nasthagiri@edx.org>
Nimisha Asthagiri <nasthagiri@edx.org>
Martyn James <mjames@edx.org>
Martyn James <mjames@edx.org>
Han Su Kim <hkim823@gmail.com>
Han Su Kim <hkim823@gmail.com>
Raees Chachar <raees.chachar@arbisoft.com>
Muhammad Ammar <muhammad.ammar@arbisoft.com>
common/test/acceptance/pages/lms/course_about.py
View file @
d88d8612
...
@@ -14,7 +14,8 @@ class CourseAboutPage(CoursePage):
...
@@ -14,7 +14,8 @@ class CourseAboutPage(CoursePage):
url_path
=
"about"
url_path
=
"about"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'section.course-info'
)
return
self
.
q
(
css
=
'section.course-info'
)
.
present
def
register
(
self
):
def
register
(
self
):
"""
"""
...
@@ -22,7 +23,7 @@ class CourseAboutPage(CoursePage):
...
@@ -22,7 +23,7 @@ class CourseAboutPage(CoursePage):
Waits for the registration page to load, then
Waits for the registration page to load, then
returns the registration page object.
returns the registration page object.
"""
"""
self
.
css_click
(
'a.register'
)
self
.
q
(
css
=
'a.register'
)
.
first
.
click
(
)
registration_page
=
RegisterPage
(
self
.
browser
,
self
.
course_id
)
registration_page
=
RegisterPage
(
self
.
browser
,
self
.
course_id
)
registration_page
.
wait_for_page
()
registration_page
.
wait_for_page
()
...
...
common/test/acceptance/pages/lms/course_info.py
View file @
d88d8612
...
@@ -13,18 +13,18 @@ class CourseInfoPage(CoursePage):
...
@@ -13,18 +13,18 @@ class CourseInfoPage(CoursePage):
url_path
=
"info"
url_path
=
"info"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'section.updates'
)
return
self
.
q
(
css
=
'section.updates'
)
.
present
@property
@property
def
num_updates
(
self
):
def
num_updates
(
self
):
"""
"""
Return the number of updates on the page.
Return the number of updates on the page.
"""
"""
return
self
.
css_count
(
'section.updates section article'
)
return
len
(
self
.
q
(
css
=
'section.updates section article'
)
.
results
)
@property
@property
def
handout_links
(
self
):
def
handout_links
(
self
):
"""
"""
Return a list of handout assets links.
Return a list of handout assets links.
"""
"""
return
self
.
css_map
(
'section.handouts ol li a'
,
lambda
el
:
el
[
'href'
])
return
self
.
q
(
css
=
'section.handouts ol li a'
)
.
map
(
lambda
el
:
el
.
get_attribute
(
'href'
))
.
results
common/test/acceptance/pages/lms/course_nav.py
View file @
d88d8612
...
@@ -4,7 +4,7 @@ Course navigation page object
...
@@ -4,7 +4,7 @@ Course navigation page object
import
re
import
re
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
from
bok_choy.promise
import
EmptyPromise
class
CourseNavPage
(
PageObject
):
class
CourseNavPage
(
PageObject
):
...
@@ -15,7 +15,7 @@ class CourseNavPage(PageObject):
...
@@ -15,7 +15,7 @@ class CourseNavPage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'div.course-index'
)
return
self
.
q
(
css
=
'div.course-index'
)
.
present
@property
@property
def
sections
(
self
):
def
sections
(
self
):
...
@@ -38,9 +38,7 @@ class CourseNavPage(PageObject):
...
@@ -38,9 +38,7 @@ class CourseNavPage(PageObject):
section_titles
=
self
.
_section_titles
()
section_titles
=
self
.
_section_titles
()
# Get the section titles for each chapter
# Get the section titles for each chapter
for
sec_index
in
range
(
len
(
section_titles
)):
for
sec_index
,
sec_title
in
enumerate
(
section_titles
):
sec_title
=
section_titles
[
sec_index
]
if
len
(
section_titles
)
<
1
:
if
len
(
section_titles
)
<
1
:
self
.
warning
(
"Could not find subsections for '{0}'"
.
format
(
sec_title
))
self
.
warning
(
"Could not find subsections for '{0}'"
.
format
(
sec_title
))
...
@@ -60,7 +58,7 @@ class CourseNavPage(PageObject):
...
@@ -60,7 +58,7 @@ class CourseNavPage(PageObject):
['Chemical Bonds Video', 'Practice Problems', 'Homework']
['Chemical Bonds Video', 'Practice Problems', 'Homework']
"""
"""
seq_css
=
'ol#sequence-list>li>a>p'
seq_css
=
'ol#sequence-list>li>a>p'
return
self
.
css_map
(
seq_css
,
self
.
_clean_seq_titles
)
return
self
.
q
(
css
=
seq_css
)
.
map
(
self
.
_clean_seq_titles
)
.
results
def
go_to_section
(
self
,
section_title
,
subsection_title
):
def
go_to_section
(
self
,
section_title
,
subsection_title
):
"""
"""
...
@@ -73,7 +71,7 @@ class CourseNavPage(PageObject):
...
@@ -73,7 +71,7 @@ class CourseNavPage(PageObject):
"""
"""
# For test stability, disable JQuery animations (opening / closing menus)
# For test stability, disable JQuery animations (opening / closing menus)
self
.
disable_jquery_animations
(
)
self
.
browser
.
execute_script
(
"jQuery.fx.off = true;"
)
# Get the section by index
# Get the section by index
try
:
try
:
...
@@ -85,7 +83,7 @@ class CourseNavPage(PageObject):
...
@@ -85,7 +83,7 @@ class CourseNavPage(PageObject):
# Click the section to ensure it's open (no harm in clicking twice if it's already open)
# 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
# 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
)
section_css
=
'nav>div.chapter:nth-of-type({0})>h3>a'
.
format
(
sec_index
+
1
)
self
.
css_click
(
section_css
)
self
.
q
(
css
=
section_css
)
.
first
.
click
(
)
# Get the subsection by index
# Get the subsection by index
try
:
try
:
...
@@ -101,8 +99,9 @@ class CourseNavPage(PageObject):
...
@@ -101,8 +99,9 @@ class CourseNavPage(PageObject):
)
)
# Click the subsection and ensure that the page finishes reloading
# Click the subsection and ensure that the page finishes reloading
with
fulfill_after
(
self
.
_on_section_promise
(
section_title
,
subsection_title
)):
self
.
q
(
css
=
subsection_css
)
.
first
.
click
()
self
.
css_click
(
subsection_css
)
self
.
_on_section_promise
(
section_title
,
subsection_title
)
.
fulfill
()
def
go_to_sequential
(
self
,
sequential_title
):
def
go_to_sequential
(
self
,
sequential_title
):
"""
"""
...
@@ -126,14 +125,14 @@ class CourseNavPage(PageObject):
...
@@ -126,14 +125,14 @@ class CourseNavPage(PageObject):
# Click on the sequence item at the correct index
# Click on the sequence item at the correct index
# Convert the list index (starts at 0) to a CSS index (starts at 1)
# 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
)
seq_css
=
"ol#sequence-list>li:nth-of-type({0})>a"
.
format
(
seq_index
+
1
)
self
.
css_click
(
seq_css
)
self
.
q
(
css
=
seq_css
)
.
first
.
click
(
)
def
_section_titles
(
self
):
def
_section_titles
(
self
):
"""
"""
Return a list of all section titles on the page.
Return a list of all section titles on the page.
"""
"""
chapter_css
=
'nav
>div.chapter>h3>
a'
chapter_css
=
'nav
> div.chapter > h3 >
a'
return
self
.
css_map
(
chapter_css
,
lambda
el
:
el
.
text
.
strip
())
return
self
.
q
(
css
=
chapter_css
)
.
map
(
lambda
el
:
el
.
text
.
strip
())
.
results
def
_subsection_titles
(
self
,
section_index
):
def
_subsection_titles
(
self
,
section_index
):
"""
"""
...
@@ -148,10 +147,10 @@ class CourseNavPage(PageObject):
...
@@ -148,10 +147,10 @@ class CourseNavPage(PageObject):
# Otherwise, we need to get the HTML
# Otherwise, we need to get the HTML
# It *would* make sense to always get the HTML, but unfortunately
# It *would* make sense to always get the HTML, but unfortunately
# the open tab has some child <span> tags that we don't want.
# the open tab has some child <span> tags that we don't want.
return
self
.
css_map
(
return
self
.
q
(
subsection_css
,
css
=
subsection_css
)
.
map
(
lambda
el
:
el
.
text
.
strip
()
.
split
(
'
\n
'
)[
0
]
if
el
.
visible
else
el
.
html
.
strip
()
lambda
el
:
el
.
text
.
strip
()
.
split
(
'
\n
'
)[
0
]
if
el
.
is_displayed
()
else
el
.
get_attribute
(
'innerHTML'
)
.
strip
()
)
)
.
results
def
_on_section_promise
(
self
,
section_title
,
subsection_title
):
def
_on_section_promise
(
self
,
section_title
,
subsection_title
):
"""
"""
...
@@ -172,8 +171,8 @@ class CourseNavPage(PageObject):
...
@@ -172,8 +171,8 @@ class CourseNavPage(PageObject):
That's true right after we click the section/subsection, but not true in general
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).
(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_section_list
=
self
.
q
(
css
=
'nav>div.chapter.is-open>h3>a'
)
.
text
current_subsection_list
=
self
.
css_text
(
'nav>div.chapter.is-open li.active>a>p'
)
current_subsection_list
=
self
.
q
(
css
=
'nav>div.chapter.is-open li.active>a>p'
)
.
text
if
len
(
current_section_list
)
==
0
:
if
len
(
current_section_list
)
==
0
:
self
.
warning
(
"Could not find the current section"
)
self
.
warning
(
"Could not find the current section"
)
...
@@ -196,4 +195,4 @@ class CourseNavPage(PageObject):
...
@@ -196,4 +195,4 @@ class CourseNavPage(PageObject):
"""
"""
Clean HTML of sequence titles, stripping out span tags and returning the first line.
Clean HTML of sequence titles, stripping out span tags and returning the first line.
"""
"""
return
self
.
REMOVE_SPAN_TAG_RE
.
sub
(
''
,
element
.
html
)
.
strip
()
.
split
(
'
\n
'
)[
0
]
return
self
.
REMOVE_SPAN_TAG_RE
.
sub
(
''
,
element
.
get_attribute
(
'innerHTML'
)
)
.
strip
()
.
split
(
'
\n
'
)[
0
]
common/test/acceptance/pages/lms/dashboard.py
View file @
d88d8612
...
@@ -4,6 +4,7 @@ Student dashboard page.
...
@@ -4,6 +4,7 @@ Student dashboard page.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
from
.
import
BASE_URL
from
.
import
BASE_URL
...
@@ -16,11 +17,11 @@ class DashboardPage(PageObject):
...
@@ -16,11 +17,11 @@ class DashboardPage(PageObject):
url
=
BASE_URL
+
"/dashboard"
url
=
BASE_URL
+
"/dashboard"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'section.my-courses'
)
return
self
.
q
(
css
=
'section.my-courses'
)
.
present
@property
@property
def
current_courses_text
(
self
):
def
current_courses_text
(
self
):
text_items
=
self
.
css_text
(
'section#my-courses'
)
text_items
=
self
.
q
(
css
=
'section#my-courses'
)
.
text
if
len
(
text_items
)
>
0
:
if
len
(
text_items
)
>
0
:
return
text_items
[
0
]
return
text_items
[
0
]
else
:
else
:
...
@@ -36,7 +37,7 @@ class DashboardPage(PageObject):
...
@@ -36,7 +37,7 @@ class DashboardPage(PageObject):
_
,
course_name
=
el
.
text
.
split
(
' '
,
1
)
_
,
course_name
=
el
.
text
.
split
(
' '
,
1
)
return
course_name
return
course_name
return
self
.
css_map
(
'section.info > hgroup > h3 > a'
,
_get_course_name
)
return
self
.
q
(
css
=
'section.info > hgroup > h3 > a'
)
.
map
(
_get_course_name
)
.
results
def
view_course
(
self
,
course_id
):
def
view_course
(
self
,
course_id
):
"""
"""
...
@@ -45,7 +46,7 @@ class DashboardPage(PageObject):
...
@@ -45,7 +46,7 @@ class DashboardPage(PageObject):
link_css
=
self
.
_link_css
(
course_id
)
link_css
=
self
.
_link_css
(
course_id
)
if
link_css
is
not
None
:
if
link_css
is
not
None
:
self
.
css_click
(
link_css
)
self
.
q
(
css
=
link_css
)
.
first
.
click
(
)
else
:
else
:
msg
=
"No links found for course {0}"
.
format
(
course_id
)
msg
=
"No links found for course {0}"
.
format
(
course_id
)
self
.
warning
(
msg
)
self
.
warning
(
msg
)
...
@@ -55,7 +56,7 @@ class DashboardPage(PageObject):
...
@@ -55,7 +56,7 @@ class DashboardPage(PageObject):
Return a CSS selector for the link to the course with `course_id`.
Return a CSS selector for the link to the course with `course_id`.
"""
"""
# Get the link hrefs for all courses
# Get the link hrefs for all courses
all_links
=
self
.
css_map
(
'a.enter-course'
,
lambda
el
:
el
[
'href'
])
all_links
=
self
.
q
(
css
=
'a.enter-course'
)
.
map
(
lambda
el
:
el
.
get_attribute
(
'href'
))
.
results
# Search for the first link that matches the course id
# Search for the first link that matches the course id
link_index
=
None
link_index
=
None
...
@@ -73,6 +74,13 @@ class DashboardPage(PageObject):
...
@@ -73,6 +74,13 @@ class DashboardPage(PageObject):
"""
"""
Change the language on the dashboard to the language corresponding with `code`.
Change the language on the dashboard to the language corresponding with `code`.
"""
"""
self
.
css_click
(
".edit-language"
)
self
.
q
(
css
=
".edit-language"
)
.
first
.
click
()
self
.
select_option
(
"language"
,
code
)
self
.
q
(
css
=
'select[name="language"] option[value="{}"]'
.
format
(
code
))
.
first
.
click
()
self
.
css_click
(
"#submit-lang"
)
self
.
q
(
css
=
"#submit-lang"
)
.
first
.
click
()
self
.
_changed_lang_promise
(
code
)
.
fulfill
()
def
_changed_lang_promise
(
self
,
code
):
def
_check_func
():
return
self
.
q
(
css
=
'select[name="language"] option[value="{}"]'
.
format
(
code
))
.
selected
return
EmptyPromise
(
_check_func
,
"language changed"
)
common/test/acceptance/pages/lms/discussion_single_thread.py
View file @
d88d8612
from
bok_choy.page_object
import
unguarded
from
bok_choy.page_object
import
unguarded
from
bok_choy.promise
import
EmptyPromise
,
fulfill
from
bok_choy.promise
import
EmptyPromise
from
.course_page
import
CoursePage
from
.course_page
import
CoursePage
...
@@ -10,9 +10,9 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -10,9 +10,9 @@ class DiscussionSingleThreadPage(CoursePage):
self
.
thread_id
=
thread_id
self
.
thread_id
=
thread_id
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
return
self
.
q
(
"body.discussion .discussion-article[data-id='{thread_id}']"
.
format
(
thread_id
=
self
.
thread_id
)
css
=
"body.discussion .discussion-article[data-id='{thread_id}']"
.
format
(
thread_id
=
self
.
thread_id
)
)
)
.
present
@property
@property
@unguarded
@unguarded
...
@@ -24,7 +24,7 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -24,7 +24,7 @@ class DiscussionSingleThreadPage(CoursePage):
Returns the text of the first element matching the given selector, or
Returns the text of the first element matching the given selector, or
None if no such element exists
None if no such element exists
"""
"""
text_list
=
self
.
css_text
(
selector
)
text_list
=
self
.
q
(
css
=
selector
)
.
text
return
text_list
[
0
]
if
text_list
else
None
return
text_list
[
0
]
if
text_list
else
None
def
get_response_total_text
(
self
):
def
get_response_total_text
(
self
):
...
@@ -33,7 +33,7 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -33,7 +33,7 @@ class DiscussionSingleThreadPage(CoursePage):
def
get_num_displayed_responses
(
self
):
def
get_num_displayed_responses
(
self
):
"""Returns the number of responses actually rendered"""
"""Returns the number of responses actually rendered"""
return
self
.
css_count
(
".discussion-response"
)
return
len
(
self
.
q
(
css
=
".discussion-response"
)
.
results
)
def
get_shown_responses_text
(
self
):
def
get_shown_responses_text
(
self
):
"""Returns the shown response count text, or None if not present"""
"""Returns the shown response count text, or None if not present"""
...
@@ -44,12 +44,16 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -44,12 +44,16 @@ class DiscussionSingleThreadPage(CoursePage):
return
self
.
_get_element_text
(
".load-response-button"
)
return
self
.
_get_element_text
(
".load-response-button"
)
def
load_more_responses
(
self
):
def
load_more_responses
(
self
):
"""Clicks the laod more responses button and waits for responses to load"""
"""Clicks the load more responses button and waits for responses to load"""
self
.
css_click
(
".load-response-button"
)
self
.
q
(
css
=
".load-response-button"
)
.
first
.
click
()
fulfill
(
EmptyPromise
(
lambda
:
not
self
.
is_css_present
(
".loading"
),
def
_is_ajax_finished
():
"Loading more responses completed"
return
self
.
browser
.
execute_script
(
"return jQuery.active"
)
==
0
))
EmptyPromise
(
_is_ajax_finished
,
"Loading more Responses"
)
.
fulfill
()
def
has_add_response_button
(
self
):
def
has_add_response_button
(
self
):
"""Returns true if the add response button is visible, false otherwise"""
"""Returns true if the add response button is visible, false otherwise"""
...
@@ -60,14 +64,17 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -60,14 +64,17 @@ class DiscussionSingleThreadPage(CoursePage):
Clicks the add response button and ensures that the response text
Clicks the add response button and ensures that the response text
field receives focus
field receives focus
"""
"""
self
.
css_click
(
".add-response-btn"
)
self
.
q
(
css
=
".add-response-btn"
)
.
first
.
click
(
)
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
self
.
is_css_present
(
"#wmd-input-reply-body-{thread_id}:focus"
.
format
(
thread_id
=
self
.
thread_id
)),
lambda
:
self
.
q
(
css
=
"#wmd-input-reply-body-{thread_id}:focus"
.
format
(
thread_id
=
self
.
thread_id
)),
"Response field received focus"
"Response field received focus"
))
)
.
fulfill
(
)
def
_is_element_visible
(
self
,
selector
):
def
_is_element_visible
(
self
,
selector
):
return
any
(
self
.
css_map
(
selector
,
lambda
el
:
el
.
visible
))
return
(
self
.
q
(
css
=
selector
)
.
present
and
self
.
q
(
css
=
selector
)
.
visible
)
def
is_response_editor_visible
(
self
,
response_id
):
def
is_response_editor_visible
(
self
,
response_id
):
"""Returns true if the response editor is present, false otherwise"""
"""Returns true if the response editor is present, false otherwise"""
...
@@ -75,15 +82,15 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -75,15 +82,15 @@ class DiscussionSingleThreadPage(CoursePage):
def
start_response_edit
(
self
,
response_id
):
def
start_response_edit
(
self
,
response_id
):
"""Click the edit button for the response, loading the editing view"""
"""Click the edit button for the response, loading the editing view"""
self
.
css_click
(
".response_{} .discussion-response .action-edit"
.
format
(
response_id
)
)
self
.
q
(
css
=
".response_{} .discussion-response .action-edit"
.
format
(
response_id
))
.
first
.
click
(
)
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
self
.
is_response_editor_visible
(
response_id
),
lambda
:
self
.
is_response_editor_visible
(
response_id
),
"Response edit started"
"Response edit started"
))
)
.
fulfill
(
)
def
is_add_comment_visible
(
self
,
response_id
):
def
is_add_comment_visible
(
self
,
response_id
):
"""Returns true if the "add comment" form is visible for a response"""
"""Returns true if the "add comment" form is visible for a response"""
return
self
.
_is_element_visible
(
"
.response_{} .new-comment
"
.
format
(
response_id
))
return
self
.
_is_element_visible
(
"
#wmd-input-comment-body-{}
"
.
format
(
response_id
))
def
is_comment_visible
(
self
,
comment_id
):
def
is_comment_visible
(
self
,
comment_id
):
"""Returns true if the comment is viewable onscreen"""
"""Returns true if the comment is viewable onscreen"""
...
@@ -98,11 +105,11 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -98,11 +105,11 @@ class DiscussionSingleThreadPage(CoursePage):
def
delete_comment
(
self
,
comment_id
):
def
delete_comment
(
self
,
comment_id
):
with
self
.
handle_alert
():
with
self
.
handle_alert
():
self
.
css_click
(
"#comment_{} div.action-delete"
.
format
(
comment_id
)
)
self
.
q
(
css
=
"#comment_{} div.action-delete"
.
format
(
comment_id
))
.
first
.
click
(
)
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
not
self
.
is_comment_visible
(
comment_id
),
lambda
:
not
self
.
is_comment_visible
(
comment_id
),
"Deleted comment was removed"
"Deleted comment was removed"
))
)
.
fulfill
(
)
def
is_comment_editable
(
self
,
comment_id
):
def
is_comment_editable
(
self
,
comment_id
):
"""Returns true if the edit comment button is present, false otherwise"""
"""Returns true if the edit comment button is present, false otherwise"""
...
@@ -110,49 +117,48 @@ class DiscussionSingleThreadPage(CoursePage):
...
@@ -110,49 +117,48 @@ class DiscussionSingleThreadPage(CoursePage):
def
is_comment_editor_visible
(
self
,
comment_id
):
def
is_comment_editor_visible
(
self
,
comment_id
):
"""Returns true if the comment editor is present, false otherwise"""
"""Returns true if the comment editor is present, false otherwise"""
return
self
.
_is_element_visible
(
"
#comment_{} .edit-comment-body
"
.
format
(
comment_id
))
return
self
.
_is_element_visible
(
"
.edit-comment-body[data-id='{}']
"
.
format
(
comment_id
))
def
_get_comment_editor_value
(
self
,
comment_id
):
def
_get_comment_editor_value
(
self
,
comment_id
):
return
self
.
css_value
(
"#comment_{} .wmd-input"
.
format
(
comment_id
))
[
0
]
return
self
.
q
(
css
=
"#wmd-input-edit-comment-body-{}"
.
format
(
comment_id
))
.
text
[
0
]
def
start_comment_edit
(
self
,
comment_id
):
def
start_comment_edit
(
self
,
comment_id
):
"""Click the edit button for the comment, loading the editing view"""
"""Click the edit button for the comment, loading the editing view"""
old_body
=
self
.
get_comment_body
(
comment_id
)
old_body
=
self
.
get_comment_body
(
comment_id
)
self
.
css_click
(
"#comment_{} .action-edit"
.
format
(
comment_id
)
)
self
.
q
(
css
=
"#comment_{} .action-edit"
.
format
(
comment_id
))
.
first
.
click
(
)
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
(
lambda
:
(
self
.
is_comment_editor_visible
(
comment_id
)
and
self
.
is_comment_editor_visible
(
comment_id
)
and
not
self
.
is_comment_visible
(
comment_id
)
and
not
self
.
is_comment_visible
(
comment_id
)
and
self
.
_get_comment_editor_value
(
comment_id
)
==
old_body
self
.
_get_comment_editor_value
(
comment_id
)
==
old_body
),
),
"Comment edit started"
"Comment edit started"
))
)
.
fulfill
(
)
def
set_comment_editor_value
(
self
,
comment_id
,
new_body
):
def
set_comment_editor_value
(
self
,
comment_id
,
new_body
):
"""Replace the contents of the comment editor"""
"""Replace the contents of the comment editor"""
self
.
css_fill
(
"#comment_{} .wmd-input"
.
format
(
comment_id
),
new_body
)
self
.
q
(
css
=
"#comment_{} .wmd-input"
.
format
(
comment_id
))
.
fill
(
new_body
)
def
submit_comment_edit
(
self
,
comment_id
):
def
submit_comment_edit
(
self
,
comment_id
,
new_comment_body
):
"""Click the submit button on the comment editor"""
"""Click the submit button on the comment editor"""
new_body
=
self
.
_get_comment_editor_value
(
comment_id
)
self
.
q
(
css
=
"#comment_{} .post-update"
.
format
(
comment_id
))
.
first
.
click
()
self
.
css_click
(
"#comment_{} .post-update"
.
format
(
comment_id
))
EmptyPromise
(
fulfill
(
EmptyPromise
(
lambda
:
(
lambda
:
(
not
self
.
is_comment_editor_visible
(
comment_id
)
and
not
self
.
is_comment_editor_visible
(
comment_id
)
and
self
.
is_comment_visible
(
comment_id
)
and
self
.
is_comment_visible
(
comment_id
)
and
self
.
get_comment_body
(
comment_id
)
==
new_body
self
.
get_comment_body
(
comment_id
)
==
new_
comment_
body
),
),
"Comment edit succeeded"
"Comment edit succeeded"
))
)
.
fulfill
(
)
def
cancel_comment_edit
(
self
,
comment_id
,
original_body
):
def
cancel_comment_edit
(
self
,
comment_id
,
original_body
):
"""Click the cancel button on the comment editor"""
"""Click the cancel button on the comment editor"""
self
.
css_click
(
"#comment_{} .post-cancel"
.
format
(
comment_id
)
)
self
.
q
(
css
=
"#comment_{} .post-cancel"
.
format
(
comment_id
))
.
first
.
click
(
)
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
(
lambda
:
(
not
self
.
is_comment_editor_visible
(
comment_id
)
and
not
self
.
is_comment_editor_visible
(
comment_id
)
and
self
.
is_comment_visible
(
comment_id
)
and
self
.
is_comment_visible
(
comment_id
)
and
self
.
get_comment_body
(
comment_id
)
==
original_body
self
.
get_comment_body
(
comment_id
)
==
original_body
),
),
"Comment edit was canceled"
"Comment edit was canceled"
))
)
.
fulfill
(
)
common/test/acceptance/pages/lms/find_courses.py
View file @
d88d8612
...
@@ -23,4 +23,5 @@ class FindCoursesPage(PageObject):
...
@@ -23,4 +23,5 @@ class FindCoursesPage(PageObject):
Retrieve the list of available course IDs
Retrieve the list of available course IDs
on the page.
on the page.
"""
"""
return
self
.
css_map
(
'article.course'
,
lambda
el
:
el
[
'id'
])
return
self
.
q
(
css
=
'article.course'
)
.
attrs
(
'id'
)
common/test/acceptance/pages/lms/login.py
View file @
d88d8612
...
@@ -3,7 +3,7 @@ Login page for the LMS.
...
@@ -3,7 +3,7 @@ Login page for the LMS.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
from
bok_choy.promise
import
EmptyPromise
from
.
import
BASE_URL
from
.
import
BASE_URL
...
@@ -17,13 +17,27 @@ class LoginPage(PageObject):
...
@@ -17,13 +17,27 @@ class LoginPage(PageObject):
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
any
([
return
any
([
'log in'
in
title
.
lower
()
'log in'
in
title
.
lower
()
for
title
in
self
.
css_text
(
'span.title-super'
)
for
title
in
self
.
q
(
css
=
'span.title-super'
)
.
text
])
])
def
login
(
self
,
email
,
password
):
def
login
(
self
,
email
,
password
):
"""
"""
Attempt to log in using `email` and `password`.
Attempt to log in using `email` and `password`.
"""
"""
EmptyPromise
(
self
.
q
(
css
=
'input#email'
)
.
is_present
,
"Click ready"
)
.
fulfill
()
EmptyPromise
(
self
.
q
(
css
=
'input#password'
)
.
is_present
,
"Click ready"
)
.
fulfill
()
self
.
q
(
css
=
'input#email'
)
.
fill
(
email
)
self
.
q
(
css
=
'input#password'
)
.
fill
(
password
)
self
.
q
(
css
=
'button#submit'
)
.
click
()
EmptyPromise
(
lambda
:
"login"
not
in
self
.
browser
.
url
,
"redirected from the login page"
)
"""
# Ensure that we make it to another page
# Ensure that we make it to another page
on_next_page = EmptyPromise(
on_next_page = EmptyPromise(
lambda: "login" not in self.browser.url,
lambda: "login" not in self.browser.url,
...
@@ -34,3 +48,5 @@ class LoginPage(PageObject):
...
@@ -34,3 +48,5 @@ class LoginPage(PageObject):
self.css_fill('input#email', email)
self.css_fill('input#email', email)
self.css_fill('input#password', password)
self.css_fill('input#password', password)
self.css_click('button#submit')
self.css_click('button#submit')
"""
common/test/acceptance/pages/lms/open_response.py
View file @
d88d8612
...
@@ -3,7 +3,7 @@ Open-ended response in the courseware.
...
@@ -3,7 +3,7 @@ Open-ended response in the courseware.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
,
fulfill
from
bok_choy.promise
import
EmptyPromise
from
.rubric
import
RubricPage
from
.rubric
import
RubricPage
...
@@ -15,7 +15,7 @@ class OpenResponsePage(PageObject):
...
@@ -15,7 +15,7 @@ class OpenResponsePage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'div.xmodule_CombinedOpenEndedModule'
)
return
self
.
q
(
css
=
'div.xmodule_CombinedOpenEndedModule'
)
.
present
@property
@property
def
assessment_type
(
self
):
def
assessment_type
(
self
):
...
@@ -23,7 +23,7 @@ class OpenResponsePage(PageObject):
...
@@ -23,7 +23,7 @@ class OpenResponsePage(PageObject):
Return the type of assessment currently active.
Return the type of assessment currently active.
Options are "self", "ai", or "peer"
Options are "self", "ai", or "peer"
"""
"""
labels
=
self
.
css_text
(
'section#combined-open-ended-status>div.statusitem-current'
)
labels
=
self
.
q
(
css
=
'section#combined-open-ended-status>div.statusitem-current'
)
.
text
if
len
(
labels
)
<
1
:
if
len
(
labels
)
<
1
:
self
.
warning
(
"Could not find assessment type label"
)
self
.
warning
(
"Could not find assessment type label"
)
...
@@ -46,7 +46,7 @@ class OpenResponsePage(PageObject):
...
@@ -46,7 +46,7 @@ class OpenResponsePage(PageObject):
Return an HTML string representing the essay prompt.
Return an HTML string representing the essay prompt.
"""
"""
prompt_css
=
"section.open-ended-child>div.prompt"
prompt_css
=
"section.open-ended-child>div.prompt"
prompts
=
self
.
css_map
(
prompt_css
,
lambda
el
:
el
.
html
.
strip
())
prompts
=
self
.
q
(
css
=
prompt_css
)
.
map
(
lambda
el
:
el
.
get_attribute
(
'innerHTML'
)
.
strip
())
.
results
if
len
(
prompts
)
==
0
:
if
len
(
prompts
)
==
0
:
self
.
warning
(
"Could not find essay prompt on page."
)
self
.
warning
(
"Could not find essay prompt on page."
)
...
@@ -73,7 +73,7 @@ class OpenResponsePage(PageObject):
...
@@ -73,7 +73,7 @@ class OpenResponsePage(PageObject):
Return the written feedback from the grader (if any).
Return the written feedback from the grader (if any).
If no feedback available, returns None.
If no feedback available, returns None.
"""
"""
feedback
=
self
.
css_text
(
'div.written-feedback'
)
feedback
=
self
.
q
(
css
=
'div.written-feedback'
)
.
text
if
len
(
feedback
)
>
0
:
if
len
(
feedback
)
>
0
:
return
feedback
[
0
]
return
feedback
[
0
]
...
@@ -85,7 +85,7 @@ class OpenResponsePage(PageObject):
...
@@ -85,7 +85,7 @@ class OpenResponsePage(PageObject):
"""
"""
Alert message displayed to the user.
Alert message displayed to the user.
"""
"""
alerts
=
self
.
css_text
(
"div.open-ended-alert"
)
alerts
=
self
.
q
(
css
=
"div.open-ended-alert"
)
.
text
if
len
(
alerts
)
<
1
:
if
len
(
alerts
)
<
1
:
return
""
return
""
...
@@ -98,7 +98,7 @@ class OpenResponsePage(PageObject):
...
@@ -98,7 +98,7 @@ class OpenResponsePage(PageObject):
Status message from the grader.
Status message from the grader.
If not present, return an empty string.
If not present, return an empty string.
"""
"""
status_list
=
self
.
css_text
(
'div.grader-status'
)
status_list
=
self
.
q
(
css
=
'div.grader-status'
)
.
text
if
len
(
status_list
)
<
1
:
if
len
(
status_list
)
<
1
:
self
.
warning
(
"No grader status found"
)
self
.
warning
(
"No grader status found"
)
...
@@ -114,27 +114,26 @@ class OpenResponsePage(PageObject):
...
@@ -114,27 +114,26 @@ class OpenResponsePage(PageObject):
Input a response to the prompt.
Input a response to the prompt.
"""
"""
input_css
=
"textarea.short-form-response"
input_css
=
"textarea.short-form-response"
self
.
css_fill
(
input_css
,
response_str
)
self
.
q
(
css
=
input_css
)
.
fill
(
response_str
)
def
save_response
(
self
):
def
save_response
(
self
):
"""
"""
Save the response for later submission.
Save the response for later submission.
"""
"""
status_msg_shown
=
EmptyPromise
(
self
.
q
(
css
=
'input.save-button'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
'save'
in
self
.
alert_message
.
lower
(),
lambda
:
'save'
in
self
.
alert_message
.
lower
(),
"Status message saved"
"Status message saved"
)
)
.
fulfill
()
with
fulfill_after
(
status_msg_shown
):
self
.
css_click
(
'input.save-button'
)
def
submit_response
(
self
):
def
submit_response
(
self
):
"""
"""
Submit a response for grading.
Submit a response for grading.
"""
"""
self
.
css_click
(
'input.submit-button'
)
self
.
q
(
css
=
'input.submit-button'
)
.
first
.
click
()
# modal dialog confirmation
# modal dialog confirmation
self
.
css_click
(
'button.ok-button'
)
self
.
q
(
css
=
'button.ok-button'
)
.
first
.
click
(
)
# Ensure that the submission completes
# Ensure that the submission completes
self
.
_wait_for_submitted
(
self
.
assessment_type
)
self
.
_wait_for_submitted
(
self
.
assessment_type
)
...
@@ -148,11 +147,11 @@ class OpenResponsePage(PageObject):
...
@@ -148,11 +147,11 @@ class OpenResponsePage(PageObject):
RubricPage
(
self
.
browser
)
.
wait_for_page
()
RubricPage
(
self
.
browser
)
.
wait_for_page
()
elif
assessment_type
==
'ai'
or
assessment_type
==
"peer"
:
elif
assessment_type
==
'ai'
or
assessment_type
==
"peer"
:
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
self
.
grader_status
!=
'Unanswered'
,
lambda
:
self
.
grader_status
!=
'Unanswered'
,
"Problem status is no longer 'unanswered'"
"Problem status is no longer 'unanswered'"
))
)
.
fulfill
(
)
else
:
else
:
self
.
warning
(
"Unrecognized assessment type '{0}'"
.
format
(
assessment_type
))
self
.
warning
(
"Unrecognized assessment type '{0}'"
.
format
(
assessment_type
))
fulfill
(
EmptyPromise
(
lambda
:
True
,
"Unrecognized assessment type"
)
)
EmptyPromise
(
lambda
:
True
,
"Unrecognized assessment type"
)
.
fulfill
(
)
common/test/acceptance/pages/lms/peer_calibrate.py
View file @
d88d8612
...
@@ -4,6 +4,7 @@ Page that allows the student to grade calibration essays
...
@@ -4,6 +4,7 @@ Page that allows the student to grade calibration essays
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
from
.rubric
import
RubricPage
from
.rubric
import
RubricPage
...
@@ -15,16 +16,21 @@ class PeerCalibratePage(PageObject):
...
@@ -15,16 +16,21 @@ class PeerCalibratePage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
(
self
.
is_css_present
(
'div.peer-grading-tools'
)
or
def
_is_correct_page
():
self
.
is_css_present
(
'div.calibration-panel.current-state'
)
is_present
=
(
)
self
.
q
(
css
=
'div.peer-grading-tools'
)
.
present
or
self
.
q
(
css
=
'div.calibration-panel.current-state'
)
.
present
)
return
is_present
,
is_present
return
Promise
(
_is_correct_page
,
'On the peer grading calibration page.'
)
.
fulfill
()
def
continue_to_grading
(
self
):
def
continue_to_grading
(
self
):
"""
"""
Continue to peer grading after completing calibration.
Continue to peer grading after completing calibration.
"""
"""
self
.
css_click
(
'input.calibration-feedback-button'
)
self
.
q
(
css
=
'input.calibration-feedback-button'
)
.
first
.
click
(
)
@property
@property
def
rubric
(
self
):
def
rubric
(
self
):
...
@@ -33,7 +39,7 @@ class PeerCalibratePage(PageObject):
...
@@ -33,7 +39,7 @@ class PeerCalibratePage(PageObject):
If no rubric is available, raises a `BrokenPromise` exception.
If no rubric is available, raises a `BrokenPromise` exception.
"""
"""
rubric
=
RubricPage
(
self
.
browser
)
rubric
=
RubricPage
(
self
.
browser
)
rubric
.
wait_for_page
()
rubric
.
wait_for_page
(
timeout
=
60
)
return
rubric
return
rubric
@property
@property
...
@@ -41,7 +47,7 @@ class PeerCalibratePage(PageObject):
...
@@ -41,7 +47,7 @@ class PeerCalibratePage(PageObject):
"""
"""
Return a message shown to the user, or None if no message is available.
Return a message shown to the user, or None if no message is available.
"""
"""
messages
=
self
.
css_text
(
'div.peer-grading-tools > div.message-container > p'
)
messages
=
self
.
q
(
css
=
'div.peer-grading-tools > div.message-container > p'
)
.
text
if
len
(
messages
)
<
1
:
if
len
(
messages
)
<
1
:
return
None
return
None
else
:
else
:
...
...
common/test/acceptance/pages/lms/peer_confirm.py
View file @
d88d8612
...
@@ -3,6 +3,7 @@ Confirmation screen for peer calibration and grading.
...
@@ -3,6 +3,7 @@ Confirmation screen for peer calibration and grading.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
class
PeerConfirmPage
(
PageObject
):
class
PeerConfirmPage
(
PageObject
):
...
@@ -13,7 +14,12 @@ class PeerConfirmPage(PageObject):
...
@@ -13,7 +14,12 @@ class PeerConfirmPage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'section.calibration-interstitial-page'
)
def
_is_correct_page
():
is_present
=
self
.
q
(
css
=
'section.calibration-interstitial-page'
)
.
present
return
is_present
,
is_present
return
Promise
(
_is_correct_page
,
'On the confirmation page for peer calibration and grading.'
)
.
fulfill
()
def
start
(
self
,
is_calibrating
=
False
):
def
start
(
self
,
is_calibrating
=
False
):
"""
"""
...
@@ -21,7 +27,6 @@ class PeerConfirmPage(PageObject):
...
@@ -21,7 +27,6 @@ class PeerConfirmPage(PageObject):
If `is_calibrating` is false, try to continue to peer grading.
If `is_calibrating` is false, try to continue to peer grading.
Otherwise, try to continue to calibration grading.
Otherwise, try to continue to calibration grading.
"""
"""
self
.
css_click
(
self
.
q
(
css
=
'input.calibration-interstitial-page-button'
'input.calibration-interstitial-page-button'
if
is_calibrating
else
'input.interstitial-page-button'
if
is_calibrating
else
'input.interstitial-page-button'
)
)
.
first
.
click
()
common/test/acceptance/pages/lms/peer_grade.py
View file @
d88d8612
...
@@ -3,6 +3,7 @@ Students grade peer submissions.
...
@@ -3,6 +3,7 @@ Students grade peer submissions.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
from
.rubric
import
RubricPage
from
.rubric
import
RubricPage
...
@@ -14,24 +15,28 @@ class PeerGradePage(PageObject):
...
@@ -14,24 +15,28 @@ class PeerGradePage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
(
def
_is_correct_page
():
self
.
is_css_present
(
'div.peer-grading-tools'
)
or
is_present
=
(
self
.
is_css_present
(
'div.grading-panel.current-state'
)
self
.
q
(
css
=
'div.peer-grading-tools'
)
.
present
or
)
self
.
q
(
css
=
'div.grading-panel.current-state'
)
.
present
)
return
is_present
,
is_present
return
Promise
(
_is_correct_page
,
'On the peer grading page.'
)
.
fulfill
()
@property
@property
def
problem_list
(
self
):
def
problem_list
(
self
):
"""
"""
Return the list of available problems to peer grade.
Return the list of available problems to peer grade.
"""
"""
return
self
.
css_text
(
'a.problem-button'
)
return
self
.
q
(
css
=
'a.problem-button'
)
.
text
def
select_problem
(
self
,
problem_name
):
def
select_problem
(
self
,
problem_name
):
"""
"""
Choose the problem with `problem_name` to start grading or calibrating.
Choose the problem with `problem_name` to start grading or calibrating.
"""
"""
index
=
self
.
problem_list
.
index
(
problem_name
)
+
1
index
=
self
.
problem_list
.
index
(
problem_name
)
+
1
self
.
css_click
(
'a.problem-button:nth-of-type({})'
.
format
(
index
)
)
self
.
q
(
css
=
'a.problem-button:nth-of-type({})'
.
format
(
index
))
.
first
.
click
(
)
@property
@property
def
rubric
(
self
):
def
rubric
(
self
):
...
...
common/test/acceptance/pages/lms/progress.py
View file @
d88d8612
...
@@ -12,9 +12,10 @@ class ProgressPage(CoursePage):
...
@@ -12,9 +12,10 @@ class ProgressPage(CoursePage):
url_path
=
"progress"
url_path
=
"progress"
#@property
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
has_course_info
=
self
.
is_css_present
(
'div.course-info'
)
has_course_info
=
self
.
q
(
css
=
'div.course-info'
)
.
present
has_graph
=
self
.
is_css_present
(
'div#grade-detail-graph'
)
has_graph
=
self
.
q
(
css
=
'div#grade-detail-graph'
)
.
present
return
has_course_info
and
has_graph
return
has_course_info
and
has_graph
def
scores
(
self
,
chapter
,
section
):
def
scores
(
self
,
chapter
,
section
):
...
@@ -46,7 +47,7 @@ class ProgressPage(CoursePage):
...
@@ -46,7 +47,7 @@ class ProgressPage(CoursePage):
Returns `None` if it cannot find such a chapter.
Returns `None` if it cannot find such a chapter.
"""
"""
chapter_css
=
'div.chapters section h2'
chapter_css
=
'div.chapters section h2'
chapter_titles
=
self
.
css_map
(
chapter_css
,
lambda
el
:
el
.
text
.
lower
()
.
strip
())
chapter_titles
=
self
.
q
(
css
=
chapter_css
)
.
map
(
lambda
el
:
el
.
text
.
lower
()
.
strip
())
.
results
try
:
try
:
# CSS indices are 1-indexed, so add one to the list index
# CSS indices are 1-indexed, so add one to the list index
...
@@ -65,7 +66,7 @@ class ProgressPage(CoursePage):
...
@@ -65,7 +66,7 @@ class ProgressPage(CoursePage):
# Get the links containing the section titles in `chapter_index`.
# Get the links containing the section titles in `chapter_index`.
# The link text is the section title.
# The link text is the section title.
section_css
=
'div.chapters>section:nth-of-type({0}) div.sections div h3 a'
.
format
(
chapter_index
)
section_css
=
'div.chapters>section:nth-of-type({0}) div.sections div h3 a'
.
format
(
chapter_index
)
section_titles
=
self
.
css_map
(
section_css
,
lambda
el
:
el
.
text
.
lower
()
.
strip
())
section_titles
=
self
.
q
(
css
=
section_css
)
.
map
(
lambda
el
:
el
.
text
.
lower
()
.
strip
())
.
results
# The section titles also contain "n of m possible points" on the second line
# The section titles also contain "n of m possible points" on the second line
# We have to remove this to find the right title
# We have to remove this to find the right title
...
@@ -95,7 +96,7 @@ class ProgressPage(CoursePage):
...
@@ -95,7 +96,7 @@ class ProgressPage(CoursePage):
chapter_index
,
section_index
chapter_index
,
section_index
)
)
text_scores
=
self
.
css_text
(
score_css
)
text_scores
=
self
.
q
(
css
=
score_css
)
.
text
# Convert text scores to tuples of (points, max_points)
# Convert text scores to tuples of (points, max_points)
return
[
tuple
(
map
(
int
,
score
.
split
(
'/'
)))
for
score
in
text_scores
]
return
[
tuple
(
map
(
int
,
score
.
split
(
'/'
)))
for
score
in
text_scores
]
common/test/acceptance/pages/lms/register.py
View file @
d88d8612
...
@@ -34,7 +34,7 @@ class RegisterPage(PageObject):
...
@@ -34,7 +34,7 @@ class RegisterPage(PageObject):
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
any
([
return
any
([
'register'
in
title
.
lower
()
'register'
in
title
.
lower
()
for
title
in
self
.
css_text
(
'span.title-sub'
)
for
title
in
self
.
q
(
css
=
'span.title-sub'
)
.
text
])
])
def
provide_info
(
self
,
email
,
password
,
username
,
full_name
):
def
provide_info
(
self
,
email
,
password
,
username
,
full_name
):
...
@@ -42,18 +42,18 @@ class RegisterPage(PageObject):
...
@@ -42,18 +42,18 @@ class RegisterPage(PageObject):
Fill in registration info.
Fill in registration info.
`email`, `password`, `username`, and `full_name` are the user's credentials.
`email`, `password`, `username`, and `full_name` are the user's credentials.
"""
"""
self
.
css_fill
(
'input#email'
,
email
)
self
.
q
(
css
=
'input#email'
)
.
fill
(
email
)
self
.
css_fill
(
'input#password'
,
password
)
self
.
q
(
css
=
'input#password'
)
.
fill
(
password
)
self
.
css_fill
(
'input#username'
,
username
)
self
.
q
(
css
=
'input#username'
)
.
fill
(
username
)
self
.
css_fill
(
'input#name'
,
full_name
)
self
.
q
(
css
=
'input#name'
)
.
fill
(
full_name
)
self
.
css_check
(
'input#tos-yes'
)
self
.
q
(
css
=
'input#tos-yes'
)
.
first
.
click
(
)
self
.
css_check
(
'input#honorcode-yes'
)
self
.
q
(
css
=
'input#honorcode-yes'
)
.
first
.
click
(
)
def
submit
(
self
):
def
submit
(
self
):
"""
"""
Submit registration info to create an account.
Submit registration info to create an account.
"""
"""
self
.
css_click
(
'button#submit'
)
self
.
q
(
css
=
'button#submit'
)
.
first
.
click
(
)
# The next page is the dashboard; make sure it loads
# The next page is the dashboard; make sure it loads
dashboard
=
DashboardPage
(
self
.
browser
)
dashboard
=
DashboardPage
(
self
.
browser
)
...
...
common/test/acceptance/pages/lms/rubric.py
View file @
d88d8612
...
@@ -3,7 +3,7 @@ Rubric for open-ended response problems, including calibration and peer-grading.
...
@@ -3,7 +3,7 @@ Rubric for open-ended response problems, including calibration and peer-grading.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
,
fulfill_before
from
bok_choy.promise
import
EmptyPromise
class
ScoreMismatchError
(
Exception
):
class
ScoreMismatchError
(
Exception
):
...
@@ -24,7 +24,7 @@ class RubricPage(PageObject):
...
@@ -24,7 +24,7 @@ class RubricPage(PageObject):
"""
"""
Return a boolean indicating whether the rubric is available.
Return a boolean indicating whether the rubric is available.
"""
"""
return
self
.
is_css_present
(
'div.rubric'
)
return
self
.
q
(
css
=
'div.rubric'
)
.
present
@property
@property
def
categories
(
self
):
def
categories
(
self
):
...
@@ -37,7 +37,7 @@ class RubricPage(PageObject):
...
@@ -37,7 +37,7 @@ class RubricPage(PageObject):
The rubric is not always visible; if it's not available,
The rubric is not always visible; if it's not available,
this will return an empty list.
this will return an empty list.
"""
"""
return
self
.
css_text
(
'span.rubric-category'
)
return
self
.
q
(
css
=
'span.rubric-category'
)
.
text
def
set_scores
(
self
,
scores
):
def
set_scores
(
self
,
scores
):
"""
"""
...
@@ -60,10 +60,9 @@ class RubricPage(PageObject):
...
@@ -60,10 +60,9 @@ class RubricPage(PageObject):
# Set the score for each category
# Set the score for each category
for
score_index
in
range
(
len
(
scores
)):
for
score_index
in
range
(
len
(
scores
)):
# Check that we have the enough radio buttons
# Check that we have the enough radio buttons
category_css
=
"div.rubric>ul.rubric-list:nth-of-type({0})"
.
format
(
score_index
+
1
)
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'
):
if
scores
[
score_index
]
>
len
(
self
.
q
(
css
=
category_css
+
' input.score-selection'
)
.
results
):
raise
ScoreMismatchError
(
raise
ScoreMismatchError
(
"Tried to select score {0} but there are only {1} options"
.
format
(
"Tried to select score {0} but there are only {1} options"
.
format
(
score_index
,
len
(
scores
)))
score_index
,
len
(
scores
)))
...
@@ -74,7 +73,12 @@ class RubricPage(PageObject):
...
@@ -74,7 +73,12 @@ class RubricPage(PageObject):
category_css
+
category_css
+
">li.rubric-list-item:nth-of-type({0}) input.score-selection"
.
format
(
scores
[
score_index
]
+
1
)
">li.rubric-list-item:nth-of-type({0}) input.score-selection"
.
format
(
scores
[
score_index
]
+
1
)
)
)
self
.
css_check
(
input_css
)
EmptyPromise
(
lambda
:
self
.
_select_score_radio_button
(
input_css
),
"Score selection failed."
)
.
fulfill
()
def
_select_score_radio_button
(
self
,
radio_button_css
):
self
.
q
(
css
=
radio_button_css
)
.
first
.
click
()
return
self
.
q
(
css
=
radio_button_css
)
.
selected
@property
@property
def
feedback
(
self
):
def
feedback
(
self
):
...
@@ -86,14 +90,13 @@ class RubricPage(PageObject):
...
@@ -86,14 +90,13 @@ class RubricPage(PageObject):
If feedback could not be interpreted (unexpected CSS class),
If feedback could not be interpreted (unexpected CSS class),
the list will contain a `None` item.
the list will contain a `None` item.
"""
"""
# Get the green checkmark / red x labels
# Get the green checkmark / red x labels
# We need to filter out the similar-looking CSS classes
# We need to filter out the similar-looking CSS classes
# for the rubric items that are NOT marked correct/incorrect
# for the rubric items that are NOT marked correct/incorrect
feedback_css
=
'div.rubric-label>label'
feedback_css
=
'div.rubric-label>label'
labels
=
[
labels
=
[
el_class
for
el_class
in
el_class
for
el_class
in
self
.
css_map
(
feedback_css
,
lambda
el
:
el
[
'class'
]
)
self
.
q
(
css
=
feedback_css
)
.
attrs
(
'class'
)
if
el_class
!=
'rubric-elements-info'
if
el_class
!=
'rubric-elements-info'
]
]
...
@@ -110,17 +113,29 @@ class RubricPage(PageObject):
...
@@ -110,17 +113,29 @@ class RubricPage(PageObject):
return
map
(
map_feedback
,
labels
)
return
map
(
map_feedback
,
labels
)
def
submit
(
self
):
def
submit
(
self
,
promise_check_type
=
None
):
"""
"""
Submit the rubric.
Submit the rubric.
`promise_check_type` is either 'self', or 'peer'. If promise check is not required then don't pass any value.
"""
"""
# Wait for the button to become enabled
# Wait for the button to become enabled
button_css
=
'input.submit-button'
button_css
=
'input.submit-button'
button_enabled
=
EmptyPromise
(
lambda
:
all
(
self
.
css_map
(
button_css
,
lambda
el
:
not
el
[
'disabled'
])),
EmptyPromise
(
"Submit button enabled"
lambda
:
all
(
self
.
q
(
css
=
button_css
)
.
map
(
lambda
el
:
not
el
.
get_attribute
(
'disabled'
))
.
results
),
)
"Submit button not enabled"
)
.
fulfill
()
# Submit the assessment
# Submit the assessment
with
fulfill_before
(
button_enabled
):
self
.
q
(
css
=
button_css
)
.
first
.
click
()
self
.
css_click
(
button_css
)
if
promise_check_type
==
'self'
:
# Check if submitted rubric is available
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'div.rubric-label>label'
)
.
present
,
'Submitted Rubric not available!'
)
.
fulfill
()
elif
promise_check_type
==
'peer'
:
# Check if we are ready for peer grading
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'input.calibration-feedback-button'
)
.
present
,
'Not ready for peer grading!'
)
.
fulfill
()
common/test/acceptance/pages/lms/tab_nav.py
View file @
d88d8612
...
@@ -3,7 +3,7 @@ High-level tab navigation.
...
@@ -3,7 +3,7 @@ High-level tab navigation.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
,
EmptyPromise
,
fulfill_after
,
fulfill
from
bok_choy.promise
import
Promise
,
EmptyPromise
class
TabNavPage
(
PageObject
):
class
TabNavPage
(
PageObject
):
...
@@ -14,12 +14,13 @@ class TabNavPage(PageObject):
...
@@ -14,12 +14,13 @@ class TabNavPage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'ol.course-tabs'
)
return
self
.
q
(
css
=
'ol.course-tabs'
)
.
present
def
go_to_tab
(
self
,
tab_name
):
def
go_to_tab
(
self
,
tab_name
):
"""
"""
Navigate to the tab `tab_name`.
Navigate to the tab `tab_name`.
"""
"""
if
tab_name
not
in
[
'Courseware'
,
'Course Info'
,
'Discussion'
,
'Wiki'
,
'Progress'
]:
if
tab_name
not
in
[
'Courseware'
,
'Course Info'
,
'Discussion'
,
'Wiki'
,
'Progress'
]:
self
.
warning
(
"'{0}' is not a valid tab name"
.
format
(
tab_name
))
self
.
warning
(
"'{0}' is not a valid tab name"
.
format
(
tab_name
))
...
@@ -27,11 +28,12 @@ class TabNavPage(PageObject):
...
@@ -27,11 +28,12 @@ class TabNavPage(PageObject):
# so we find the tab with `tab_name` in its text.
# so we find the tab with `tab_name` in its text.
tab_css
=
self
.
_tab_css
(
tab_name
)
tab_css
=
self
.
_tab_css
(
tab_name
)
with
fulfill_after
(
self
.
_is_on_tab_promise
(
tab_name
)):
if
tab_css
is
not
None
:
if
tab_css
is
not
None
:
self
.
q
(
css
=
tab_css
)
.
first
.
click
()
self
.
css_click
(
tab_css
)
else
:
else
:
self
.
warning
(
"No tabs found for '{0}'"
.
format
(
tab_name
))
self
.
warning
(
"No tabs found for '{0}'"
.
format
(
tab_name
))
self
.
_is_on_tab_promise
(
tab_name
)
.
fulfill
()
def
is_on_tab
(
self
,
tab_name
):
def
is_on_tab
(
self
,
tab_name
):
"""
"""
...
@@ -63,10 +65,10 @@ class TabNavPage(PageObject):
...
@@ -63,10 +65,10 @@ class TabNavPage(PageObject):
if the tab names fail to load.
if the tab names fail to load.
"""
"""
def
_check_func
():
def
_check_func
():
tab_names
=
self
.
css_text
(
'ol.course-tabs li a'
)
tab_names
=
self
.
q
(
css
=
'ol.course-tabs li a'
)
.
text
return
(
len
(
tab_names
)
>
0
,
tab_names
)
return
(
len
(
tab_names
)
>
0
,
tab_names
)
return
fulfill
(
Promise
(
_check_func
,
"Get all tab names"
)
)
return
Promise
(
_check_func
,
"Get all tab names"
)
.
fulfill
(
)
def
_is_on_tab
(
self
,
tab_name
):
def
_is_on_tab
(
self
,
tab_name
):
"""
"""
...
@@ -74,14 +76,13 @@ class TabNavPage(PageObject):
...
@@ -74,14 +76,13 @@ class TabNavPage(PageObject):
This is a private method, so it does NOT enforce the page check,
This is a private method, so it does NOT enforce the page check,
which is what we want when we're polling the DOM in a promise.
which is what we want when we're polling the DOM in a promise.
"""
"""
current_tab_list
=
self
.
css_text
(
'ol.course-tabs>li>a.active'
)
current_tab_list
=
self
.
q
(
css
=
'ol.course-tabs > li > a.active'
)
.
text
if
len
(
current_tab_list
)
==
0
:
if
len
(
current_tab_list
)
==
0
:
self
.
warning
(
"Could not find current tab"
)
self
.
warning
(
"Could not find current tab"
)
return
False
return
False
else
:
else
:
return
(
current_tab_list
[
0
]
.
strip
()
.
split
(
'
\n
'
)[
0
]
==
tab_name
)
return
current_tab_list
[
0
]
.
strip
()
.
split
(
'
\n
'
)[
0
]
==
tab_name
def
_is_on_tab_promise
(
self
,
tab_name
):
def
_is_on_tab_promise
(
self
,
tab_name
):
...
...
common/test/acceptance/pages/lms/video.py
View file @
d88d8612
...
@@ -4,7 +4,7 @@ Video player in the courseware.
...
@@ -4,7 +4,7 @@ Video player in the courseware.
import
time
import
time
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
from
bok_choy.promise
import
EmptyPromise
from
bok_choy.javascript
import
wait_for_js
,
js_defined
from
bok_choy.javascript
import
wait_for_js
,
js_defined
...
@@ -17,7 +17,7 @@ class VideoPage(PageObject):
...
@@ -17,7 +17,7 @@ class VideoPage(PageObject):
url
=
None
url
=
None
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'div.xmodule_VideoModule'
)
return
self
.
q
(
css
=
'div.xmodule_VideoModule'
)
.
present
@property
@property
def
elapsed_time
(
self
):
def
elapsed_time
(
self
):
...
@@ -40,37 +40,37 @@ class VideoPage(PageObject):
...
@@ -40,37 +40,37 @@ class VideoPage(PageObject):
"""
"""
Return a boolean indicating whether the video is playing.
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'
)
return
self
.
q
(
css
=
'a.video_control'
)
.
present
and
self
.
q
(
css
=
'a.video_control.pause'
)
.
present
@property
@property
def
is_paused
(
self
):
def
is_paused
(
self
):
"""
"""
Return a boolean indicating whether the video is paused.
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'
)
return
self
.
q
(
css
=
'a.video_control'
)
.
present
and
self
.
q
(
css
=
'a.video_control.play'
)
.
present
@wait_for_js
@wait_for_js
def
play
(
self
):
def
play
(
self
):
"""
"""
Start playing the video.
Start playing the video.
"""
"""
with
fulfill_after
(
EmptyPromise
(
lambda
:
self
.
is_playing
,
"Video is playing"
)):
self
.
q
(
css
=
'a.video_control.play'
)
.
first
.
click
()
self
.
css_click
(
'a.video_control.play'
)
EmptyPromise
(
lambda
:
self
.
is_playing
,
"Video is playing"
)
@wait_for_js
@wait_for_js
def
pause
(
self
):
def
pause
(
self
):
"""
"""
Pause the video.
Pause the video.
"""
"""
with
fulfill_after
(
EmptyPromise
(
lambda
:
self
.
is_paused
,
"Video is paused"
)):
self
.
q
(
css
=
'a.video_control.pause'
)
.
first
.
click
()
self
.
css_click
(
'a.video_control.pause'
)
EmptyPromise
(
lambda
:
self
.
is_paused
,
"Video is paused"
)
def
_video_time
(
self
):
def
_video_time
(
self
):
"""
"""
Return a tuple `(elapsed_time, duration)`, each in seconds.
Return a tuple `(elapsed_time, duration)`, each in seconds.
"""
"""
# The full time has the form "0:32 / 3:14"
# The full time has the form "0:32 / 3:14"
all_times
=
self
.
css_text
(
'div.vidtime'
)
all_times
=
self
.
q
(
css
=
'div.vidtime'
)
.
text
if
len
(
all_times
)
==
0
:
if
len
(
all_times
)
==
0
:
self
.
warning
(
'Could not find video time'
)
self
.
warning
(
'Could not find video time'
)
...
@@ -82,7 +82,7 @@ class VideoPage(PageObject):
...
@@ -82,7 +82,7 @@ class VideoPage(PageObject):
elapsed_str
,
duration_str
=
full_time
.
split
(
' / '
)
elapsed_str
,
duration_str
=
full_time
.
split
(
' / '
)
# Convert each string to seconds
# Convert each string to seconds
return
(
self
.
_parse_time_str
(
elapsed_str
),
self
.
_parse_time_str
(
duration_str
)
)
return
self
.
_parse_time_str
(
elapsed_str
),
self
.
_parse_time_str
(
duration_str
)
def
_parse_time_str
(
self
,
time_str
):
def
_parse_time_str
(
self
,
time_str
):
"""
"""
...
...
common/test/acceptance/pages/studio/asset_index.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class AssetIndexPage(CoursePage):
...
@@ -13,4 +13,4 @@ class AssetIndexPage(CoursePage):
url_path
=
"assets"
url_path
=
"assets"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-uploads'
)
return
self
.
q
(
css
=
'body.view-uploads'
)
.
present
common/test/acceptance/pages/studio/auto_auth.py
View file @
d88d8612
...
@@ -68,6 +68,6 @@ class AutoAuthPage(PageObject):
...
@@ -68,6 +68,6 @@ class AutoAuthPage(PageObject):
return
True
return
True
def
get_user_id
(
self
):
def
get_user_id
(
self
):
message
=
self
.
css_text
(
'BODY'
)
[
0
]
.
strip
()
message
=
self
.
q
(
css
=
'BODY'
)
.
text
[
0
]
.
strip
()
match
=
re
.
search
(
r' user_id ([^$]+)$'
,
message
)
match
=
re
.
search
(
r' user_id ([^$]+)$'
,
message
)
return
match
.
groups
()[
0
]
if
match
else
None
return
match
.
groups
()[
0
]
if
match
else
None
common/test/acceptance/pages/studio/checklists.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class ChecklistsPage(CoursePage):
...
@@ -13,4 +13,4 @@ class ChecklistsPage(CoursePage):
url_path
=
"checklists"
url_path
=
"checklists"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-checklists'
)
return
self
.
q
(
css
=
'body.view-checklists'
)
.
present
common/test/acceptance/pages/studio/container.py
View file @
d88d8612
...
@@ -24,8 +24,9 @@ class ContainerPage(PageObject):
...
@@ -24,8 +24,9 @@ class ContainerPage(PageObject):
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
# Wait until all components have been loaded
# Wait until all components have been loaded
return
(
return
(
self
.
is_css_present
(
'body.view-container'
)
and
self
.
q
(
css
=
'body.view-container'
)
.
present
and
len
(
self
.
q
(
css
=
XBlockWrapper
.
BODY_SELECTOR
))
==
len
(
self
.
q
(
css
=
'{} .xblock'
.
format
(
XBlockWrapper
.
BODY_SELECTOR
)))
len
(
self
.
q
(
css
=
XBlockWrapper
.
BODY_SELECTOR
)
.
results
)
==
len
(
self
.
q
(
css
=
'{} .xblock'
.
format
(
XBlockWrapper
.
BODY_SELECTOR
))
.
results
)
)
)
@property
@property
...
@@ -33,7 +34,8 @@ class ContainerPage(PageObject):
...
@@ -33,7 +34,8 @@ class ContainerPage(PageObject):
"""
"""
Return a list of xblocks loaded on the container page.
Return a list of xblocks loaded on the container page.
"""
"""
return
self
.
q
(
css
=
XBlockWrapper
.
BODY_SELECTOR
)
.
map
(
lambda
el
:
XBlockWrapper
(
self
.
browser
,
el
[
'data-locator'
]))
.
results
return
self
.
q
(
css
=
XBlockWrapper
.
BODY_SELECTOR
)
.
map
(
lambda
el
:
XBlockWrapper
(
self
.
browser
,
el
.
get_attribute
(
'data-locator'
)))
.
results
class
XBlockWrapper
(
PageObject
):
class
XBlockWrapper
(
PageObject
):
...
@@ -49,7 +51,7 @@ class XBlockWrapper(PageObject):
...
@@ -49,7 +51,7 @@ class XBlockWrapper(PageObject):
self
.
locator
=
locator
self
.
locator
=
locator
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'{}[data-locator="{}"]'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
))
return
self
.
q
(
css
=
'{}[data-locator="{}"]'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
))
.
present
def
_bounded_selector
(
self
,
selector
):
def
_bounded_selector
(
self
,
selector
):
"""
"""
...
@@ -63,7 +65,7 @@ class XBlockWrapper(PageObject):
...
@@ -63,7 +65,7 @@ class XBlockWrapper(PageObject):
@property
@property
def
name
(
self
):
def
name
(
self
):
titles
=
self
.
css_text
(
self
.
_bounded_selector
(
self
.
NAME_SELECTOR
))
titles
=
self
.
q
(
css
=
self
.
_bounded_selector
(
self
.
NAME_SELECTOR
))
.
text
if
titles
:
if
titles
:
return
titles
[
0
]
return
titles
[
0
]
else
:
else
:
...
...
common/test/acceptance/pages/studio/course_import.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class ImportPage(CoursePage):
...
@@ -13,4 +13,4 @@ class ImportPage(CoursePage):
url_path
=
"import"
url_path
=
"import"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-import'
)
return
self
.
q
(
css
=
'body.view-import'
)
.
present
common/test/acceptance/pages/studio/course_info.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class CourseUpdatesPage(CoursePage):
...
@@ -13,4 +13,4 @@ class CourseUpdatesPage(CoursePage):
url_path
=
"course_info"
url_path
=
"course_info"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-updates'
)
return
self
.
q
(
css
=
'body.view-updates'
)
.
present
common/test/acceptance/pages/studio/edit_subsection.py
View file @
d88d8612
...
@@ -11,4 +11,4 @@ class SubsectionPage(PageObject):
...
@@ -11,4 +11,4 @@ class SubsectionPage(PageObject):
"""
"""
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-subsection'
)
return
self
.
q
(
css
=
'body.view-subsection'
)
.
present
common/test/acceptance/pages/studio/edit_tabs.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class PagesPage(CoursePage):
...
@@ -13,4 +13,4 @@ class PagesPage(CoursePage):
url_path
=
"tabs"
url_path
=
"tabs"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-static-pages'
)
return
self
.
q
(
css
=
'body.view-static-pages'
)
.
present
common/test/acceptance/pages/studio/export.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class ExportPage(CoursePage):
...
@@ -13,4 +13,4 @@ class ExportPage(CoursePage):
url_path
=
"export"
url_path
=
"export"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-export'
)
return
self
.
q
(
css
=
'body.view-export'
)
.
present
common/test/acceptance/pages/studio/howitworks.py
View file @
d88d8612
...
@@ -14,4 +14,4 @@ class HowitworksPage(PageObject):
...
@@ -14,4 +14,4 @@ class HowitworksPage(PageObject):
url
=
BASE_URL
+
"/howitworks"
url
=
BASE_URL
+
"/howitworks"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-howitworks'
)
return
self
.
q
(
css
=
'body.view-howitworks'
)
.
present
common/test/acceptance/pages/studio/index.py
View file @
d88d8612
...
@@ -14,4 +14,4 @@ class DashboardPage(PageObject):
...
@@ -14,4 +14,4 @@ class DashboardPage(PageObject):
url
=
BASE_URL
+
"/course"
url
=
BASE_URL
+
"/course"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-dashboard'
)
return
self
.
q
(
css
=
'body.view-dashboard'
)
.
present
common/test/acceptance/pages/studio/login.py
View file @
d88d8612
...
@@ -3,7 +3,7 @@ Login page for Studio.
...
@@ -3,7 +3,7 @@ Login page for Studio.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
from
bok_choy.promise
import
EmptyPromise
from
.
import
BASE_URL
from
.
import
BASE_URL
...
@@ -15,20 +15,19 @@ class LoginPage(PageObject):
...
@@ -15,20 +15,19 @@ class LoginPage(PageObject):
url
=
BASE_URL
+
"/signin"
url
=
BASE_URL
+
"/signin"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-signin'
)
return
self
.
q
(
css
=
'body.view-signin'
)
.
present
def
login
(
self
,
email
,
password
):
def
login
(
self
,
email
,
password
):
"""
"""
Attempt to log in using `email` and `password`.
Attempt to log in using `email` and `password`.
"""
"""
self
.
q
(
css
=
'input#email'
)
.
fill
(
email
)
self
.
q
(
css
=
'input#password'
)
.
fill
(
password
)
self
.
q
(
css
=
'button#submit'
)
.
first
.
click
()
# Ensure that we make it to another page
# Ensure that we make it to another page
on_next_page
=
EmptyPromise
(
EmptyPromise
(
lambda
:
"login"
not
in
self
.
browser
.
url
,
lambda
:
"login"
not
in
self
.
browser
.
url
,
"redirected from the login page"
"redirected from the login page"
)
)
.
fulfill
()
with
fulfill_after
(
on_next_page
):
self
.
css_fill
(
'input#email'
,
email
)
self
.
css_fill
(
'input#password'
,
password
)
self
.
css_click
(
'button#submit'
)
common/test/acceptance/pages/studio/manage_users.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class CourseTeamPage(CoursePage):
...
@@ -13,4 +13,4 @@ class CourseTeamPage(CoursePage):
url_path
=
"course_team"
url_path
=
"course_team"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-team'
)
return
self
.
q
(
css
=
'body.view-team'
)
.
present
common/test/acceptance/pages/studio/overview.py
View file @
d88d8612
...
@@ -2,12 +2,12 @@
...
@@ -2,12 +2,12 @@
Course Outline page in Studio.
Course Outline page in Studio.
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.query
import
SubQuery
from
bok_choy.promise
import
EmptyPromise
from
bok_choy.promise
import
EmptyPromise
,
fulfill
from
.course_page
import
CoursePage
from
.course_page
import
CoursePage
from
.unit
import
UnitPage
from
.unit
import
UnitPage
class
CourseOutlineContainer
(
object
):
class
CourseOutlineContainer
(
object
):
"""
"""
A mixin to a CourseOutline page object that adds the ability to load
A mixin to a CourseOutline page object that adds the ability to load
...
@@ -18,13 +18,19 @@ class CourseOutlineContainer(object):
...
@@ -18,13 +18,19 @@ class CourseOutlineContainer(object):
CHILD_CLASS
=
None
CHILD_CLASS
=
None
def
child
(
self
,
title
,
child_class
=
None
):
def
child
(
self
,
title
,
child_class
=
None
):
"""
:type self: object
"""
if
not
child_class
:
if
not
child_class
:
child_class
=
self
.
CHILD_CLASS
child_class
=
self
.
CHILD_CLASS
return
child_class
(
return
child_class
(
self
.
browser
,
self
.
browser
,
self
.
q
(
css
=
child_class
.
BODY_SELECTOR
)
.
filter
(
self
.
q
(
css
=
child_class
.
BODY_SELECTOR
)
.
filter
(
SubQuery
(
css
=
child_class
.
NAME_SELECTOR
)
.
filter
(
text
=
title
)
lambda
el
:
title
in
[
inner
.
text
for
inner
in
)[
0
][
'data-locator'
]
el
.
find_elements_by_css_selector
(
child_class
.
NAME_SELECTOR
)]
)
.
attrs
(
'data-locator'
)[
0
]
)
)
...
@@ -104,22 +110,24 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
...
@@ -104,22 +110,24 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
"""
"""
Toggle the expansion of this subsection.
Toggle the expansion of this subsection.
"""
"""
self
.
disable_jquery_animations
(
)
self
.
browser
.
execute_script
(
"jQuery.fx.off = true;"
)
def
subsection_expanded
():
def
subsection_expanded
():
return
all
(
return
all
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.new-unit-item'
))
self
.
q
(
css
=
self
.
_bounded_selector
(
'.new-unit-item'
))
.
map
(
lambda
el
:
el
.
visible
)
.
map
(
lambda
el
:
el
.
is_displayed
()
)
.
results
.
results
)
)
currently_expanded
=
subsection_expanded
()
currently_expanded
=
subsection_expanded
()
self
.
css_click
(
self
.
_bounded_selector
(
'.expand-collapse'
))
self
.
q
(
css
=
self
.
_bounded_selector
(
'.expand-collapse'
))
.
first
.
click
()
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
subsection_expanded
()
!=
currently_expanded
,
lambda
:
subsection_expanded
()
!=
currently_expanded
,
"Check that the subsection {} has been toggled"
.
format
(
self
.
locator
),
"Check that the subsection {} has been toggled"
.
format
(
self
.
locator
)
))
)
.
fulfill
()
return
self
return
self
...
@@ -147,7 +155,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
...
@@ -147,7 +155,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
CHILD_CLASS
=
CourseOutlineSection
CHILD_CLASS
=
CourseOutlineSection
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-outline'
)
return
self
.
q
(
css
=
'body.view-outline'
)
.
present
def
section
(
self
,
title
):
def
section
(
self
,
title
):
"""
"""
...
...
common/test/acceptance/pages/studio/settings.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class SettingsPage(CoursePage):
...
@@ -13,4 +13,4 @@ class SettingsPage(CoursePage):
url_path
=
"settings/details"
url_path
=
"settings/details"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-settings'
)
return
self
.
q
(
css
=
'body.view-settings'
)
.
present
common/test/acceptance/pages/studio/settings_advanced.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class AdvancedSettingsPage(CoursePage):
...
@@ -13,4 +13,4 @@ class AdvancedSettingsPage(CoursePage):
url_path
=
"settings/advanced"
url_path
=
"settings/advanced"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.advanced'
)
return
self
.
q
(
css
=
'body.advanced'
)
.
present
common/test/acceptance/pages/studio/settings_graders.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class GradingPage(CoursePage):
...
@@ -13,4 +13,4 @@ class GradingPage(CoursePage):
url_path
=
"settings/grading"
url_path
=
"settings/grading"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.grading'
)
return
self
.
q
(
css
=
'body.grading'
)
.
present
common/test/acceptance/pages/studio/signup.py
View file @
d88d8612
...
@@ -10,4 +10,4 @@ class SignupPage(PageObject):
...
@@ -10,4 +10,4 @@ class SignupPage(PageObject):
url
=
BASE_URL
+
"/signup"
url
=
BASE_URL
+
"/signup"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-signup'
)
return
self
.
q
(
css
=
'body.view-signup'
)
.
present
common/test/acceptance/pages/studio/textbooks.py
View file @
d88d8612
...
@@ -13,4 +13,4 @@ class TextbooksPage(CoursePage):
...
@@ -13,4 +13,4 @@ class TextbooksPage(CoursePage):
url_path
=
"textbooks"
url_path
=
"textbooks"
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'body.view-textbooks'
)
return
self
.
q
(
css
=
'body.view-textbooks'
)
.
present
common/test/acceptance/pages/studio/unit.py
View file @
d88d8612
...
@@ -3,8 +3,7 @@ Unit page in Studio
...
@@ -3,8 +3,7 @@ Unit page in Studio
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.query
import
SubQuery
from
bok_choy.promise
import
EmptyPromise
from
bok_choy.promise
import
EmptyPromise
,
fulfill
from
.
import
BASE_URL
from
.
import
BASE_URL
from
.container
import
ContainerPage
from
.container
import
ContainerPage
...
@@ -26,11 +25,11 @@ class UnitPage(PageObject):
...
@@ -26,11 +25,11 @@ class UnitPage(PageObject):
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
# Wait until all components have been loaded
# Wait until all components have been loaded
number_of_leaf_xblocks
=
len
(
self
.
q
(
css
=
'{} .xblock-student_view'
.
format
(
Component
.
BODY_SELECTOR
)))
number_of_leaf_xblocks
=
len
(
self
.
q
(
css
=
'{} .xblock-student_view'
.
format
(
Component
.
BODY_SELECTOR
))
.
results
)
number_of_container_xblocks
=
len
(
self
.
q
(
css
=
'{} .wrapper-xblock'
.
format
(
Component
.
BODY_SELECTOR
)))
number_of_container_xblocks
=
len
(
self
.
q
(
css
=
'{} .wrapper-xblock'
.
format
(
Component
.
BODY_SELECTOR
))
.
results
)
return
(
return
(
self
.
is_css_present
(
'body.view-unit'
)
and
self
.
q
(
css
=
'body.view-unit'
)
.
present
and
len
(
self
.
q
(
css
=
Component
.
BODY_SELECTOR
))
==
number_of_leaf_xblocks
+
number_of_container_xblocks
len
(
self
.
q
(
css
=
Component
.
BODY_SELECTOR
)
.
results
)
==
number_of_leaf_xblocks
+
number_of_container_xblocks
)
)
@property
@property
...
@@ -38,21 +37,24 @@ class UnitPage(PageObject):
...
@@ -38,21 +37,24 @@ class UnitPage(PageObject):
"""
"""
Return a list of components loaded on the unit page.
Return a list of components loaded on the unit page.
"""
"""
return
self
.
q
(
css
=
Component
.
BODY_SELECTOR
)
.
map
(
lambda
el
:
Component
(
self
.
browser
,
el
[
'data-locator'
]))
.
results
return
self
.
q
(
css
=
Component
.
BODY_SELECTOR
)
.
map
(
lambda
el
:
Component
(
self
.
browser
,
el
.
get_attribute
(
'data-locator'
)))
.
results
def
edit_draft
(
self
):
def
edit_draft
(
self
):
"""
"""
Started editing a draft of this unit.
Started editing a draft of this unit.
"""
"""
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.create-draft'
)
.
present
,
lambda
:
self
.
q
(
css
=
'.create-draft'
)
.
present
,
'Wait for edit draft link to be present'
'Wait for edit draft link to be present'
))
)
.
fulfill
()
self
.
q
(
css
=
'.create-draft'
)
.
click
()
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
'.create-draft'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.editing-draft-alert'
)
.
present
,
lambda
:
self
.
q
(
css
=
'.editing-draft-alert'
)
.
present
,
'Wait for draft mode to be activated'
'Wait for draft mode to be activated'
))
)
.
fulfill
(
)
class
Component
(
PageObject
):
class
Component
(
PageObject
):
...
@@ -69,7 +71,7 @@ class Component(PageObject):
...
@@ -69,7 +71,7 @@ class Component(PageObject):
self
.
locator
=
locator
self
.
locator
=
locator
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'{}[data-locator="{}"]'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
))
return
self
.
q
(
css
=
'{}[data-locator="{}"]'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
))
.
present
def
_bounded_selector
(
self
,
selector
):
def
_bounded_selector
(
self
,
selector
):
"""
"""
...
@@ -83,7 +85,7 @@ class Component(PageObject):
...
@@ -83,7 +85,7 @@ class Component(PageObject):
@property
@property
def
name
(
self
):
def
name
(
self
):
titles
=
self
.
css_text
(
self
.
_bounded_selector
(
self
.
NAME_SELECTOR
))
titles
=
self
.
q
(
css
=
self
.
_bounded_selector
(
self
.
NAME_SELECTOR
))
.
text
if
titles
:
if
titles
:
return
titles
[
0
]
return
titles
[
0
]
else
:
else
:
...
@@ -94,15 +96,15 @@ class Component(PageObject):
...
@@ -94,15 +96,15 @@ class Component(PageObject):
return
self
.
_bounded_selector
(
'.xblock-student_view'
)
return
self
.
_bounded_selector
(
'.xblock-student_view'
)
def
edit
(
self
):
def
edit
(
self
):
self
.
css_click
(
self
.
_bounded_selector
(
'.edit-button'
)
)
self
.
q
(
css
=
self
.
_bounded_selector
(
'.edit-button'
))
.
first
.
click
(
)
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
all
(
lambda
:
all
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.component-editor'
))
self
.
q
(
css
=
self
.
_bounded_selector
(
'.component-editor'
))
.
map
(
lambda
el
:
el
.
visible
)
.
map
(
lambda
el
:
el
.
is_displayed
())
.
results
.
results
),
),
"Verify that the editor for component {} has been expanded"
.
format
(
self
.
locator
)
"Verify that the editor for component {} has been expanded"
.
format
(
self
.
locator
)
))
)
.
fulfill
()
return
self
return
self
@property
@property
...
...
common/test/acceptance/pages/xblock/acid.py
View file @
d88d8612
...
@@ -3,8 +3,7 @@ PageObjects related to the AcidBlock
...
@@ -3,8 +3,7 @@ PageObjects related to the AcidBlock
"""
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
BrokenPromise
,
fulfill
from
bok_choy.promise
import
EmptyPromise
,
BrokenPromise
class
AcidView
(
PageObject
):
class
AcidView
(
PageObject
):
"""
"""
...
@@ -15,7 +14,7 @@ class AcidView(PageObject):
...
@@ -15,7 +14,7 @@ class AcidView(PageObject):
def
__init__
(
self
,
browser
,
context_selector
):
def
__init__
(
self
,
browser
,
context_selector
):
"""
"""
Args:
Args:
browser (s
plinter.browser.Browser): The
browser that this page is loaded in.
browser (s
elenium.webdriver): The Selenium-controlled
browser that this page is loaded in.
context_selector (str): The selector that identifies where this :class:`.AcidBlock` view
context_selector (str): The selector that identifies where this :class:`.AcidBlock` view
is on the page.
is on the page.
"""
"""
...
@@ -25,14 +24,17 @@ class AcidView(PageObject):
...
@@ -25,14 +24,17 @@ class AcidView(PageObject):
self
.
context_selector
=
context_selector
self
.
context_selector
=
context_selector
def
is_browser_on_page
(
self
):
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'{}.xblock-initialized .acid-block'
.
format
(
self
.
context_selector
))
return
(
self
.
q
(
css
=
'{} .acid-block'
.
format
(
self
.
context_selector
))
.
present
and
self
.
browser
.
execute_script
(
"return $({!r}).data('initialized')"
.
format
(
self
.
context_selector
))
)
def
test_passed
(
self
,
test_selector
):
def
test_passed
(
self
,
test_selector
):
"""
"""
Return whether a particular :class:`.AcidBlock` test passed.
Return whether a particular :class:`.AcidBlock` test passed.
"""
"""
selector
=
'{} .acid-block {} .pass'
.
format
(
self
.
context_selector
,
test_selector
)
selector
=
'{} .acid-block {} .pass'
.
format
(
self
.
context_selector
,
test_selector
)
return
bool
(
self
.
q
(
css
=
selector
)
.
execute
(
try_interval
=
0.1
,
timeout
=
3
)
)
return
bool
(
self
.
q
(
css
=
selector
)
.
results
)
def
child_test_passed
(
self
,
test_selector
):
def
child_test_passed
(
self
,
test_selector
):
"""
"""
...
...
common/test/acceptance/tests/test_discussion.py
View file @
d88d8612
...
@@ -161,8 +161,9 @@ class DiscussionCommentEditTest(UniqueCourseTest):
...
@@ -161,8 +161,9 @@ class DiscussionCommentEditTest(UniqueCourseTest):
def
edit_comment
(
self
,
page
,
comment_id
):
def
edit_comment
(
self
,
page
,
comment_id
):
page
.
start_comment_edit
(
comment_id
)
page
.
start_comment_edit
(
comment_id
)
page
.
set_comment_editor_value
(
comment_id
,
"edited body"
)
new_comment
=
"edited body"
page
.
submit_comment_edit
(
comment_id
)
page
.
set_comment_editor_value
(
comment_id
,
new_comment
)
page
.
submit_comment_edit
(
comment_id
,
new_comment
)
def
test_edit_comment_as_student
(
self
):
def
test_edit_comment_as_student
(
self
):
self
.
setup_user
()
self
.
setup_user
()
...
...
common/test/acceptance/tests/test_lms.py
View file @
d88d8612
...
@@ -3,17 +3,12 @@
...
@@ -3,17 +3,12 @@
E2E tests for the LMS.
E2E tests for the LMS.
"""
"""
from
unittest
import
skip
,
expectedFailure
from
unittest
import
skip
from
bok_choy.web_app_test
import
WebAppTest
from
bok_choy.promise
import
EmptyPromise
,
fulfill_before
,
fulfill
,
Promise
from
.helpers
import
UniqueCourseTest
,
load_data_str
from
.helpers
import
UniqueCourseTest
,
load_data_str
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.lms.login
import
LoginPage
from
..pages.lms.find_courses
import
FindCoursesPage
from
..pages.lms.find_courses
import
FindCoursesPage
from
..pages.lms.course_about
import
CourseAboutPage
from
..pages.lms.course_about
import
CourseAboutPage
from
..pages.lms.register
import
RegisterPage
from
..pages.lms.course_info
import
CourseInfoPage
from
..pages.lms.course_info
import
CourseInfoPage
from
..pages.lms.tab_nav
import
TabNavPage
from
..pages.lms.tab_nav
import
TabNavPage
from
..pages.lms.course_nav
import
CourseNavPage
from
..pages.lms.course_nav
import
CourseNavPage
...
@@ -69,36 +64,12 @@ class RegistrationTest(UniqueCourseTest):
...
@@ -69,36 +64,12 @@ class RegistrationTest(UniqueCourseTest):
course_names
=
dashboard
.
available_courses
course_names
=
dashboard
.
available_courses
self
.
assertIn
(
self
.
course_info
[
'display_name'
],
course_names
)
self
.
assertIn
(
self
.
course_info
[
'display_name'
],
course_names
)
def
assert_course_available
(
self
,
course_id
):
# Occassionally this does not show up immediately,
# so we wait and try reloading the page
def
_check_course_available
():
available
=
self
.
find_courses_page
.
course_id_list
if
course_id
in
available
:
return
True
else
:
self
.
find_courses_page
.
visit
()
return
False
return
fulfill
(
EmptyPromise
(
_check_course_available
,
"Found course {course_id} in the list of available courses"
.
format
(
course_id
=
course_id
),
try_limit
=
3
,
try_interval
=
2
))
class
LanguageTest
(
UniqueCourseTest
):
class
LanguageTest
(
UniqueCourseTest
):
"""
"""
Tests that the change language functionality on the dashboard works
Tests that the change language functionality on the dashboard works
"""
"""
@property
def
_changed_lang_promise
(
self
):
def
_check_func
():
text
=
self
.
dashboard_page
.
current_courses_text
return
(
len
(
text
)
>
0
,
text
)
return
Promise
(
_check_func
,
"language changed"
)
def
setUp
(
self
):
def
setUp
(
self
):
"""
"""
Initiailize dashboard page
Initiailize dashboard page
...
@@ -116,18 +87,17 @@ class LanguageTest(UniqueCourseTest):
...
@@ -116,18 +87,17 @@ class LanguageTest(UniqueCourseTest):
self
.
password
=
"testpass"
self
.
password
=
"testpass"
self
.
email
=
"test@example.com"
self
.
email
=
"test@example.com"
@skip
(
"Flakey in its present form; re-enable when fixed"
)
def
test_change_lang
(
self
):
def
test_change_lang
(
self
):
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
self
.
dashboard_page
.
visit
()
self
.
dashboard_page
.
visit
()
# Change language to Dummy Esperanto
# Change language to Dummy Esperanto
self
.
dashboard_page
.
change_language
(
self
.
test_new_lang
)
self
.
dashboard_page
.
change_language
(
self
.
test_new_lang
)
changed_text
=
fulfill
(
self
.
_changed_lang_promise
)
changed_text
=
self
.
dashboard_page
.
current_courses_text
# We should see the dummy-language text on the page
# We should see the dummy-language text on the page
self
.
assertIn
(
self
.
current_courses_text
,
changed_text
)
self
.
assertIn
(
self
.
current_courses_text
,
changed_text
)
@skip
(
"Flakey in its present form; re-enable when fixed"
)
def
test_language_persists
(
self
):
def
test_language_persists
(
self
):
auto_auth_page
=
AutoAuthPage
(
self
.
browser
,
username
=
self
.
username
,
password
=
self
.
password
,
email
=
self
.
email
,
course_id
=
self
.
course_id
)
auto_auth_page
=
AutoAuthPage
(
self
.
browser
,
username
=
self
.
username
,
password
=
self
.
password
,
email
=
self
.
email
,
course_id
=
self
.
course_id
)
auto_auth_page
.
visit
()
auto_auth_page
.
visit
()
...
@@ -137,14 +107,15 @@ class LanguageTest(UniqueCourseTest):
...
@@ -137,14 +107,15 @@ class LanguageTest(UniqueCourseTest):
self
.
dashboard_page
.
change_language
(
self
.
test_new_lang
)
self
.
dashboard_page
.
change_language
(
self
.
test_new_lang
)
# destroy session
# destroy session
self
.
browser
.
_cookie_manager
.
delete
()
self
.
browser
.
delete_all_cookies
()
# log back in
# log back in
auto_auth_page
.
visit
()
auto_auth_page
.
visit
()
self
.
dashboard_page
.
visit
()
self
.
dashboard_page
.
visit
()
changed_text
=
fulfill
(
self
.
_changed_lang_promise
)
changed_text
=
self
.
dashboard_page
.
current_courses_text
# We should see the dummy-language text on the page
# We should see the dummy-language text on the page
self
.
assertIn
(
self
.
current_courses_text
,
changed_text
)
self
.
assertIn
(
self
.
current_courses_text
,
changed_text
)
...
@@ -173,7 +144,7 @@ class HighLevelTabTest(UniqueCourseTest):
...
@@ -173,7 +144,7 @@ class HighLevelTabTest(UniqueCourseTest):
)
)
course_fix
.
add_update
(
course_fix
.
add_update
(
CourseUpdateDesc
(
date
=
'January 29, 2014'
,
content
=
'Test course update'
)
CourseUpdateDesc
(
date
=
'January 29, 2014'
,
content
=
'Test course update
1
'
)
)
)
course_fix
.
add_handout
(
'demoPDF.pdf'
)
course_fix
.
add_handout
(
'demoPDF.pdf'
)
...
@@ -200,6 +171,7 @@ class HighLevelTabTest(UniqueCourseTest):
...
@@ -200,6 +171,7 @@ class HighLevelTabTest(UniqueCourseTest):
"""
"""
Navigate to the course info page.
Navigate to the course info page.
"""
"""
# Navigate to the course info page from the progress page
# Navigate to the course info page from the progress page
self
.
progress_page
.
visit
()
self
.
progress_page
.
visit
()
self
.
tab_nav
.
go_to_tab
(
'Course Info'
)
self
.
tab_nav
.
go_to_tab
(
'Course Info'
)
...
@@ -251,6 +223,7 @@ class HighLevelTabTest(UniqueCourseTest):
...
@@ -251,6 +223,7 @@ class HighLevelTabTest(UniqueCourseTest):
'Test Section'
:
[
'Test Subsection'
],
'Test Section'
:
[
'Test Subsection'
],
'Test Section 2'
:
[
'Test Subsection 2'
,
'Test Subsection 3'
]
'Test Section 2'
:
[
'Test Subsection 2'
,
'Test Subsection 3'
]
}
}
actual_sections
=
self
.
course_nav
.
sections
actual_sections
=
self
.
course_nav
.
sections
for
section
,
subsections
in
EXPECTED_SECTIONS
.
iteritems
():
for
section
,
subsections
in
EXPECTED_SECTIONS
.
iteritems
():
self
.
assertIn
(
section
,
actual_sections
)
self
.
assertIn
(
section
,
actual_sections
)
...
@@ -321,22 +294,22 @@ class VideoTest(UniqueCourseTest):
...
@@ -321,22 +294,22 @@ class VideoTest(UniqueCourseTest):
# Now we should be playing
# Now we should be playing
self
.
assertTrue
(
self
.
video
.
is_playing
)
self
.
assertTrue
(
self
.
video
.
is_playing
)
# Commented the below EmptyPromise, will move to its page once this test is working and stable
# Also there is should be no Promise check in any test as this should be done in Page Object
# Wait for the video to load the duration
# Wait for the video to load the duration
video_duration_loaded
=
EmptyPromise
(
# EmptyPromise(
lambda
:
self
.
video
.
duration
>
0
,
# lambda: self.video.duration > 0,
'video has duration'
,
timeout
=
20
# 'video has duration', timeout=20
)
# ).fulfill()
with
fulfill_before
(
video_duration_loaded
):
# Pause the video
# Pause the video
self
.
video
.
pause
()
self
.
video
.
pause
()
# Expect that the elapsed time and duration are reasonable
# Expect that the elapsed time and duration are reasonable
# Again, we can't expect the video to actually play because of
# Again, we can't expect the video to actually play because of
# latency through the ssh tunnel
# latency through the ssh tunnel
self
.
assertGreaterEqual
(
self
.
video
.
elapsed_time
,
0
)
self
.
assertGreaterEqual
(
self
.
video
.
elapsed_time
,
0
)
self
.
assertGreaterEqual
(
self
.
video
.
duration
,
self
.
video
.
elapsed_time
)
self
.
assertGreaterEqual
(
self
.
video
.
duration
,
self
.
video
.
elapsed_time
)
class
XBlockAcidBase
(
UniqueCourseTest
):
class
XBlockAcidBase
(
UniqueCourseTest
):
...
@@ -441,7 +414,6 @@ class XBlockAcidChildTest(XBlockAcidBase):
...
@@ -441,7 +414,6 @@ class XBlockAcidChildTest(XBlockAcidBase):
super
(
XBlockAcidChildTest
,
self
)
.
validate_acid_block_view
()
super
(
XBlockAcidChildTest
,
self
)
.
validate_acid_block_view
()
self
.
assertTrue
(
acid_block
.
child_tests_passed
)
self
.
assertTrue
(
acid_block
.
child_tests_passed
)
# This will fail until we fix support of children in pure XBlocks
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
@expectedFailure
def
test_acid_block
(
self
):
def
test_acid_block
(
self
):
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block
()
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block
()
common/test/acceptance/tests/test_ora.py
View file @
d88d8612
...
@@ -3,7 +3,10 @@ Tests for ORA (Open Response Assessment) through the LMS UI.
...
@@ -3,7 +3,10 @@ Tests for ORA (Open Response Assessment) through the LMS UI.
"""
"""
import
json
import
json
from
bok_choy.promise
import
fulfill
,
Promise
,
BrokenPromise
from
unittest
import
skip
from
bok_choy.promise
import
Promise
,
BrokenPromise
from
..pages.lms.peer_confirm
import
PeerConfirmPage
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.lms.course_info
import
CourseInfoPage
from
..pages.lms.course_info
import
CourseInfoPage
from
..pages.lms.tab_nav
import
TabNavPage
from
..pages.lms.tab_nav
import
TabNavPage
...
@@ -11,7 +14,7 @@ from ..pages.lms.course_nav import CourseNavPage
...
@@ -11,7 +14,7 @@ from ..pages.lms.course_nav import CourseNavPage
from
..pages.lms.open_response
import
OpenResponsePage
from
..pages.lms.open_response
import
OpenResponsePage
from
..pages.lms.peer_grade
import
PeerGradePage
from
..pages.lms.peer_grade
import
PeerGradePage
from
..pages.lms.peer_calibrate
import
PeerCalibratePage
from
..pages.lms.peer_calibrate
import
PeerCalibratePage
from
..pages.lms.peer_confirm
import
PeerConfirmPage
from
..pages.lms.progress
import
ProgressPage
from
..pages.lms.progress
import
ProgressPage
from
..fixtures.course
import
XBlockFixtureDesc
,
CourseFixture
from
..fixtures.course
import
XBlockFixtureDesc
,
CourseFixture
from
..fixtures.xqueue
import
XQueueResponseFixture
from
..fixtures.xqueue
import
XQueueResponseFixture
...
@@ -123,12 +126,11 @@ class OpenResponseTest(UniqueCourseTest):
...
@@ -123,12 +126,11 @@ class OpenResponseTest(UniqueCourseTest):
# Because the check function involves fairly complicated actions
# Because the check function involves fairly complicated actions
# (navigating through several screens), we give it more time to complete
# (navigating through several screens), we give it more time to complete
# than the default.
# than the default.
feedback_promise
=
Promise
(
return
Promise
(
self
.
_check_feedback_func
(
assessment_type
),
self
.
_check_feedback_func
(
assessment_type
),
'Got feedback for {0} problem'
.
format
(
assessment_type
),
'Got feedback for {0} problem'
.
format
(
assessment_type
),
timeout
=
600
,
try_interval
=
5
timeout
=
600
,
try_interval
=
5
)
)
.
fulfill
()
return
fulfill
(
feedback_promise
)
def
_check_feedback_func
(
self
,
assessment_type
):
def
_check_feedback_func
(
self
,
assessment_type
):
"""
"""
...
@@ -155,11 +157,11 @@ class OpenResponseTest(UniqueCourseTest):
...
@@ -155,11 +157,11 @@ class OpenResponseTest(UniqueCourseTest):
# Unsuccessful if the rubric hasn't loaded
# Unsuccessful if the rubric hasn't loaded
except
BrokenPromise
:
except
BrokenPromise
:
return
(
False
,
None
)
return
False
,
None
# Successful if `feedback` is a non-empty list
# Successful if `feedback` is a non-empty list
else
:
else
:
return
(
bool
(
feedback
),
feedback
)
return
bool
(
feedback
),
feedback
return
_inner_check
return
_inner_check
...
@@ -183,9 +185,11 @@ class SelfAssessmentTest(OpenResponseTest):
...
@@ -183,9 +185,11 @@ class SelfAssessmentTest(OpenResponseTest):
# Fill in the rubric and expect that we get feedback
# Fill in the rubric and expect that we get feedback
rubric
=
self
.
open_response
.
rubric
rubric
=
self
.
open_response
.
rubric
self
.
assertEqual
(
rubric
.
categories
,
[
"Writing Applications"
,
"Language Conventions"
])
self
.
assertEqual
(
rubric
.
categories
,
[
"Writing Applications"
,
"Language Conventions"
])
rubric
.
set_scores
([
0
,
1
])
rubric
.
set_scores
([
0
,
1
])
rubric
.
submit
()
rubric
.
submit
(
'self'
)
self
.
assertEqual
(
rubric
.
feedback
,
[
'incorrect'
,
'correct'
])
self
.
assertEqual
(
rubric
.
feedback
,
[
'incorrect'
,
'correct'
])
# Verify the progress page
# Verify the progress page
...
@@ -213,6 +217,7 @@ class AIAssessmentTest(OpenResponseTest):
...
@@ -213,6 +217,7 @@ class AIAssessmentTest(OpenResponseTest):
'rubric_xml'
:
load_data_str
(
'ora_rubric.xml'
)
'rubric_xml'
:
load_data_str
(
'ora_rubric.xml'
)
}
}
@skip
(
'Intermittently failing, see ORA-342'
)
def
test_ai_assessment
(
self
):
def
test_ai_assessment
(
self
):
"""
"""
Given I am viewing an AI-assessment problem that has a trained ML model
Given I am viewing an AI-assessment problem that has a trained ML model
...
@@ -258,6 +263,7 @@ class InstructorAssessmentTest(OpenResponseTest):
...
@@ -258,6 +263,7 @@ class InstructorAssessmentTest(OpenResponseTest):
'rubric_xml'
:
load_data_str
(
'ora_rubric.xml'
)
'rubric_xml'
:
load_data_str
(
'ora_rubric.xml'
)
}
}
@skip
(
'Intermittently failing, see ORA-342'
)
def
test_instructor_assessment
(
self
):
def
test_instructor_assessment
(
self
):
"""
"""
Given an instructor has graded my submission
Given an instructor has graded my submission
...
@@ -306,7 +312,7 @@ class PeerAssessmentTest(OpenResponseTest):
...
@@ -306,7 +312,7 @@ class PeerAssessmentTest(OpenResponseTest):
"""
"""
Given I am viewing a peer-assessment problem
Given I am viewing a peer-assessment problem
And the instructor has submitted enough example essays
And the instructor has submitted enough example essays
When I submit
submit
acceptable scores for enough calibration essays
When I submit acceptable scores for enough calibration essays
Then I am able to peer-grade other students' essays.
Then I am able to peer-grade other students' essays.
Given I have submitted an essay for peer-assessment
Given I have submitted an essay for peer-assessment
...
@@ -339,7 +345,7 @@ class PeerAssessmentTest(OpenResponseTest):
...
@@ -339,7 +345,7 @@ class PeerAssessmentTest(OpenResponseTest):
rubric
=
self
.
peer_calibrate
.
rubric
rubric
=
self
.
peer_calibrate
.
rubric
self
.
assertEqual
(
rubric
.
categories
,
[
"Writing Applications"
,
"Language Conventions"
])
self
.
assertEqual
(
rubric
.
categories
,
[
"Writing Applications"
,
"Language Conventions"
])
rubric
.
set_scores
([
0
,
1
])
rubric
.
set_scores
([
0
,
1
])
rubric
.
submit
()
rubric
.
submit
(
'peer'
)
self
.
peer_calibrate
.
continue_to_grading
()
self
.
peer_calibrate
.
continue_to_grading
()
# Grade a peer
# Grade a peer
...
...
common/test/acceptance/tests/test_studio.py
View file @
d88d8612
"""
"""
Acceptance tests for Studio.
Acceptance tests for Studio.
"""
"""
from
unittest
import
expectedFailure
from
unittest
import
skip
from
bok_choy.web_app_test
import
WebAppTest
from
bok_choy.web_app_test
import
WebAppTest
...
@@ -25,7 +25,7 @@ from ..pages.studio.textbooks import TextbooksPage
...
@@ -25,7 +25,7 @@ from ..pages.studio.textbooks import TextbooksPage
from
..pages.xblock.acid
import
AcidView
from
..pages.xblock.acid
import
AcidView
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
.helpers
import
UniqueCourseTest
from
.helpers
import
UniqueCourseTest
,
load_data_str
class
LoggedOutTest
(
WebAppTest
):
class
LoggedOutTest
(
WebAppTest
):
...
@@ -237,8 +237,7 @@ class XBlockAcidParentBase(XBlockAcidBase):
...
@@ -237,8 +237,7 @@ class XBlockAcidParentBase(XBlockAcidBase):
acid_block
=
AcidView
(
self
.
browser
,
container
.
xblocks
[
0
]
.
preview_selector
)
acid_block
=
AcidView
(
self
.
browser
,
container
.
xblocks
[
0
]
.
preview_selector
)
self
.
validate_acid_block_preview
(
acid_block
)
self
.
validate_acid_block_preview
(
acid_block
)
# This will fail until the container page supports editing
@skip
(
'This will fail until the container page supports editing'
)
@expectedFailure
def
test_acid_block_editor
(
self
):
def
test_acid_block_editor
(
self
):
super
(
XBlockAcidParentBase
,
self
)
.
test_acid_block_editor
()
super
(
XBlockAcidParentBase
,
self
)
.
test_acid_block_editor
()
...
@@ -299,12 +298,10 @@ class XBlockAcidChildTest(XBlockAcidParentBase):
...
@@ -299,12 +298,10 @@ class XBlockAcidChildTest(XBlockAcidParentBase):
)
)
)
.
install
()
)
.
install
()
# This will fail until we fix support of children in pure XBlocks
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
@expectedFailure
def
test_acid_block_preview
(
self
):
def
test_acid_block_preview
(
self
):
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block_preview
()
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block_preview
()
# This will fail until we fix support of children in pure XBlocks
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
@expectedFailure
def
test_acid_block_editor
(
self
):
def
test_acid_block_editor
(
self
):
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block_editor
()
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block_editor
()
requirements/edx/github.txt
View file @
d88d8612
...
@@ -23,6 +23,6 @@
...
@@ -23,6 +23,6 @@
-e git+https://github.com/edx/js-test-tool.git@v0.1.5#egg=js_test_tool
-e git+https://github.com/edx/js-test-tool.git@v0.1.5#egg=js_test_tool
-e git+https://github.com/edx/django-waffle.git@823a102e48#egg=django-waffle
-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/event-tracking.git@f0211d702d#egg=event-tracking
-e git+https://github.com/edx/bok-choy.git@
62de7b576a08f36cde5b030c52bccb1a2f3f8df1
#egg=bok_choy
-e git+https://github.com/edx/bok-choy.git@
25a47b3bf87c503fc4996e52addac83b42ec6f38
#egg=bok_choy
-e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash
-e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash
-e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock
-e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment