Commit e01af341 by Omar Khan

Merge pull request #107 from open-craft/omar/report-download

Fix dashboard report image download
parents 594f1f28 0ef0a747
...@@ -4,7 +4,7 @@ machine: ...@@ -4,7 +4,7 @@ machine:
dependencies: dependencies:
override: override:
- "pip install -U pip wheel setuptools" - "pip install -U pip wheel setuptools"
- "pip install -e git://github.com/edx/xblock-sdk.git@22c1b2f173919bef22f2d9d9295ec5396d02dffd#egg=xblock-sdk" - "pip install -e git://github.com/edx/xblock-sdk.git@bddf9f4a2c6e4df28a411c8f632cc2250170ae9d#egg=xblock-sdk"
- "pip install -r requirements.txt" - "pip install -r requirements.txt"
- "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/base.txt" - "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/base.txt"
- "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/test.txt" - "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/test.txt"
......
...@@ -8,6 +8,18 @@ function ExportBase(runtime, element, initData) { ...@@ -8,6 +8,18 @@ function ExportBase(runtime, element, initData) {
var generateDataUriFromImageURL = function(imgURL) { var generateDataUriFromImageURL = function(imgURL) {
// Given the URL to an image, IF the image has already been cached by the browser, // Given the URL to an image, IF the image has already been cached by the browser,
// returns a data: URI with the contents of the image (image will be converted to PNG) // returns a data: URI with the contents of the image (image will be converted to PNG)
// Expand relative urls and urls without an explicit protocol into absolute urls
var a = document.createElement('a');
a.href = imgURL;
imgURL = a.href;
// If the image is from another domain, just return its URL. We can't
// create a data URL from cross-domain images:
// https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-todataurl
if (a.origin !== window.location.origin)
return imgURL;
var img = new Image(); var img = new Image();
img.src = imgURL; img.src = imgURL;
if (!img.complete) if (!img.complete)
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
# along with this program in a file in the toplevel directory called # along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>. # "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
# #
import json
from functools import wraps
from textwrap import dedent from textwrap import dedent
from mock import Mock, patch from mock import Mock, patch
from .base_test import ProblemBuilderBaseTest from .base_test import ProblemBuilderBaseTest
...@@ -52,6 +54,40 @@ class MockSubmissionsAPI(object): ...@@ -52,6 +54,40 @@ class MockSubmissionsAPI(object):
return [] return []
def check_dashboard_and_report(fixture, set_mentoring_values=True, **kwargs):
"""
Decorator for dashboard test methods.
- Sets up the given fixture
- Runs the decorated test
- Clicks the download link
- Opens the report
- Runs the decorated test again against the report
Any extra keyword arguments are passed to the dashboard XBlock.
"""
def wrapper(test):
@wraps(test)
def wrapped(test_case):
test_case._install_fixture(fixture)
if kwargs:
dashboard = test_case.vertical.get_child('test-scenario.pb-dashboard.d0.u0')
for key, value in kwargs.items():
setattr(dashboard, key, value)
dashboard.save()
if set_mentoring_values:
test_case._set_mentoring_values()
test_case.go_to_view('student_view')
test(test_case)
download_link = test_case.browser.find_element_by_css_selector('.report-download-link')
download_link.click()
test_case.browser.get(download_link.get_attribute('href'))
test_case.assertRegexpMatches(test_case.browser.current_url, r'^data:text/html;base64,')
test(test_case)
return wrapped
return wrapper
class TestDashboardBlock(ProblemBuilderBaseTest): class TestDashboardBlock(ProblemBuilderBaseTest):
""" """
Test the Student View of a dashboard XBlock linked to some problem builder blocks Test the Student View of a dashboard XBlock linked to some problem builder blocks
...@@ -75,6 +111,9 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -75,6 +111,9 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
/> />
""") """)
# Clean up screenshots if the tests pass
cleanup_on_success = True
def setUp(self): def setUp(self):
super(TestDashboardBlock, self).setUp() super(TestDashboardBlock, self).setUp()
...@@ -128,12 +167,21 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -128,12 +167,21 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
def _format_sr_text(self, visible_text): def _format_sr_text(self, visible_text):
return "Score: {value}".format(value=visible_text) return "Score: {value}".format(value=visible_text)
def _set_mentoring_values(self):
pbs = self.browser.find_elements_by_css_selector('.mentoring')
for pb in pbs:
mcqs = pb.find_elements_by_css_selector('fieldset.choices')
for idx, mcq in enumerate(mcqs):
choices = mcq.find_elements_by_css_selector('.choices .choice label')
choices[idx].click()
self.click_submit(pb)
@check_dashboard_and_report(SIMPLE_DASHBOARD, set_mentoring_values=False)
def test_empty_dashboard(self): def test_empty_dashboard(self):
""" """
Test that when the student has not submitted any question answers, we still see Test that when the student has not submitted any question answers, we still see
the dashboard, and its lists all the MCQ questions in the way we expect. the dashboard, and its lists all the MCQ questions in the way we expect.
""" """
self._install_fixture(self.SIMPLE_DASHBOARD)
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard') dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
step_headers = dashboard.find_elements_by_css_selector('thead') step_headers = dashboard.find_elements_by_css_selector('thead')
self.assertEqual(len(step_headers), 3) self.assertEqual(len(step_headers), 3)
...@@ -148,24 +196,11 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -148,24 +196,11 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
cell = mcq.find_element_by_css_selector('td:last-child') cell = mcq.find_element_by_css_selector('td:last-child')
self._assert_cell_contents(cell, '', 'No value yet') self._assert_cell_contents(cell, '', 'No value yet')
def _set_mentoring_values(self): @check_dashboard_and_report(SIMPLE_DASHBOARD)
pbs = self.browser.find_elements_by_css_selector('.mentoring')
for pb in pbs:
mcqs = pb.find_elements_by_css_selector('fieldset.choices')
for idx, mcq in enumerate(mcqs):
choices = mcq.find_elements_by_css_selector('.choices .choice label')
choices[idx].click()
self.click_submit(pb)
def test_dashboard(self): def test_dashboard(self):
""" """
Submit an answer to each MCQ, then check that the dashboard reflects those answers. Submit an answer to each MCQ, then check that the dashboard reflects those answers.
""" """
self._install_fixture(self.SIMPLE_DASHBOARD)
self._set_mentoring_values()
# Reload the page:
self.go_to_view("student_view")
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard') dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
headers = dashboard.find_elements_by_class_name('report-header') headers = dashboard.find_elements_by_class_name('report-header')
self.assertEqual(len(headers), 0) self.assertEqual(len(headers), 0)
...@@ -191,6 +226,52 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -191,6 +226,52 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
expected_average = {0: "2", 1: "3", 2: "1"}[step_num] expected_average = {0: "2", 1: "3", 2: "1"}[step_num]
self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average)) self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average))
@check_dashboard_and_report(SIMPLE_DASHBOARD, visual_rules=json.dumps({
'background': '/static/test/swoop-bg.png',
}))
def test_dashboard_image(self):
"""
Test that the dashboard image is displayed correctly, both on the page
and it the report. We allow minor differences here as the report
screenshot is not a perfect match.
"""
self.assertScreenshot('.pb-dashboard-visual svg', 'dashboard-image', threshold=100)
@check_dashboard_and_report(SIMPLE_DASHBOARD, visual_rules=json.dumps({
'background': ('//raw.githubusercontent.com/open-craft/problem-builder/omar/report-download/'
'problem_builder/static/test/swoop-bg.png'),
}))
def test_dashboard_image_cross_domain(self):
"""
Test that cross-domain dashboard images are displayed correctly. We
allow minor differences here as the report screenshot is not a perfect
match.
"""
self.assertScreenshot('.pb-dashboard-visual svg', 'dashboard-image', threshold=100)
@check_dashboard_and_report(
SIMPLE_DASHBOARD,
visual_rules=json.dumps({
'background': '/static/test/swoop-bg.png',
'images': [
'/static/test/swoop-step1.png',
'/static/test/swoop-step2.png',
],
}),
color_rules='\n'.join([
'0: grey',
'x <= 2: #aa2626',
'x <= 3: #e7ce76',
'#84b077',
]),
)
def test_dashboard_image_overlay(self):
"""
Test that image overlays are displayed correctly on the dashboard.
"""
self.assertScreenshot('.pb-dashboard-visual svg', 'dashboard-image-overlay', threshold=100)
@check_dashboard_and_report(ALTERNATIVE_DASHBOARD)
def test_dashboard_alternative(self): def test_dashboard_alternative(self):
""" """
Submit an answer to each MCQ, then check that the dashboard reflects those answers with alternative Submit an answer to each MCQ, then check that the dashboard reflects those answers with alternative
...@@ -200,11 +281,6 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -200,11 +281,6 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
* Numerical values are not shown * Numerical values are not shown
* Include HTML header and footer snippets * Include HTML header and footer snippets
""" """
self._install_fixture(self.ALTERNATIVE_DASHBOARD)
self._set_mentoring_values()
# Reload the page:
self.go_to_view("student_view")
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard') dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
header_p = dashboard.find_element_by_id('header-paragraph') header_p = dashboard.find_element_by_id('header-paragraph')
self.assertEquals(header_p.text, 'Header') self.assertEquals(header_p.text, 'Header')
...@@ -233,17 +309,13 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -233,17 +309,13 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
self.assertEqual(left_col.text, average_labels[step_num]) self.assertEqual(left_col.text, average_labels[step_num])
right_col = avg_row.find_element_by_css_selector('.value') right_col = avg_row.find_element_by_css_selector('.value')
expected_average = {0: "2", 1: "3", 2: "1"}[step_num] expected_average = {0: "2", 1: "3", 2: "1"}[step_num]
self._assert_cell_contents(right_col, '', self._format_sr_text(expected_average)) self._assert_cell_contents(right_col, '', self._format_sr_text(expected_average))
@check_dashboard_and_report(HIDE_QUESTIONS_DASHBOARD)
def test_dashboard_exclude_questions(self): def test_dashboard_exclude_questions(self):
""" """
Submit an answer to each MCQ, then check that the dashboard ignores questions it is configured to ignore Submit an answer to each MCQ, then check that the dashboard ignores questions it is configured to ignore
""" """
self._install_fixture(self.HIDE_QUESTIONS_DASHBOARD)
self._set_mentoring_values()
# Reload the page:
self.go_to_view("student_view")
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard') dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
steps = dashboard.find_elements_by_css_selector('tbody') steps = dashboard.find_elements_by_css_selector('tbody')
self.assertEqual(len(steps), 3) self.assertEqual(len(steps), 3)
...@@ -267,15 +339,11 @@ class TestDashboardBlock(ProblemBuilderBaseTest): ...@@ -267,15 +339,11 @@ class TestDashboardBlock(ProblemBuilderBaseTest):
expected_average = {0: "1", 1: "3", 2: "1"}[step_num] expected_average = {0: "1", 1: "3", 2: "1"}[step_num]
self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average)) self._assert_cell_contents(right_col, expected_average, self._format_sr_text(expected_average))
@check_dashboard_and_report(MALFORMED_HIDE_QUESTIONS_DASHBOARD)
def test_dashboard_malformed_exclude_questions(self): def test_dashboard_malformed_exclude_questions(self):
""" """
Submit an answer to each MCQ, then check that the dashboard ignores questions it is configured to ignore Submit an answer to each MCQ, then check that the dashboard ignores questions it is configured to ignore
""" """
self._install_fixture(self.MALFORMED_HIDE_QUESTIONS_DASHBOARD)
self._set_mentoring_values()
# Reload the page:
self.go_to_view("student_view")
dashboard = self.browser.find_element_by_css_selector('.pb-dashboard') dashboard = self.browser.find_element_by_css_selector('.pb-dashboard')
steps = dashboard.find_elements_by_css_selector('tbody') steps = dashboard.find_elements_by_css_selector('tbody')
self.assertEqual(len(steps), 3) self.assertEqual(len(steps), 3)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment