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
Show whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
340 additions
and
286 deletions
+340
-286
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
+12
-6
common/test/acceptance/pages/lms/peer_confirm.py
+9
-4
common/test/acceptance/pages/lms/peer_grade.py
+10
-5
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
+10
-9
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
+19
-11
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
+16
-44
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>
Nimisha Asthagiri <nasthagiri@edx.org>
Martyn James <mjames@edx.org>
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):
url_path
=
"about"
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
):
"""
...
...
@@ -22,7 +23,7 @@ class CourseAboutPage(CoursePage):
Waits for the registration page to load, then
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
.
wait_for_page
()
...
...
common/test/acceptance/pages/lms/course_info.py
View file @
d88d8612
...
...
@@ -13,18 +13,18 @@ class CourseInfoPage(CoursePage):
url_path
=
"info"
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'section.updates'
)
return
self
.
q
(
css
=
'section.updates'
)
.
present
@property
def
num_updates
(
self
):
"""
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
def
handout_links
(
self
):
"""
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
import
re
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
,
fulfill_after
from
bok_choy.promise
import
EmptyPromise
class
CourseNavPage
(
PageObject
):
...
...
@@ -15,7 +15,7 @@ class CourseNavPage(PageObject):
url
=
None
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'div.course-index'
)
return
self
.
q
(
css
=
'div.course-index'
)
.
present
@property
def
sections
(
self
):
...
...
@@ -38,9 +38,7 @@ class CourseNavPage(PageObject):
section_titles
=
self
.
_section_titles
()
# Get the section titles for each chapter
for
sec_index
in
range
(
len
(
section_titles
)):
sec_title
=
section_titles
[
sec_index
]
for
sec_index
,
sec_title
in
enumerate
(
section_titles
):
if
len
(
section_titles
)
<
1
:
self
.
warning
(
"Could not find subsections for '{0}'"
.
format
(
sec_title
))
...
...
@@ -60,7 +58,7 @@ class CourseNavPage(PageObject):
['Chemical Bonds Video', 'Practice Problems', 'Homework']
"""
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
):
"""
...
...
@@ -73,7 +71,7 @@ class CourseNavPage(PageObject):
"""
# 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
try
:
...
...
@@ -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)
# Add one to convert from list index to CSS index
section_css
=
'nav>div.chapter:nth-of-type({0})>h3>a'
.
format
(
sec_index
+
1
)
self
.
css_click
(
section_css
)
self
.
q
(
css
=
section_css
)
.
first
.
click
(
)
# Get the subsection by index
try
:
...
...
@@ -101,8 +99,9 @@ class CourseNavPage(PageObject):
)
# Click the subsection and ensure that the page finishes reloading
with
fulfill_after
(
self
.
_on_section_promise
(
section_title
,
subsection_title
)):
self
.
css_click
(
subsection_css
)
self
.
q
(
css
=
subsection_css
)
.
first
.
click
()
self
.
_on_section_promise
(
section_title
,
subsection_title
)
.
fulfill
()
def
go_to_sequential
(
self
,
sequential_title
):
"""
...
...
@@ -126,14 +125,14 @@ class CourseNavPage(PageObject):
# Click on the sequence item at the correct index
# Convert the list index (starts at 0) to a CSS index (starts at 1)
seq_css
=
"ol#sequence-list>li:nth-of-type({0})>a"
.
format
(
seq_index
+
1
)
self
.
css_click
(
seq_css
)
self
.
q
(
css
=
seq_css
)
.
first
.
click
(
)
def
_section_titles
(
self
):
"""
Return a list of all section titles on the page.
"""
chapter_css
=
'nav
>div.chapter>h3>
a'
return
self
.
css_map
(
chapter_css
,
lambda
el
:
el
.
text
.
strip
())
chapter_css
=
'nav
> div.chapter > h3 >
a'
return
self
.
q
(
css
=
chapter_css
)
.
map
(
lambda
el
:
el
.
text
.
strip
())
.
results
def
_subsection_titles
(
self
,
section_index
):
"""
...
...
@@ -148,10 +147,10 @@ class CourseNavPage(PageObject):
# Otherwise, we need to get the HTML
# It *would* make sense to always get the HTML, but unfortunately
# the open tab has some child <span> tags that we don't want.
return
self
.
css_map
(
subsection_css
,
lambda
el
:
el
.
text
.
strip
()
.
split
(
'
\n
'
)[
0
]
if
el
.
visible
else
el
.
html
.
strip
()
)
return
self
.
q
(
css
=
subsection_css
)
.
map
(
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
):
"""
...
...
@@ -172,8 +171,8 @@ class CourseNavPage(PageObject):
That's true right after we click the section/subsection, but not true in general
(the user could go to a section, then expand another tab).
"""
current_section_list
=
self
.
css_text
(
'nav>div.chapter.is-open>h3>a'
)
current_subsection_list
=
self
.
css_text
(
'nav>div.chapter.is-open li.active>a>p'
)
current_section_list
=
self
.
q
(
css
=
'nav>div.chapter.is-open>h3>a'
)
.
text
current_subsection_list
=
self
.
q
(
css
=
'nav>div.chapter.is-open li.active>a>p'
)
.
text
if
len
(
current_section_list
)
==
0
:
self
.
warning
(
"Could not find the current section"
)
...
...
@@ -196,4 +195,4 @@ class CourseNavPage(PageObject):
"""
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.
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
EmptyPromise
from
.
import
BASE_URL
...
...
@@ -16,11 +17,11 @@ class DashboardPage(PageObject):
url
=
BASE_URL
+
"/dashboard"
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'section.my-courses'
)
return
self
.
q
(
css
=
'section.my-courses'
)
.
present
@property
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
:
return
text_items
[
0
]
else
:
...
...
@@ -36,7 +37,7 @@ class DashboardPage(PageObject):
_
,
course_name
=
el
.
text
.
split
(
' '
,
1
)
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
):
"""
...
...
@@ -45,7 +46,7 @@ class DashboardPage(PageObject):
link_css
=
self
.
_link_css
(
course_id
)
if
link_css
is
not
None
:
self
.
css_click
(
link_css
)
self
.
q
(
css
=
link_css
)
.
first
.
click
(
)
else
:
msg
=
"No links found for course {0}"
.
format
(
course_id
)
self
.
warning
(
msg
)
...
...
@@ -55,7 +56,7 @@ class DashboardPage(PageObject):
Return a CSS selector for the link to the course with `course_id`.
"""
# 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
link_index
=
None
...
...
@@ -73,6 +74,13 @@ class DashboardPage(PageObject):
"""
Change the language on the dashboard to the language corresponding with `code`.
"""
self
.
css_click
(
".edit-language"
)
self
.
select_option
(
"language"
,
code
)
self
.
css_click
(
"#submit-lang"
)
self
.
q
(
css
=
".edit-language"
)
.
first
.
click
()
self
.
q
(
css
=
'select[name="language"] option[value="{}"]'
.
format
(
code
))
.
first
.
click
()
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.promise
import
EmptyPromise
,
fulfill
from
bok_choy.promise
import
EmptyPromise
from
.course_page
import
CoursePage
...
...
@@ -10,9 +10,9 @@ class DiscussionSingleThreadPage(CoursePage):
self
.
thread_id
=
thread_id
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
"body.discussion .discussion-article[data-id='{thread_id}']"
.
format
(
thread_id
=
self
.
thread_id
)
)
return
self
.
q
(
css
=
"body.discussion .discussion-article[data-id='{thread_id}']"
.
format
(
thread_id
=
self
.
thread_id
)
)
.
present
@property
@unguarded
...
...
@@ -24,7 +24,7 @@ class DiscussionSingleThreadPage(CoursePage):
Returns the text of the first element matching the given selector, or
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
def
get_response_total_text
(
self
):
...
...
@@ -33,7 +33,7 @@ class DiscussionSingleThreadPage(CoursePage):
def
get_num_displayed_responses
(
self
):
"""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
):
"""Returns the shown response count text, or None if not present"""
...
...
@@ -44,12 +44,16 @@ class DiscussionSingleThreadPage(CoursePage):
return
self
.
_get_element_text
(
".load-response-button"
)
def
load_more_responses
(
self
):
"""Clicks the laod more responses button and waits for responses to load"""
self
.
css_click
(
".load-response-button"
)
fulfill
(
EmptyPromise
(
lambda
:
not
self
.
is_css_present
(
".loading"
),
"Loading more responses completed"
))
"""Clicks the load more responses button and waits for responses to load"""
self
.
q
(
css
=
".load-response-button"
)
.
first
.
click
()
def
_is_ajax_finished
():
return
self
.
browser
.
execute_script
(
"return jQuery.active"
)
==
0
EmptyPromise
(
_is_ajax_finished
,
"Loading more Responses"
)
.
fulfill
()
def
has_add_response_button
(
self
):
"""Returns true if the add response button is visible, false otherwise"""
...
...
@@ -60,14 +64,17 @@ class DiscussionSingleThreadPage(CoursePage):
Clicks the add response button and ensures that the response text
field receives focus
"""
self
.
css_click
(
".add-response-btn"
)
fulfill
(
EmptyPromise
(
lambda
:
self
.
is_css_present
(
"#wmd-input-reply-body-{thread_id}:focus"
.
format
(
thread_id
=
self
.
thread_id
)),
self
.
q
(
css
=
".add-response-btn"
)
.
first
.
click
(
)
EmptyPromise
(
lambda
:
self
.
q
(
css
=
"#wmd-input-reply-body-{thread_id}:focus"
.
format
(
thread_id
=
self
.
thread_id
)),
"Response field received focus"
))
)
.
fulfill
(
)
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
):
"""Returns true if the response editor is present, false otherwise"""
...
...
@@ -75,15 +82,15 @@ class DiscussionSingleThreadPage(CoursePage):
def
start_response_edit
(
self
,
response_id
):
"""Click the edit button for the response, loading the editing view"""
self
.
css_click
(
".response_{} .discussion-response .action-edit"
.
format
(
response_id
)
)
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
".response_{} .discussion-response .action-edit"
.
format
(
response_id
))
.
first
.
click
(
)
EmptyPromise
(
lambda
:
self
.
is_response_editor_visible
(
response_id
),
"Response edit started"
))
)
.
fulfill
(
)
def
is_add_comment_visible
(
self
,
response_id
):
"""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
):
"""Returns true if the comment is viewable onscreen"""
...
...
@@ -98,11 +105,11 @@ class DiscussionSingleThreadPage(CoursePage):
def
delete_comment
(
self
,
comment_id
):
with
self
.
handle_alert
():
self
.
css_click
(
"#comment_{} div.action-delete"
.
format
(
comment_id
)
)
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
"#comment_{} div.action-delete"
.
format
(
comment_id
))
.
first
.
click
(
)
EmptyPromise
(
lambda
:
not
self
.
is_comment_visible
(
comment_id
),
"Deleted comment was removed"
))
)
.
fulfill
(
)
def
is_comment_editable
(
self
,
comment_id
):
"""Returns true if the edit comment button is present, false otherwise"""
...
...
@@ -110,49 +117,48 @@ class DiscussionSingleThreadPage(CoursePage):
def
is_comment_editor_visible
(
self
,
comment_id
):
"""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
):
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
):
"""Click the edit button for the comment, loading the editing view"""
old_body
=
self
.
get_comment_body
(
comment_id
)
self
.
css_click
(
"#comment_{} .action-edit"
.
format
(
comment_id
)
)
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
"#comment_{} .action-edit"
.
format
(
comment_id
))
.
first
.
click
(
)
EmptyPromise
(
lambda
:
(
self
.
is_comment_editor_visible
(
comment_id
)
and
not
self
.
is_comment_visible
(
comment_id
)
and
self
.
_get_comment_editor_value
(
comment_id
)
==
old_body
),
"Comment edit started"
))
)
.
fulfill
(
)
def
set_comment_editor_value
(
self
,
comment_id
,
new_body
):
"""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"""
new_body
=
self
.
_get_comment_editor_value
(
comment_id
)
self
.
css_click
(
"#comment_{} .post-update"
.
format
(
comment_id
))
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
"#comment_{} .post-update"
.
format
(
comment_id
))
.
first
.
click
()
EmptyPromise
(
lambda
:
(
not
self
.
is_comment_editor_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"
))
)
.
fulfill
(
)
def
cancel_comment_edit
(
self
,
comment_id
,
original_body
):
"""Click the cancel button on the comment editor"""
self
.
css_click
(
"#comment_{} .post-cancel"
.
format
(
comment_id
)
)
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
"#comment_{} .post-cancel"
.
format
(
comment_id
))
.
first
.
click
(
)
EmptyPromise
(
lambda
:
(
not
self
.
is_comment_editor_visible
(
comment_id
)
and
self
.
is_comment_visible
(
comment_id
)
and
self
.
get_comment_body
(
comment_id
)
==
original_body
),
"Comment edit was canceled"
))
)
.
fulfill
(
)
common/test/acceptance/pages/lms/find_courses.py
View file @
d88d8612
...
...
@@ -23,4 +23,5 @@ class FindCoursesPage(PageObject):
Retrieve the list of available course IDs
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.
"""
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
...
...
@@ -17,13 +17,27 @@ class LoginPage(PageObject):
def
is_browser_on_page
(
self
):
return
any
([
'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
):
"""
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
on_next_page = EmptyPromise(
lambda: "login" not in self.browser.url,
...
...
@@ -34,3 +48,5 @@ class LoginPage(PageObject):
self.css_fill('input#email', email)
self.css_fill('input#password', password)
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.
"""
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
...
...
@@ -15,7 +15,7 @@ class OpenResponsePage(PageObject):
url
=
None
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'div.xmodule_CombinedOpenEndedModule'
)
return
self
.
q
(
css
=
'div.xmodule_CombinedOpenEndedModule'
)
.
present
@property
def
assessment_type
(
self
):
...
...
@@ -23,7 +23,7 @@ class OpenResponsePage(PageObject):
Return the type of assessment currently active.
Options are "self", "ai", or "peer"
"""
labels
=
self
.
css_text
(
'section#combined-open-ended-status>div.statusitem-current'
)
labels
=
self
.
q
(
css
=
'section#combined-open-ended-status>div.statusitem-current'
)
.
text
if
len
(
labels
)
<
1
:
self
.
warning
(
"Could not find assessment type label"
)
...
...
@@ -46,7 +46,7 @@ class OpenResponsePage(PageObject):
Return an HTML string representing the essay prompt.
"""
prompt_css
=
"section.open-ended-child>div.prompt"
prompts
=
self
.
css_map
(
prompt_css
,
lambda
el
:
el
.
html
.
strip
())
prompts
=
self
.
q
(
css
=
prompt_css
)
.
map
(
lambda
el
:
el
.
get_attribute
(
'innerHTML'
)
.
strip
())
.
results
if
len
(
prompts
)
==
0
:
self
.
warning
(
"Could not find essay prompt on page."
)
...
...
@@ -73,7 +73,7 @@ class OpenResponsePage(PageObject):
Return the written feedback from the grader (if any).
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
:
return
feedback
[
0
]
...
...
@@ -85,7 +85,7 @@ class OpenResponsePage(PageObject):
"""
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
:
return
""
...
...
@@ -98,7 +98,7 @@ class OpenResponsePage(PageObject):
Status message from the grader.
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
:
self
.
warning
(
"No grader status found"
)
...
...
@@ -114,27 +114,26 @@ class OpenResponsePage(PageObject):
Input a response to the prompt.
"""
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
):
"""
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
(),
"Status message saved"
)
with
fulfill_after
(
status_msg_shown
):
self
.
css_click
(
'input.save-button'
)
)
.
fulfill
()
def
submit_response
(
self
):
"""
Submit a response for grading.
"""
self
.
css_click
(
'input.submit-button'
)
self
.
q
(
css
=
'input.submit-button'
)
.
first
.
click
()
# modal dialog confirmation
self
.
css_click
(
'button.ok-button'
)
self
.
q
(
css
=
'button.ok-button'
)
.
first
.
click
(
)
# Ensure that the submission completes
self
.
_wait_for_submitted
(
self
.
assessment_type
)
...
...
@@ -148,11 +147,11 @@ class OpenResponsePage(PageObject):
RubricPage
(
self
.
browser
)
.
wait_for_page
()
elif
assessment_type
==
'ai'
or
assessment_type
==
"peer"
:
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
self
.
grader_status
!=
'Unanswered'
,
"Problem status is no longer 'unanswered'"
))
)
.
fulfill
(
)
else
:
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
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
from
.rubric
import
RubricPage
...
...
@@ -15,16 +16,21 @@ class PeerCalibratePage(PageObject):
url
=
None
def
is_browser_on_page
(
self
):
return
(
self
.
is_css_present
(
'div.peer-grading-tools'
)
or
self
.
is_css_present
(
'div.calibration-panel.current-state'
)
def
_is_correct_page
():
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
):
"""
Continue to peer grading after completing calibration.
"""
self
.
css_click
(
'input.calibration-feedback-button'
)
self
.
q
(
css
=
'input.calibration-feedback-button'
)
.
first
.
click
(
)
@property
def
rubric
(
self
):
...
...
@@ -33,7 +39,7 @@ class PeerCalibratePage(PageObject):
If no rubric is available, raises a `BrokenPromise` exception.
"""
rubric
=
RubricPage
(
self
.
browser
)
rubric
.
wait_for_page
()
rubric
.
wait_for_page
(
timeout
=
60
)
return
rubric
@property
...
...
@@ -41,7 +47,7 @@ class PeerCalibratePage(PageObject):
"""
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
:
return
None
else
:
...
...
common/test/acceptance/pages/lms/peer_confirm.py
View file @
d88d8612
...
...
@@ -3,6 +3,7 @@ Confirmation screen for peer calibration and grading.
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
class
PeerConfirmPage
(
PageObject
):
...
...
@@ -13,7 +14,12 @@ class PeerConfirmPage(PageObject):
url
=
None
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
):
"""
...
...
@@ -21,7 +27,6 @@ class PeerConfirmPage(PageObject):
If `is_calibrating` is false, try to continue to peer grading.
Otherwise, try to continue to calibration grading.
"""
self
.
css_click
(
'input.calibration-interstitial-page-button'
self
.
q
(
css
=
'input.calibration-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.
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
from
.rubric
import
RubricPage
...
...
@@ -14,24 +15,28 @@ class PeerGradePage(PageObject):
url
=
None
def
is_browser_on_page
(
self
):
return
(
self
.
is_css_present
(
'div.peer-grading-tools'
)
or
self
.
is_css_present
(
'div.grading-panel.current-state'
)
def
_is_correct_page
():
is_present
=
(
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
def
problem_list
(
self
):
"""
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
):
"""
Choose the problem with `problem_name` to start grading or calibrating.
"""
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
def
rubric
(
self
):
...
...
common/test/acceptance/pages/lms/progress.py
View file @
d88d8612
...
...
@@ -12,9 +12,10 @@ class ProgressPage(CoursePage):
url_path
=
"progress"
#@property
def
is_browser_on_page
(
self
):
has_course_info
=
self
.
is_css_present
(
'div.course-info'
)
has_graph
=
self
.
is_css_present
(
'div#grade-detail-graph'
)
has_course_info
=
self
.
q
(
css
=
'div.course-info'
)
.
present
has_graph
=
self
.
q
(
css
=
'div#grade-detail-graph'
)
.
present
return
has_course_info
and
has_graph
def
scores
(
self
,
chapter
,
section
):
...
...
@@ -46,7 +47,7 @@ class ProgressPage(CoursePage):
Returns `None` if it cannot find such a chapter.
"""
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
:
# CSS indices are 1-indexed, so add one to the list index
...
...
@@ -65,7 +66,7 @@ class ProgressPage(CoursePage):
# Get the links containing the section titles in `chapter_index`.
# 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_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
# We have to remove this to find the right title
...
...
@@ -95,7 +96,7 @@ class ProgressPage(CoursePage):
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)
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):
def
is_browser_on_page
(
self
):
return
any
([
'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
):
...
...
@@ -42,18 +42,18 @@ class RegisterPage(PageObject):
Fill in registration info.
`email`, `password`, `username`, and `full_name` are the user's credentials.
"""
self
.
css_fill
(
'input#email'
,
email
)
self
.
css_fill
(
'input#password'
,
password
)
self
.
css_fill
(
'input#username'
,
username
)
self
.
css_fill
(
'input#name'
,
full_name
)
self
.
css_check
(
'input#tos-yes'
)
self
.
css_check
(
'input#honorcode-yes'
)
self
.
q
(
css
=
'input#email'
)
.
fill
(
email
)
self
.
q
(
css
=
'input#password'
)
.
fill
(
password
)
self
.
q
(
css
=
'input#username'
)
.
fill
(
username
)
self
.
q
(
css
=
'input#name'
)
.
fill
(
full_name
)
self
.
q
(
css
=
'input#tos-yes'
)
.
first
.
click
(
)
self
.
q
(
css
=
'input#honorcode-yes'
)
.
first
.
click
(
)
def
submit
(
self
):
"""
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
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.
"""
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
):
...
...
@@ -24,7 +24,7 @@ class RubricPage(PageObject):
"""
Return a boolean indicating whether the rubric is available.
"""
return
self
.
is_css_present
(
'div.rubric'
)
return
self
.
q
(
css
=
'div.rubric'
)
.
present
@property
def
categories
(
self
):
...
...
@@ -37,7 +37,7 @@ class RubricPage(PageObject):
The rubric is not always visible; if it's not available,
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
):
"""
...
...
@@ -60,10 +60,9 @@ class RubricPage(PageObject):
# Set the score for each category
for
score_index
in
range
(
len
(
scores
)):
# Check that we have the enough radio buttons
category_css
=
"div.rubric>ul.rubric-list:nth-of-type({0})"
.
format
(
score_index
+
1
)
if
scores
[
score_index
]
>
self
.
css_count
(
category_css
+
' input.score-selection'
):
if
scores
[
score_index
]
>
len
(
self
.
q
(
css
=
category_css
+
' input.score-selection'
)
.
results
):
raise
ScoreMismatchError
(
"Tried to select score {0} but there are only {1} options"
.
format
(
score_index
,
len
(
scores
)))
...
...
@@ -74,7 +73,12 @@ class RubricPage(PageObject):
category_css
+
">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
def
feedback
(
self
):
...
...
@@ -86,14 +90,13 @@ class RubricPage(PageObject):
If feedback could not be interpreted (unexpected CSS class),
the list will contain a `None` item.
"""
# Get the green checkmark / red x labels
# We need to filter out the similar-looking CSS classes
# for the rubric items that are NOT marked correct/incorrect
feedback_css
=
'div.rubric-label>label'
labels
=
[
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'
]
...
...
@@ -110,17 +113,29 @@ class RubricPage(PageObject):
return
map
(
map_feedback
,
labels
)
def
submit
(
self
):
def
submit
(
self
,
promise_check_type
=
None
):
"""
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
button_css
=
'input.submit-button'
button_enabled
=
EmptyPromise
(
lambda
:
all
(
self
.
css_map
(
button_css
,
lambda
el
:
not
el
[
'disabled'
])),
"Submit button enabled"
)
EmptyPromise
(
lambda
:
all
(
self
.
q
(
css
=
button_css
)
.
map
(
lambda
el
:
not
el
.
get_attribute
(
'disabled'
))
.
results
),
"Submit button not enabled"
)
.
fulfill
()
# Submit the assessment
with
fulfill_before
(
button_enabled
):
self
.
css_click
(
button_css
)
self
.
q
(
css
=
button_css
)
.
first
.
click
()
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.
"""
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
):
...
...
@@ -14,12 +14,13 @@ class TabNavPage(PageObject):
url
=
None
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
):
"""
Navigate to the tab `tab_name`.
"""
if
tab_name
not
in
[
'Courseware'
,
'Course Info'
,
'Discussion'
,
'Wiki'
,
'Progress'
]:
self
.
warning
(
"'{0}' is not a valid tab name"
.
format
(
tab_name
))
...
...
@@ -27,12 +28,13 @@ class TabNavPage(PageObject):
# so we find the tab with `tab_name` in its text.
tab_css
=
self
.
_tab_css
(
tab_name
)
with
fulfill_after
(
self
.
_is_on_tab_promise
(
tab_name
)):
if
tab_css
is
not
None
:
self
.
css_click
(
tab_css
)
self
.
q
(
css
=
tab_css
)
.
first
.
click
(
)
else
:
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
):
"""
Return a boolean indicating whether the current tab is `tab_name`.
...
...
@@ -63,10 +65,10 @@ class TabNavPage(PageObject):
if the tab names fail to load.
"""
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
fulfill
(
Promise
(
_check_func
,
"Get all tab names"
)
)
return
Promise
(
_check_func
,
"Get all tab names"
)
.
fulfill
(
)
def
_is_on_tab
(
self
,
tab_name
):
"""
...
...
@@ -74,14 +76,13 @@ class TabNavPage(PageObject):
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.
"""
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
:
self
.
warning
(
"Could not find current tab"
)
return
False
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
):
...
...
common/test/acceptance/pages/lms/video.py
View file @
d88d8612
...
...
@@ -4,7 +4,7 @@ Video player in the courseware.
import
time
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
...
...
@@ -17,7 +17,7 @@ class VideoPage(PageObject):
url
=
None
def
is_browser_on_page
(
self
):
return
self
.
is_css_present
(
'div.xmodule_VideoModule'
)
return
self
.
q
(
css
=
'div.xmodule_VideoModule'
)
.
present
@property
def
elapsed_time
(
self
):
...
...
@@ -40,37 +40,37 @@ class VideoPage(PageObject):
"""
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
def
is_paused
(
self
):
"""
Return a boolean indicating whether the video is paused.
"""
return
self
.
is_css_present
(
'a.video_control'
)
and
self
.
is_css_present
(
'a.video_control.play'
)
return
self
.
q
(
css
=
'a.video_control'
)
.
present
and
self
.
q
(
css
=
'a.video_control.play'
)
.
present
@wait_for_js
def
play
(
self
):
"""
Start playing the video.
"""
with
fulfill_after
(
EmptyPromise
(
lambda
:
self
.
is_playing
,
"Video is playing"
)):
self
.
css_click
(
'a.video_control.play'
)
self
.
q
(
css
=
'a.video_control.play'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
is_playing
,
"Video is playing"
)
@wait_for_js
def
pause
(
self
):
"""
Pause the video.
"""
with
fulfill_after
(
EmptyPromise
(
lambda
:
self
.
is_paused
,
"Video is paused"
)):
self
.
css_click
(
'a.video_control.pause'
)
self
.
q
(
css
=
'a.video_control.pause'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
is_paused
,
"Video is paused"
)
def
_video_time
(
self
):
"""
Return a tuple `(elapsed_time, duration)`, each in seconds.
"""
# The full time has the form "0:32 / 3:14"
all_times
=
self
.
css_text
(
'div.vidtime'
)
all_times
=
self
.
q
(
css
=
'div.vidtime'
)
.
text
if
len
(
all_times
)
==
0
:
self
.
warning
(
'Could not find video time'
)
...
...
@@ -82,7 +82,7 @@ class VideoPage(PageObject):
elapsed_str
,
duration_str
=
full_time
.
split
(
' / '
)
# Convert each string to seconds
return
(
self
.
_parse_time_str
(
elapsed_str
),
self
.
_parse_time_str
(
duration_str
)
)
return
self
.
_parse_time_str
(
elapsed_str
),
self
.
_parse_time_str
(
duration_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):
url_path
=
"assets"
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):
return
True
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
)
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):
url_path
=
"checklists"
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):
def
is_browser_on_page
(
self
):
# Wait until all components have been loaded
return
(
self
.
is_css_present
(
'body.view-container'
)
and
len
(
self
.
q
(
css
=
XBlockWrapper
.
BODY_SELECTOR
))
==
len
(
self
.
q
(
css
=
'{} .xblock'
.
format
(
XBlockWrapper
.
BODY_SELECTOR
)))
self
.
q
(
css
=
'body.view-container'
)
.
present
and
len
(
self
.
q
(
css
=
XBlockWrapper
.
BODY_SELECTOR
)
.
results
)
==
len
(
self
.
q
(
css
=
'{} .xblock'
.
format
(
XBlockWrapper
.
BODY_SELECTOR
))
.
results
)
)
@property
...
...
@@ -33,7 +34,8 @@ class ContainerPage(PageObject):
"""
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
):
...
...
@@ -49,7 +51,7 @@ class XBlockWrapper(PageObject):
self
.
locator
=
locator
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
):
"""
...
...
@@ -63,7 +65,7 @@ class XBlockWrapper(PageObject):
@property
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
:
return
titles
[
0
]
else
:
...
...
common/test/acceptance/pages/studio/course_import.py
View file @
d88d8612
...
...
@@ -13,4 +13,4 @@ class ImportPage(CoursePage):
url_path
=
"import"
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):
url_path
=
"course_info"
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):
"""
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):
url_path
=
"tabs"
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):
url_path
=
"export"
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):
url
=
BASE_URL
+
"/howitworks"
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):
url
=
BASE_URL
+
"/course"
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.
"""
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
...
...
@@ -15,20 +15,19 @@ class LoginPage(PageObject):
url
=
BASE_URL
+
"/signin"
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
):
"""
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
on_next_page
=
EmptyPromise
(
EmptyPromise
(
lambda
:
"login"
not
in
self
.
browser
.
url
,
"redirected from the login page"
)
with
fulfill_after
(
on_next_page
):
self
.
css_fill
(
'input#email'
,
email
)
self
.
css_fill
(
'input#password'
,
password
)
self
.
css_click
(
'button#submit'
)
)
.
fulfill
()
common/test/acceptance/pages/studio/manage_users.py
View file @
d88d8612
...
...
@@ -13,4 +13,4 @@ class CourseTeamPage(CoursePage):
url_path
=
"course_team"
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 @@
Course Outline page in Studio.
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.query
import
SubQuery
from
bok_choy.promise
import
EmptyPromise
,
fulfill
from
bok_choy.promise
import
EmptyPromise
from
.course_page
import
CoursePage
from
.unit
import
UnitPage
class
CourseOutlineContainer
(
object
):
"""
A mixin to a CourseOutline page object that adds the ability to load
...
...
@@ -18,13 +18,19 @@ class CourseOutlineContainer(object):
CHILD_CLASS
=
None
def
child
(
self
,
title
,
child_class
=
None
):
"""
:type self: object
"""
if
not
child_class
:
child_class
=
self
.
CHILD_CLASS
return
child_class
(
self
.
browser
,
self
.
q
(
css
=
child_class
.
BODY_SELECTOR
)
.
filter
(
SubQuery
(
css
=
child_class
.
NAME_SELECTOR
)
.
filter
(
text
=
title
)
)[
0
][
'data-locator'
]
lambda
el
:
title
in
[
inner
.
text
for
inner
in
el
.
find_elements_by_css_selector
(
child_class
.
NAME_SELECTOR
)]
)
.
attrs
(
'data-locator'
)[
0
]
)
...
...
@@ -104,22 +110,24 @@ class CourseOutlineSubsection(CourseOutlineChild, CourseOutlineContainer):
"""
Toggle the expansion of this subsection.
"""
self
.
disable_jquery_animations
(
)
self
.
browser
.
execute_script
(
"jQuery.fx.off = true;"
)
def
subsection_expanded
():
return
all
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.new-unit-item'
))
.
map
(
lambda
el
:
el
.
visible
)
.
map
(
lambda
el
:
el
.
is_displayed
()
)
.
results
)
currently_expanded
=
subsection_expanded
()
self
.
css_click
(
self
.
_bounded_selector
(
'.expand-collapse'
))
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.expand-collapse'
))
.
first
.
click
()
EmptyPromise
(
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
...
...
@@ -147,7 +155,7 @@ class CourseOutlinePage(CoursePage, CourseOutlineContainer):
CHILD_CLASS
=
CourseOutlineSection
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
):
"""
...
...
common/test/acceptance/pages/studio/settings.py
View file @
d88d8612
...
...
@@ -13,4 +13,4 @@ class SettingsPage(CoursePage):
url_path
=
"settings/details"
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):
url_path
=
"settings/advanced"
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):
url_path
=
"settings/grading"
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):
url
=
BASE_URL
+
"/signup"
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):
url_path
=
"textbooks"
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
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.query
import
SubQuery
from
bok_choy.promise
import
EmptyPromise
,
fulfill
from
bok_choy.promise
import
EmptyPromise
from
.
import
BASE_URL
from
.container
import
ContainerPage
...
...
@@ -26,11 +25,11 @@ class UnitPage(PageObject):
def
is_browser_on_page
(
self
):
# Wait until all components have been loaded
number_of_leaf_xblocks
=
len
(
self
.
q
(
css
=
'{} .xblock-student_view'
.
format
(
Component
.
BODY_SELECTOR
)))
number_of_container_xblocks
=
len
(
self
.
q
(
css
=
'{} .wrapper-xblock'
.
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
))
.
results
)
return
(
self
.
is_css_present
(
'body.view-unit'
)
and
len
(
self
.
q
(
css
=
Component
.
BODY_SELECTOR
))
==
number_of_leaf_xblocks
+
number_of_container_xblocks
self
.
q
(
css
=
'body.view-unit'
)
.
present
and
len
(
self
.
q
(
css
=
Component
.
BODY_SELECTOR
)
.
results
)
==
number_of_leaf_xblocks
+
number_of_container_xblocks
)
@property
...
...
@@ -38,21 +37,24 @@ class UnitPage(PageObject):
"""
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
):
"""
Started editing a draft of this unit.
"""
fulfill
(
EmptyPromise
(
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.create-draft'
)
.
present
,
'Wait for edit draft link to be present'
))
self
.
q
(
css
=
'.create-draft'
)
.
click
()
fulfill
(
EmptyPromise
(
)
.
fulfill
()
self
.
q
(
css
=
'.create-draft'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.editing-draft-alert'
)
.
present
,
'Wait for draft mode to be activated'
))
)
.
fulfill
(
)
class
Component
(
PageObject
):
...
...
@@ -69,7 +71,7 @@ class Component(PageObject):
self
.
locator
=
locator
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
):
"""
...
...
@@ -83,7 +85,7 @@ class Component(PageObject):
@property
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
:
return
titles
[
0
]
else
:
...
...
@@ -94,15 +96,15 @@ class Component(PageObject):
return
self
.
_bounded_selector
(
'.xblock-student_view'
)
def
edit
(
self
):
self
.
css_click
(
self
.
_bounded_selector
(
'.edit-button'
)
)
fulfill
(
EmptyPromise
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.edit-button'
))
.
first
.
click
(
)
EmptyPromise
(
lambda
:
all
(
self
.
q
(
css
=
self
.
_bounded_selector
(
'.component-editor'
))
.
map
(
lambda
el
:
el
.
visible
)
.
results
),
.
map
(
lambda
el
:
el
.
is_displayed
())
.
results
),
"Verify that the editor for component {} has been expanded"
.
format
(
self
.
locator
)
))
)
.
fulfill
()
return
self
@property
...
...
common/test/acceptance/pages/xblock/acid.py
View file @
d88d8612
...
...
@@ -3,8 +3,7 @@ PageObjects related to the AcidBlock
"""
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
):
"""
...
...
@@ -15,7 +14,7 @@ class AcidView(PageObject):
def
__init__
(
self
,
browser
,
context_selector
):
"""
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
is on the page.
"""
...
...
@@ -25,14 +24,17 @@ class AcidView(PageObject):
self
.
context_selector
=
context_selector
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
):
"""
Return whether a particular :class:`.AcidBlock` test passed.
"""
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
):
"""
...
...
common/test/acceptance/tests/test_discussion.py
View file @
d88d8612
...
...
@@ -161,8 +161,9 @@ class DiscussionCommentEditTest(UniqueCourseTest):
def
edit_comment
(
self
,
page
,
comment_id
):
page
.
start_comment_edit
(
comment_id
)
page
.
set_comment_editor_value
(
comment_id
,
"edited body"
)
page
.
submit_comment_edit
(
comment_id
)
new_comment
=
"edited body"
page
.
set_comment_editor_value
(
comment_id
,
new_comment
)
page
.
submit_comment_edit
(
comment_id
,
new_comment
)
def
test_edit_comment_as_student
(
self
):
self
.
setup_user
()
...
...
common/test/acceptance/tests/test_lms.py
View file @
d88d8612
...
...
@@ -3,17 +3,12 @@
E2E tests for the LMS.
"""
from
unittest
import
skip
,
expectedFailure
from
bok_choy.web_app_test
import
WebAppTest
from
bok_choy.promise
import
EmptyPromise
,
fulfill_before
,
fulfill
,
Promise
from
unittest
import
skip
from
.helpers
import
UniqueCourseTest
,
load_data_str
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.lms.login
import
LoginPage
from
..pages.lms.find_courses
import
FindCoursesPage
from
..pages.lms.course_about
import
CourseAboutPage
from
..pages.lms.register
import
RegisterPage
from
..pages.lms.course_info
import
CourseInfoPage
from
..pages.lms.tab_nav
import
TabNavPage
from
..pages.lms.course_nav
import
CourseNavPage
...
...
@@ -69,36 +64,12 @@ class RegistrationTest(UniqueCourseTest):
course_names
=
dashboard
.
available_courses
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
):
"""
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
):
"""
Initiailize dashboard page
...
...
@@ -116,18 +87,17 @@ class LanguageTest(UniqueCourseTest):
self
.
password
=
"testpass"
self
.
email
=
"test@example.com"
@skip
(
"Flakey in its present form; re-enable when fixed"
)
def
test_change_lang
(
self
):
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
self
.
dashboard_page
.
visit
()
# Change language to Dummy Esperanto
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
self
.
assertIn
(
self
.
current_courses_text
,
changed_text
)
@skip
(
"Flakey in its present form; re-enable when fixed"
)
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
.
visit
()
...
...
@@ -137,14 +107,15 @@ class LanguageTest(UniqueCourseTest):
self
.
dashboard_page
.
change_language
(
self
.
test_new_lang
)
# destroy session
self
.
browser
.
_cookie_manager
.
delete
()
self
.
browser
.
delete_all_cookies
()
# log back in
auto_auth_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
self
.
assertIn
(
self
.
current_courses_text
,
changed_text
)
...
...
@@ -173,7 +144,7 @@ class HighLevelTabTest(UniqueCourseTest):
)
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'
)
...
...
@@ -200,6 +171,7 @@ class HighLevelTabTest(UniqueCourseTest):
"""
Navigate to the course info page.
"""
# Navigate to the course info page from the progress page
self
.
progress_page
.
visit
()
self
.
tab_nav
.
go_to_tab
(
'Course Info'
)
...
...
@@ -251,6 +223,7 @@ class HighLevelTabTest(UniqueCourseTest):
'Test Section'
:
[
'Test Subsection'
],
'Test Section 2'
:
[
'Test Subsection 2'
,
'Test Subsection 3'
]
}
actual_sections
=
self
.
course_nav
.
sections
for
section
,
subsections
in
EXPECTED_SECTIONS
.
iteritems
():
self
.
assertIn
(
section
,
actual_sections
)
...
...
@@ -321,13 +294,13 @@ class VideoTest(UniqueCourseTest):
# Now we should be 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
video_duration_loaded
=
EmptyPromise
(
lambda
:
self
.
video
.
duration
>
0
,
'video has duration'
,
timeout
=
20
)
with
fulfill_before
(
video_duration_loaded
):
# EmptyPromise(
# lambda: self.video.duration > 0,
# 'video has duration', timeout=20
# ).fulfill()
# Pause the video
self
.
video
.
pause
()
...
...
@@ -441,7 +414,6 @@ class XBlockAcidChildTest(XBlockAcidBase):
super
(
XBlockAcidChildTest
,
self
)
.
validate_acid_block_view
()
self
.
assertTrue
(
acid_block
.
child_tests_passed
)
# This will fail until we fix support of children in pure XBlocks
@expectedFailure
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
def
test_acid_block
(
self
):
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.
"""
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.lms.course_info
import
CourseInfoPage
from
..pages.lms.tab_nav
import
TabNavPage
...
...
@@ -11,7 +14,7 @@ from ..pages.lms.course_nav import CourseNavPage
from
..pages.lms.open_response
import
OpenResponsePage
from
..pages.lms.peer_grade
import
PeerGradePage
from
..pages.lms.peer_calibrate
import
PeerCalibratePage
from
..pages.lms.peer_confirm
import
PeerConfirmPage
from
..pages.lms.progress
import
ProgressPage
from
..fixtures.course
import
XBlockFixtureDesc
,
CourseFixture
from
..fixtures.xqueue
import
XQueueResponseFixture
...
...
@@ -123,12 +126,11 @@ class OpenResponseTest(UniqueCourseTest):
# Because the check function involves fairly complicated actions
# (navigating through several screens), we give it more time to complete
# than the default.
feedback_promise
=
Promise
(
return
Promise
(
self
.
_check_feedback_func
(
assessment_type
),
'Got feedback for {0} problem'
.
format
(
assessment_type
),
timeout
=
600
,
try_interval
=
5
)
return
fulfill
(
feedback_promise
)
)
.
fulfill
()
def
_check_feedback_func
(
self
,
assessment_type
):
"""
...
...
@@ -155,11 +157,11 @@ class OpenResponseTest(UniqueCourseTest):
# Unsuccessful if the rubric hasn't loaded
except
BrokenPromise
:
return
(
False
,
None
)
return
False
,
None
# Successful if `feedback` is a non-empty list
else
:
return
(
bool
(
feedback
),
feedback
)
return
bool
(
feedback
),
feedback
return
_inner_check
...
...
@@ -183,9 +185,11 @@ class SelfAssessmentTest(OpenResponseTest):
# Fill in the rubric and expect that we get feedback
rubric
=
self
.
open_response
.
rubric
self
.
assertEqual
(
rubric
.
categories
,
[
"Writing Applications"
,
"Language Conventions"
])
rubric
.
set_scores
([
0
,
1
])
rubric
.
submit
()
rubric
.
submit
(
'self'
)
self
.
assertEqual
(
rubric
.
feedback
,
[
'incorrect'
,
'correct'
])
# Verify the progress page
...
...
@@ -213,6 +217,7 @@ class AIAssessmentTest(OpenResponseTest):
'rubric_xml'
:
load_data_str
(
'ora_rubric.xml'
)
}
@skip
(
'Intermittently failing, see ORA-342'
)
def
test_ai_assessment
(
self
):
"""
Given I am viewing an AI-assessment problem that has a trained ML model
...
...
@@ -258,6 +263,7 @@ class InstructorAssessmentTest(OpenResponseTest):
'rubric_xml'
:
load_data_str
(
'ora_rubric.xml'
)
}
@skip
(
'Intermittently failing, see ORA-342'
)
def
test_instructor_assessment
(
self
):
"""
Given an instructor has graded my submission
...
...
@@ -306,7 +312,7 @@ class PeerAssessmentTest(OpenResponseTest):
"""
Given I am viewing a peer-assessment problem
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.
Given I have submitted an essay for peer-assessment
...
...
@@ -339,7 +345,7 @@ class PeerAssessmentTest(OpenResponseTest):
rubric
=
self
.
peer_calibrate
.
rubric
self
.
assertEqual
(
rubric
.
categories
,
[
"Writing Applications"
,
"Language Conventions"
])
rubric
.
set_scores
([
0
,
1
])
rubric
.
submit
()
rubric
.
submit
(
'peer'
)
self
.
peer_calibrate
.
continue_to_grading
()
# Grade a peer
...
...
common/test/acceptance/tests/test_studio.py
View file @
d88d8612
"""
Acceptance tests for Studio.
"""
from
unittest
import
expectedFailure
from
unittest
import
skip
from
bok_choy.web_app_test
import
WebAppTest
...
...
@@ -25,7 +25,7 @@ from ..pages.studio.textbooks import TextbooksPage
from
..pages.xblock.acid
import
AcidView
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
.helpers
import
UniqueCourseTest
from
.helpers
import
UniqueCourseTest
,
load_data_str
class
LoggedOutTest
(
WebAppTest
):
...
...
@@ -237,8 +237,7 @@ class XBlockAcidParentBase(XBlockAcidBase):
acid_block
=
AcidView
(
self
.
browser
,
container
.
xblocks
[
0
]
.
preview_selector
)
self
.
validate_acid_block_preview
(
acid_block
)
# This will fail until the container page supports editing
@expectedFailure
@skip
(
'This will fail until the container page supports editing'
)
def
test_acid_block_editor
(
self
):
super
(
XBlockAcidParentBase
,
self
)
.
test_acid_block_editor
()
...
...
@@ -299,12 +298,10 @@ class XBlockAcidChildTest(XBlockAcidParentBase):
)
)
.
install
()
# This will fail until we fix support of children in pure XBlocks
@expectedFailure
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
def
test_acid_block_preview
(
self
):
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block_preview
()
# This will fail until we fix support of children in pure XBlocks
@expectedFailure
@skip
(
'This will fail until we fix support of children in pure XBlocks'
)
def
test_acid_block_editor
(
self
):
super
(
XBlockAcidChildTest
,
self
)
.
test_acid_block_editor
()
requirements/edx/github.txt
View file @
d88d8612
...
...
@@ -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/django-waffle.git@823a102e48#egg=django-waffle
-e git+https://github.com/edx/event-tracking.git@f0211d702d#egg=event-tracking
-e git+https://github.com/edx/bok-choy.git@
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/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