Refactored bok-choy directory structure
Added fixtures for course and xblock creation Added bok-choy Studio tests Added bok-choy tests for ora self- and ai- assessment Refactored auto-auth; added staff and course-enrollment options Removed extra javascript properties from page objects
This commit is contained in:
10
common/test/acceptance/tests/__init__.py
Normal file
10
common/test/acceptance/tests/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import logging
|
||||
|
||||
# Silence noisy loggers
|
||||
LOG_OVERRIDES = [
|
||||
('requests.packages.urllib3.connectionpool', logging.ERROR),
|
||||
('django.db.backends', logging.ERROR)
|
||||
]
|
||||
|
||||
for log_name, log_level in LOG_OVERRIDES:
|
||||
logging.getLogger(log_name).setLevel(log_level)
|
||||
30
common/test/acceptance/tests/data/ora_ai_problem.xml
Normal file
30
common/test/acceptance/tests/data/ora_ai_problem.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<combinedopenended max_score="2" markdown="null" max_attempts="1000">
|
||||
<rubric>
|
||||
<rubric>
|
||||
<category>
|
||||
<description>Writing Applications</description>
|
||||
<option> The essay loses focus, has little information or supporting details, and the organization makes it difficult to follow.</option>
|
||||
<option> The essay presents a mostly unified theme, includes sufficient information to convey the theme, and is generally organized well.</option>
|
||||
</category>
|
||||
<category>
|
||||
<description> Language Conventions </description>
|
||||
<option> The essay demonstrates a reasonable command of proper spelling and grammar. </option>
|
||||
<option> The essay demonstrates superior command of proper spelling and grammar.</option>
|
||||
</category>
|
||||
</rubric>
|
||||
</rubric>
|
||||
<prompt>
|
||||
<h4>Censorship in the Libraries</h4>
|
||||
<p>"All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us." --Katherine Paterson, Author</p>
|
||||
<p>Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.</p>
|
||||
</prompt>
|
||||
<task>
|
||||
<openended>
|
||||
<openendedparam>
|
||||
<initial_display>Enter essay here.</initial_display>
|
||||
<answer_display>This is the answer.</answer_display>
|
||||
<grader_payload>{"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
|
||||
</openendedparam>
|
||||
</openended>
|
||||
</task>
|
||||
</combinedopenended>
|
||||
24
common/test/acceptance/tests/data/ora_self_problem.xml
Normal file
24
common/test/acceptance/tests/data/ora_self_problem.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<combinedopenended max_score="1" accept_file_upload="False" markdown="null" max_attempts="1000" skip_spelling_checks="False" version="1">
|
||||
<rubric>
|
||||
<rubric>
|
||||
<category>
|
||||
<description>Writing Applications</description>
|
||||
<option> The essay loses focus, has little information or supporting details, and the organization makes it difficult to follow.</option>
|
||||
<option> The essay presents a mostly unified theme, includes sufficient information to convey the theme, and is generally organized well.</option>
|
||||
</category>
|
||||
<category>
|
||||
<description> Language Conventions </description>
|
||||
<option> The essay demonstrates a reasonable command of proper spelling and grammar. </option>
|
||||
<option> The essay demonstrates superior command of proper spelling and grammar.</option>
|
||||
</category>
|
||||
</rubric>
|
||||
</rubric>
|
||||
<prompt>
|
||||
<h4>Censorship in the Libraries</h4>
|
||||
<p>"All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us." --Katherine Paterson, Author</p>
|
||||
<p>Write a persuasive essay to a newspaper reflecting your views on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.</p>
|
||||
</prompt>
|
||||
<task>
|
||||
<selfassessment/>
|
||||
</task>
|
||||
</combinedopenended>
|
||||
14
common/test/acceptance/tests/helpers.py
Normal file
14
common/test/acceptance/tests/helpers.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Test helper functions.
|
||||
"""
|
||||
from path import path
|
||||
|
||||
|
||||
def load_data_str(rel_path):
|
||||
"""
|
||||
Load a file from the "data" directory as a string.
|
||||
`rel_path` is the path relative to the data directory.
|
||||
"""
|
||||
full_path = path(__file__).abspath().dirname() / "data" / rel_path #pylint: disable=E1120
|
||||
with open(full_path) as data_file:
|
||||
return data_file.read()
|
||||
132
common/test/acceptance/tests/test_ora.py
Normal file
132
common/test/acceptance/tests/test_ora.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
Tests for ORA (Open Response Assessment) through the LMS UI.
|
||||
"""
|
||||
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
from ..edxapp_pages.studio.auto_auth import AutoAuthPage
|
||||
from ..edxapp_pages.lms.course_info import CourseInfoPage
|
||||
from ..edxapp_pages.lms.tab_nav import TabNavPage
|
||||
from ..edxapp_pages.lms.course_nav import CourseNavPage
|
||||
from ..edxapp_pages.lms.open_response import OpenResponsePage
|
||||
from ..fixtures.course import XBlockFixtureDesc, CourseFixture
|
||||
|
||||
from .helpers import load_data_str
|
||||
|
||||
|
||||
class OpenResponseTest(WebAppTest):
|
||||
"""
|
||||
Tests that interact with ORA (Open Response Assessment) through the LMS UI.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Always start in the subsection with open response problems.
|
||||
"""
|
||||
|
||||
# Create a unique course ID
|
||||
self.course_info = {
|
||||
'org': 'test_org',
|
||||
'number': self.unique_id,
|
||||
'run': 'test_run',
|
||||
'display_name': 'Test Course' + self.unique_id
|
||||
}
|
||||
|
||||
# Ensure fixtures are installed
|
||||
super(OpenResponseTest, self).setUp()
|
||||
|
||||
# Log in and navigate to the essay problems
|
||||
course_id = '{org}/{number}/{run}'.format(**self.course_info)
|
||||
self.ui.visit('studio.auto_auth', course_id=course_id)
|
||||
self.ui.visit('lms.course_info', course_id=course_id)
|
||||
self.ui['lms.tab_nav'].go_to_tab('Courseware')
|
||||
self.ui['lms.course_nav'].go_to_section(
|
||||
'Example Week 2: Get Interactive', 'Homework - Essays'
|
||||
)
|
||||
|
||||
@property
|
||||
def page_object_classes(self):
|
||||
return [AutoAuthPage, CourseInfoPage, TabNavPage, CourseNavPage, OpenResponsePage]
|
||||
|
||||
@property
|
||||
def fixtures(self):
|
||||
"""
|
||||
Create a test course with open response problems.
|
||||
"""
|
||||
course_fix = CourseFixture(
|
||||
self.course_info['org'], self.course_info['number'],
|
||||
self.course_info['run'], self.course_info['display_name']
|
||||
)
|
||||
|
||||
course_fix.add_children(
|
||||
XBlockFixtureDesc('chapter', 'Test Section').add_children(
|
||||
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
|
||||
XBlockFixtureDesc('combinedopenended', 'Self-Assessed', data=load_data_str('ora_self_problem.xml')),
|
||||
XBlockFixtureDesc('combinedopenended', 'AI-Assessed', data=load_data_str('ora_ai_problem.xml'))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return [course_fix]
|
||||
|
||||
def test_self_assessment(self):
|
||||
"""
|
||||
Test that the user can self-assess an essay.
|
||||
"""
|
||||
# Navigate to the self-assessment problem and submit an essay
|
||||
self.ui['lms.course_nav'].go_to_sequential('Self-Assessed')
|
||||
self._submit_essay('self', 'Censorship in the Libraries')
|
||||
|
||||
# Check the rubric categories
|
||||
self.assertEqual(
|
||||
self.ui['lms.open_response'].rubric_categories,
|
||||
["Writing Applications", "Language Conventions"]
|
||||
)
|
||||
|
||||
# Fill in the self-assessment rubric
|
||||
self.ui['lms.open_response'].submit_self_assessment([0, 1])
|
||||
|
||||
# Expect that we get feedback
|
||||
self.assertEqual(
|
||||
self.ui['lms.open_response'].rubric_feedback,
|
||||
['incorrect', 'correct']
|
||||
)
|
||||
|
||||
def test_ai_assessment(self):
|
||||
"""
|
||||
Test that a user can submit an essay and receive AI feedback.
|
||||
"""
|
||||
|
||||
# Navigate to the AI-assessment problem and submit an essay
|
||||
self.ui['lms.course_nav'].go_to_sequential('AI-Assessed')
|
||||
self._submit_essay('ai', 'Censorship in the Libraries')
|
||||
|
||||
# Expect UI feedback that the response was submitted
|
||||
self.assertEqual(
|
||||
self.ui['lms.open_response'].grader_status,
|
||||
"Your response has been submitted. Please check back later for your grade."
|
||||
)
|
||||
|
||||
def _submit_essay(self, expected_assessment_type, expected_prompt):
|
||||
"""
|
||||
Submit an essay and verify that the problem uses
|
||||
the `expected_assessment_type` ("self", "ai", or "peer") and
|
||||
shows the `expected_prompt` (a string).
|
||||
"""
|
||||
|
||||
# Check the assessment type and prompt
|
||||
self.assertEqual(self.ui['lms.open_response'].assessment_type, expected_assessment_type)
|
||||
self.assertIn(expected_prompt, self.ui['lms.open_response'].prompt)
|
||||
|
||||
# Enter a response
|
||||
essay = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vehicula."
|
||||
self.ui['lms.open_response'].set_response(essay)
|
||||
|
||||
# Save the response and expect some UI feedback
|
||||
self.ui['lms.open_response'].save_response()
|
||||
self.assertEqual(
|
||||
self.ui['lms.open_response'].alert_message,
|
||||
"Answer saved, but not yet submitted."
|
||||
)
|
||||
|
||||
# Submit the response
|
||||
self.ui['lms.open_response'].submit_response()
|
||||
118
common/test/acceptance/tests/test_studio.py
Normal file
118
common/test/acceptance/tests/test_studio.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Acceptance tests for Studio.
|
||||
"""
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
|
||||
from ..edxapp_pages.studio.asset_index import AssetIndexPage
|
||||
from ..edxapp_pages.studio.auto_auth import AutoAuthPage
|
||||
from ..edxapp_pages.studio.checklists import ChecklistsPage
|
||||
from ..edxapp_pages.studio.course_import import ImportPage
|
||||
from ..edxapp_pages.studio.course_info import CourseUpdatesPage
|
||||
from ..edxapp_pages.studio.edit_tabs import StaticPagesPage
|
||||
from ..edxapp_pages.studio.export import ExportPage
|
||||
from ..edxapp_pages.studio.howitworks import HowitworksPage
|
||||
from ..edxapp_pages.studio.index import DashboardPage
|
||||
from ..edxapp_pages.studio.login import LoginPage
|
||||
from ..edxapp_pages.studio.manage_users import CourseTeamPage
|
||||
from ..edxapp_pages.studio.overview import CourseOutlinePage
|
||||
from ..edxapp_pages.studio.settings import SettingsPage
|
||||
from ..edxapp_pages.studio.settings_advanced import AdvancedSettingsPage
|
||||
from ..edxapp_pages.studio.settings_graders import GradingPage
|
||||
from ..edxapp_pages.studio.signup import SignupPage
|
||||
from ..edxapp_pages.studio.textbooks import TextbooksPage
|
||||
from ..fixtures.course import CourseFixture
|
||||
|
||||
|
||||
class LoggedOutTest(WebAppTest):
|
||||
"""
|
||||
Smoke test for pages in Studio that are visible when logged out.
|
||||
"""
|
||||
|
||||
@property
|
||||
def page_object_classes(self):
|
||||
return [LoginPage, HowitworksPage, SignupPage]
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that all the pages are accessible.
|
||||
Rather than fire up the browser just to check each url,
|
||||
do them all sequentially in this testcase.
|
||||
"""
|
||||
for page in ['login', 'howitworks', 'signup']:
|
||||
self.ui.visit('studio.{0}'.format(page))
|
||||
|
||||
|
||||
class LoggedInPagesTest(WebAppTest):
|
||||
"""
|
||||
Tests that verify the pages in Studio that you can get to when logged
|
||||
in and do not have a course yet.
|
||||
"""
|
||||
@property
|
||||
def page_object_classes(self):
|
||||
return [AutoAuthPage, DashboardPage]
|
||||
|
||||
def test_dashboard_no_courses(self):
|
||||
"""
|
||||
Make sure that you can get to the dashboard page without a course.
|
||||
"""
|
||||
self.ui.visit('studio.auto_auth', staff=True)
|
||||
self.ui.visit('studio.dashboard')
|
||||
|
||||
|
||||
class CoursePagesTest(WebAppTest):
|
||||
"""
|
||||
Tests that verify the pages in Studio that you can get to when logged
|
||||
in and have a course.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create a unique identifier for the course used in this test.
|
||||
"""
|
||||
# Define a unique course identifier
|
||||
self.course_info = {
|
||||
'org': 'test_org',
|
||||
'number': '101',
|
||||
'run': 'test_' + self.unique_id,
|
||||
'display_name': 'Test Course ' + self.unique_id
|
||||
}
|
||||
|
||||
# Ensure that the superclass sets up
|
||||
super(CoursePagesTest, self).setUp()
|
||||
|
||||
@property
|
||||
def page_object_classes(self):
|
||||
return [
|
||||
AutoAuthPage, AssetIndexPage, ChecklistsPage, ImportPage, CourseUpdatesPage,
|
||||
StaticPagesPage, ExportPage, CourseTeamPage, CourseOutlinePage,
|
||||
SettingsPage, AdvancedSettingsPage, GradingPage, TextbooksPage
|
||||
]
|
||||
|
||||
@property
|
||||
def fixtures(self):
|
||||
super_fixtures = super(CoursePagesTest, self).fixtures
|
||||
course_fix = CourseFixture(
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run'],
|
||||
self.course_info['display_name']
|
||||
)
|
||||
return set(super_fixtures + [course_fix])
|
||||
|
||||
def test_page_existence(self):
|
||||
"""
|
||||
Make sure that all these pages are accessible once you have a course.
|
||||
Rather than fire up the browser just to check each url,
|
||||
do them all sequentially in this testcase.
|
||||
"""
|
||||
pages = [
|
||||
'uploads', 'checklists', 'import', 'updates', 'tabs', 'export',
|
||||
'team', 'outline', 'settings', 'advanced', 'grading', 'textbooks'
|
||||
]
|
||||
|
||||
# Log in
|
||||
self.ui.visit('studio.auto_auth', staff=True)
|
||||
|
||||
course_id = '{org}.{number}.{run}'.format(**self.course_info)
|
||||
for page in pages:
|
||||
self.ui.visit('studio.{0}'.format(page), course_id=course_id)
|
||||
Reference in New Issue
Block a user