diff --git a/lms/djangoapps/courseware/features/course-section-content.feature b/lms/djangoapps/courseware/features/course-section-content.feature deleted file mode 100644 index bf6a9d8929..0000000000 --- a/lms/djangoapps/courseware/features/course-section-content.feature +++ /dev/null @@ -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 diff --git a/lms/djangoapps/courseware/features/course-section-content.py b/lms/djangoapps/courseware/features/course-section-content.py deleted file mode 100644 index 5513ab9d0c..0000000000 --- a/lms/djangoapps/courseware/features/course-section-content.py +++ /dev/null @@ -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) diff --git a/lms/djangoapps/courseware/features/courses.py b/lms/djangoapps/courseware/features/courses.py index b4bd00c055..aecaa139ff 100644 --- a/lms/djangoapps/courseware/features/courses.py +++ b/lms/djangoapps/courseware/features/courses.py @@ -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 diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py index caaddec432..f6618a344c 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.py +++ b/lms/djangoapps/courseware/features/smart-accordion.py @@ -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