Add item checking within courseware tabs
This commit is contained in:
@@ -1,10 +0,0 @@
|
||||
Feature: There are many different types of tabs
|
||||
In order to validate tab types
|
||||
As a staff member
|
||||
I want to try out all the videos, buttons, and content
|
||||
|
||||
Scenario: I visit a tabbed quiz
|
||||
Given I am registered for course "MITx/6.002x-EE98/2012_Fall_SJSU"
|
||||
And I log in
|
||||
Given I visit and check 502 for "http://www.edx.org/courses/MITx/6.002x-EE98/2012_Fall_SJSU/courseware/Week_0/Administrivia_and_Circuit_Elements/"
|
||||
I process
|
||||
@@ -1,197 +0,0 @@
|
||||
from lettuce import * #before, world
|
||||
from selenium import *
|
||||
#import lettuce_webdriver.webdriver
|
||||
import logging
|
||||
import nose.tools
|
||||
from selenium.webdriver import ActionChains
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
import re
|
||||
|
||||
## imported from lms/djangoapps/courseware/courses.py
|
||||
from collections import defaultdict
|
||||
from fs.errors import ResourceNotFoundError
|
||||
from functools import wraps
|
||||
import logging
|
||||
|
||||
from path import path
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from static_replace import replace_urls, try_staticfiles_lookup
|
||||
from courseware.access import has_access
|
||||
## end import
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from courseware.courses import course_image_url, get_course_about_section, get_course_by_id
|
||||
from courses import *
|
||||
import os.path
|
||||
import sys
|
||||
path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static'))
|
||||
if not path in sys.path:
|
||||
sys.path.insert(1, path)
|
||||
del path
|
||||
#from helpers import *
|
||||
|
||||
|
||||
@step(u'I visit and check 502 for "(.*)"')
|
||||
def i_visit_and_check_502_for_url(step, url):
|
||||
world.browser.get(url)
|
||||
check_for_502(url)
|
||||
|
||||
@step(u'I process')
|
||||
def i_make_sure_everything_is_there(step):
|
||||
e = world.browser.find_element_by_css_selector('section.course-content section')
|
||||
process_section(e)
|
||||
|
||||
|
||||
def process_section(element, num_tabs=0):
|
||||
'''
|
||||
Process section reads through whatever is in 'course-content' and classifies it according to sequence module type.
|
||||
|
||||
This function is recursive
|
||||
|
||||
There are 5 types, with 5 actions.
|
||||
|
||||
Sequence Module
|
||||
-contains one child module
|
||||
-to prevent from over-processing all its children (no easy way to specify only one level of depth), we only grab the first child
|
||||
|
||||
Vertical Module
|
||||
-contains other modules
|
||||
-process it and get its children, then process them
|
||||
|
||||
Capa Module
|
||||
-problem type, contains only one problem
|
||||
-for this, the most complex type, we created a separate method, process_problem
|
||||
|
||||
Video Module
|
||||
-video type, contains only one video
|
||||
-we only check to ensure that a section with class of video exists
|
||||
|
||||
Custom Tag Module
|
||||
-a custom 'hack' module type
|
||||
-there is a large variety of content that could go in a custom tag module, so we just pass if it is of this unusual type
|
||||
'''
|
||||
tab_type = element.get_attribute('class')
|
||||
print 'processing a %s' % (tab_type)
|
||||
if tab_type == "xmodule_display xmodule_SequenceModule":
|
||||
child_modules = element.find_elements_by_css_selector("section[class^='xmodule']")
|
||||
|
||||
## ugly bit of code to get around not being able to specify only the first level of descendants
|
||||
if child_modules[0].get_attribute('class') == "xmodule_display xmodule_VerticalModule":
|
||||
process_section(child_modules[0])
|
||||
else:
|
||||
for mod in child_modules:
|
||||
process_section(mod)
|
||||
|
||||
elif tab_type == "xmodule_display xmodule_VerticalModule":
|
||||
vert_list = element.find_elements_by_css_selector("li section[class^='xmodule']")
|
||||
print "I found %s items" % (str(len(vert_list)))
|
||||
for item in vert_list:
|
||||
print 'processing a child %s' % (item.get_attribute('class'))
|
||||
process_section(item)
|
||||
|
||||
elif tab_type == "xmodule_display xmodule_CapaModule":
|
||||
assert element.find_element_by_css_selector("section[id^='problem']") , "No problems found in %s" % (tab_type)
|
||||
p = element.find_element_by_css_selector("section[id^='problem']")
|
||||
p_id = p.get_attribute('id')
|
||||
process_problem(p, p_id)
|
||||
|
||||
elif tab_type == "xmodule_display xmodule_VideoModule":
|
||||
assert element.find_element_by_css_selector("section[class^='video']") , 'No video found in %s' % (tab_type)
|
||||
|
||||
elif tab_type == "xmodule_display xmodule_CustomTagModule":
|
||||
pass
|
||||
|
||||
else:
|
||||
assert False, "%s not recognized!!" % (tab_type)
|
||||
|
||||
|
||||
|
||||
def process_problem(element, problem_id):
|
||||
'''
|
||||
Process problem attempts to
|
||||
1) scan all the input fields and reset them
|
||||
2) click the 'check' button and look for an incorrect response (p.status text should be 'incorrect')
|
||||
3) click the 'show answer' button IF it exists and IF the answer is not already displayed
|
||||
4) enter the correct answer in each input box
|
||||
5) click the 'check' button and verify that answers are correct
|
||||
|
||||
Because of all the ajax calls happening, sometimes the test fails because objects disconnect from the DOM.
|
||||
The basic functionality does exist, though, and I'm hoping that someone can take it over and make it super effective.
|
||||
'''
|
||||
|
||||
prob_xmod = element.find_element_by_css_selector("section.problem")
|
||||
input_fields = prob_xmod.find_elements_by_css_selector("section[id^='textinput']")
|
||||
|
||||
## clear out all input to ensure an incorrect result
|
||||
for field in input_fields:
|
||||
box = field.find_element_by_css_selector("input")
|
||||
box.clear()
|
||||
print "\n I cleared out the box %s \n" % (box.get_attribute('id'))
|
||||
|
||||
## because of cookies or the application, only click the 'check' button if the status is not already 'incorrect'
|
||||
if prob_xmod.find_element_by_css_selector("p.status").text.lower() != 'incorrect':
|
||||
prob_xmod.find_element_by_css_selector("section.action input.check").click()
|
||||
world.browser.implicitly_wait(4)
|
||||
|
||||
## all elements become disconnected after the click
|
||||
element = world.browser.find_element_by_css_selector("section[id='"+problem_id+"']")
|
||||
prob_xmod = element.find_element_by_css_selector("section.problem")
|
||||
input_fields = prob_xmod.find_elements_by_css_selector("section[id^='textinput']")
|
||||
for field in input_fields:
|
||||
assert field.find_element_by_css_selector("div.incorrect") , "The 'check' button did not work for %s" % (problem_id)
|
||||
print "\n So far so good! \n"
|
||||
|
||||
|
||||
## wait for the ajax changes to render
|
||||
world.browser.implicitly_wait(4)
|
||||
|
||||
## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
|
||||
element = world.browser.find_element_by_css_selector("section[id='"+problem_id+"']")
|
||||
prob_xmod = element.find_element_by_css_selector("section.problem")
|
||||
|
||||
|
||||
show_button = element.find_element_by_css_selector("section.action input.show")
|
||||
## this logic is to ensure we do not accidentally hide the answers
|
||||
if show_button.get_attribute('value').lower() == 'show answer':
|
||||
show_button.click()
|
||||
print "\n I clicked show for %s \n" % (problem_id)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
## wait for the ajax changes to render
|
||||
world.browser.implicitly_wait(4)
|
||||
|
||||
## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
|
||||
element = world.browser.find_element_by_css_selector("section[id='"+problem_id+"']")
|
||||
prob_xmod = element.find_element_by_css_selector("section.problem")
|
||||
|
||||
## find all the input fields
|
||||
input_fields = prob_xmod.find_elements_by_css_selector("section[id^='textinput']")
|
||||
|
||||
## in each field, find the answer, and send it to the field.
|
||||
## Note that this does not work if the answer type is a strange format, e.g. "either a or b"
|
||||
for field in input_fields:
|
||||
field.find_element_by_css_selector("input").send_keys(field.find_element_by_css_selector("p[id^='answer']").text)
|
||||
print "\n \n Entered %s into %s \n \n" % (field.find_element_by_css_selector("p[id^='answer']").text,field.find_element_by_css_selector("input").get_attribute('id') )
|
||||
prob_xmod = element.find_element_by_css_selector("section.problem")
|
||||
prob_xmod.find_element_by_css_selector("section.action input.check").click()
|
||||
world.browser.implicitly_wait(4)
|
||||
|
||||
## assert that we entered the correct answers
|
||||
## we have to redefine input-fields because apparently they become detached from the dom after clicking 'check'
|
||||
|
||||
input_fields = world.browser.find_elements_by_css_selector("section[id='"+problem_id+"'] section[id^='textinput']")
|
||||
for field in input_fields:
|
||||
## if you don't use 'starts with ^=' the test will fail because the actual class is 'correct ' (with a space)
|
||||
assert world.browser.find_element_by_css_selector("div[class^='correct']"), "The check answer values were not correct for %s" % (problem_id)
|
||||
inputs = world.browser.find_elements_by_css_selector("section[id^='textinput'] input")
|
||||
for el in inputs:
|
||||
el.clear()
|
||||
print "\n checked answers for %s \n" % (problem_id)
|
||||
@@ -1,7 +1,8 @@
|
||||
from lettuce import world
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from courseware.courses import get_course_by_id
|
||||
from xmodule import seq_module
|
||||
from xmodule import seq_module, vertical_module
|
||||
|
||||
from logging import getLogger
|
||||
logger = getLogger(__name__)
|
||||
@@ -17,28 +18,28 @@ def get_courses():
|
||||
courses = sorted(courses, key=lambda course: course.number)
|
||||
return courses
|
||||
|
||||
def get_courseware(course_id):
|
||||
"""
|
||||
Given a course_id (string), return a courseware array of dictionaries for the
|
||||
top two levels of navigation. Example:
|
||||
# def get_courseware(course_id):
|
||||
# """
|
||||
# Given a course_id (string), return a courseware array of dictionaries for the
|
||||
# top two levels of navigation. Example:
|
||||
|
||||
[
|
||||
{'chapter_name': 'Overview',
|
||||
'sections': ['Welcome', 'System Usage Sequence', 'Lab0: Using the tools', 'Circuit Sandbox']
|
||||
},
|
||||
{'chapter_name': 'Week 1',
|
||||
'sections': ['Administrivia and Circuit Elements', 'Basic Circuit Analysis', 'Resistor Divider', 'Week 1 Tutorials']
|
||||
},
|
||||
{'chapter_name': 'Midterm Exam',
|
||||
'sections': ['Midterm Exam']
|
||||
}
|
||||
]
|
||||
"""
|
||||
# [
|
||||
# {'chapter_name': 'Overview',
|
||||
# 'sections': ['Welcome', 'System Usage Sequence', 'Lab0: Using the tools', 'Circuit Sandbox']
|
||||
# },
|
||||
# {'chapter_name': 'Week 1',
|
||||
# 'sections': ['Administrivia and Circuit Elements', 'Basic Circuit Analysis', 'Resistor Divider', 'Week 1 Tutorials']
|
||||
# },
|
||||
# {'chapter_name': 'Midterm Exam',
|
||||
# 'sections': ['Midterm Exam']
|
||||
# }
|
||||
# ]
|
||||
# """
|
||||
|
||||
course = get_course_by_id(course_id)
|
||||
chapters = course.get_children()
|
||||
courseware = [ {'chapter_name':c.display_name, 'sections':[s.display_name for s in c.get_children()]} for c in chapters]
|
||||
return courseware
|
||||
# course = get_course_by_id(course_id)
|
||||
# chapters = course.get_children()
|
||||
# courseware = [ {'chapter_name':c.display_name, 'sections':[s.display_name for s in c.get_children()]} for c in chapters]
|
||||
# return courseware
|
||||
|
||||
def get_courseware_with_tabs(course_id):
|
||||
"""
|
||||
@@ -101,7 +102,153 @@ def get_courseware_with_tabs(course_id):
|
||||
|
||||
course = get_course_by_id(course_id)
|
||||
chapters = [ chapter for chapter in course.get_children() if chapter.metadata.get('hide_from_toc','false').lower() != 'true' ]
|
||||
courseware = [{'chapter_name':c.display_name, 'sections':[{'section_name':s.display_name, 'clickable_tab_count': len(s.get_children()) if (type(s)==seq_module.SequenceDescriptor) else 0, 'tab_classes':[t.__class__.__name__ for t in s.get_children() ]} for s in c.get_children() if s.metadata.get('hide_from_toc', 'false').lower() != 'true']} for c in chapters ]
|
||||
courseware = [{'chapter_name':c.display_name,
|
||||
'sections':[{'section_name':s.display_name,
|
||||
'clickable_tab_count':len(s.get_children()) if (type(s)==seq_module.SequenceDescriptor) else 0,
|
||||
'tabs':[{'children_count':len(t.get_children()) if (type(t)==vertical_module.VerticalDescriptor) else 0,
|
||||
'class':t.__class__.__name__ }
|
||||
for t in s.get_children() ]}
|
||||
for s in c.get_children() if s.metadata.get('hide_from_toc', 'false').lower() != 'true']}
|
||||
for c in chapters ]
|
||||
|
||||
return courseware
|
||||
|
||||
def process_section(element, num_tabs=0):
|
||||
'''
|
||||
Process section reads through whatever is in 'course-content' and classifies it according to sequence module type.
|
||||
|
||||
This function is recursive
|
||||
|
||||
There are 6 types, with 6 actions.
|
||||
|
||||
Sequence Module
|
||||
-contains one child module
|
||||
|
||||
Vertical Module
|
||||
-contains other modules
|
||||
-process it and get its children, then process them
|
||||
|
||||
Capa Module
|
||||
-problem type, contains only one problem
|
||||
-for this, the most complex type, we created a separate method, process_problem
|
||||
|
||||
Video Module
|
||||
-video type, contains only one video
|
||||
-we only check to ensure that a section with class of video exists
|
||||
|
||||
HTML Module
|
||||
-html text
|
||||
-we do not check anything about it
|
||||
|
||||
Custom Tag Module
|
||||
-a custom 'hack' module type
|
||||
-there is a large variety of content that could go in a custom tag module, so we just pass if it is of this unusual type
|
||||
|
||||
can be used like this:
|
||||
e = world.browser.find_by_css('section.course-content section')
|
||||
process_section(e)
|
||||
|
||||
'''
|
||||
if element.has_class('xmodule_display xmodule_SequenceModule'):
|
||||
logger.debug('####### Processing xmodule_SequenceModule')
|
||||
child_modules = element.find_by_css("div>div>section[class^='xmodule']")
|
||||
for mod in child_modules:
|
||||
process_section(mod)
|
||||
|
||||
elif element.has_class('xmodule_display xmodule_VerticalModule'):
|
||||
logger.debug('####### Processing xmodule_VerticalModule')
|
||||
vert_list = element.find_by_css("li section[class^='xmodule']")
|
||||
for item in vert_list:
|
||||
process_section(item)
|
||||
|
||||
elif element.has_class('xmodule_display xmodule_CapaModule'):
|
||||
logger.debug('####### Processing xmodule_CapaModule')
|
||||
assert element.find_by_css("section[id^='problem']"), "No problems found in Capa Module"
|
||||
p = element.find_by_css("section[id^='problem']").first
|
||||
p_id = p['id']
|
||||
logger.debug('####################')
|
||||
logger.debug('id is "%s"' % p_id)
|
||||
logger.debug('####################')
|
||||
process_problem(p, p_id)
|
||||
|
||||
elif element.has_class('xmodule_display xmodule_VideoModule'):
|
||||
logger.debug('####### Processing xmodule_VideoModule')
|
||||
assert element.find_by_css("section[class^='video']"), "No video found in Video Module"
|
||||
|
||||
elif element.has_class('xmodule_display xmodule_HtmlModule'):
|
||||
logger.debug('####### Processing xmodule_HtmlModule')
|
||||
pass
|
||||
|
||||
elif element.has_class('xmodule_display xmodule_CustomTagModule'):
|
||||
logger.debug('####### Processing xmodule_CustomTagModule')
|
||||
pass
|
||||
|
||||
else:
|
||||
assert False, "Class for element not recognized!!"
|
||||
|
||||
|
||||
|
||||
def process_problem(element, problem_id):
|
||||
'''
|
||||
Process problem attempts to
|
||||
1) scan all the input fields and reset them
|
||||
2) click the 'check' button and look for an incorrect response (p.status text should be 'incorrect')
|
||||
3) click the 'show answer' button IF it exists and IF the answer is not already displayed
|
||||
4) enter the correct answer in each input box
|
||||
5) click the 'check' button and verify that answers are correct
|
||||
|
||||
Because of all the ajax calls happening, sometimes the test fails because objects disconnect from the DOM.
|
||||
The basic functionality does exist, though, and I'm hoping that someone can take it over and make it super effective.
|
||||
'''
|
||||
|
||||
prob_xmod = element.find_by_css("section.problem").first
|
||||
input_fields = prob_xmod.find_by_css("section[id^='input']")
|
||||
|
||||
## clear out all input to ensure an incorrect result
|
||||
for field in input_fields:
|
||||
field.find_by_css("input").first.fill('')
|
||||
|
||||
## because of cookies or the application, only click the 'check' button if the status is not already 'incorrect'
|
||||
# This would need to be reworked because multiple choice problems don't have this status
|
||||
# if prob_xmod.find_by_css("p.status").first.text.strip().lower() != 'incorrect':
|
||||
prob_xmod.find_by_css("section.action input.check").first.click()
|
||||
|
||||
## all elements become disconnected after the click
|
||||
## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
|
||||
# Wait for the ajax reload
|
||||
assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
|
||||
element = world.browser.find_by_css("section[id='%s']" % problem_id).first
|
||||
prob_xmod = element.find_by_css("section.problem").first
|
||||
input_fields = prob_xmod.find_by_css("section[id^='input']")
|
||||
for field in input_fields:
|
||||
assert field.find_by_css("div.incorrect"), "The 'check' button did not work for %s" % (problem_id)
|
||||
|
||||
show_button = element.find_by_css("section.action input.show").first
|
||||
## this logic is to ensure we do not accidentally hide the answers
|
||||
if show_button.value.lower() == 'show answer':
|
||||
show_button.click()
|
||||
else:
|
||||
pass
|
||||
|
||||
## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
|
||||
assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
|
||||
element = world.browser.find_by_css("section[id='%s']" % problem_id).first
|
||||
prob_xmod = element.find_by_css("section.problem").first
|
||||
input_fields = prob_xmod.find_by_css("section[id^='input']")
|
||||
|
||||
## in each field, find the answer, and send it to the field.
|
||||
## Note that this does not work if the answer type is a strange format, e.g. "either a or b"
|
||||
for field in input_fields:
|
||||
field.find_by_css("input").first.fill(field.find_by_css("p[id^='answer']").first.text)
|
||||
|
||||
prob_xmod.find_by_css("section.action input.check").first.click()
|
||||
|
||||
## assert that we entered the correct answers
|
||||
## grab element and prob_xmod because the dom has changed (some classes/elements became hidden and changed the hierarchy)
|
||||
assert world.browser.is_element_present_by_css("section[id='%s']" % problem_id, wait_time=5)
|
||||
element = world.browser.find_by_css("section[id='%s']" % problem_id).first
|
||||
prob_xmod = element.find_by_css("section.problem").first
|
||||
input_fields = prob_xmod.find_by_css("section[id^='input']")
|
||||
for field in input_fields:
|
||||
## if you don't use 'starts with ^=' the test will fail because the actual class is 'correct ' (with a space)
|
||||
assert field.find_by_css("div[class^='correct']"), "The check answer values were not correct for %s" % problem_id
|
||||
|
||||
@@ -74,7 +74,8 @@ def browse_course(course_id):
|
||||
rendered_sections = world.browser.find_by_css('#accordion > nav > div')[chapter_it].find_by_tag('li')
|
||||
num_rendered_sections = len(rendered_sections)
|
||||
|
||||
msg = '%d sections expected, %d sections found on page, %s - %d - %s' % (num_sections, num_rendered_sections, course_id, chapter_it, chapters[chapter_it]['chapter_name'])
|
||||
msg = ('%d sections expected, %d sections found on page, %s - %d - %s' %
|
||||
(num_sections, num_rendered_sections, course_id, chapter_it, chapters[chapter_it]['chapter_name']))
|
||||
logger.debug(msg)
|
||||
assert num_sections == num_rendered_sections, msg
|
||||
|
||||
@@ -104,10 +105,12 @@ def browse_course(course_id):
|
||||
rendered_tabs = 0
|
||||
num_rendered_tabs = 0
|
||||
|
||||
msg = '%d tabs expected, %d tabs found, %s - %d - %s' % (num_tabs, num_rendered_tabs, course_id, section_it, sections[section_it]['section_name'])
|
||||
msg = ('%d tabs expected, %d tabs found, %s - %d - %s' %
|
||||
(num_tabs, num_rendered_tabs, course_id, section_it, sections[section_it]['section_name']))
|
||||
logger.debug(msg)
|
||||
assert num_tabs == num_rendered_tabs, msg
|
||||
|
||||
tabs = sections[section_it]['tabs']
|
||||
tab_it = 0
|
||||
|
||||
## Iterate the tabs
|
||||
@@ -116,7 +119,17 @@ def browse_course(course_id):
|
||||
rendered_tabs[tab_it].find_by_tag('a').click()
|
||||
|
||||
## do something with the tab sections[section_it]
|
||||
check_for_errors()
|
||||
# e = world.browser.find_by_css('section.course-content section')
|
||||
# process_section(e)
|
||||
tab_children = tabs[tab_it]['children_count']
|
||||
tab_class = tabs[tab_it]['class']
|
||||
if tab_children != 0:
|
||||
rendered_items = world.browser.find_by_css('div#seq_content > section > ol > li > section')
|
||||
num_rendered_items = len(rendered_items)
|
||||
msg = ('%d items expected, %d items found, %s - %d - %s - tab %d' %
|
||||
(tab_children, num_rendered_items, course_id, section_it, sections[section_it]['section_name'], tab_it))
|
||||
logger.debug(msg)
|
||||
assert tab_children == num_rendered_items, msg
|
||||
|
||||
tab_it += 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user