Skip to content

Commit

Permalink
Simplify test infrastructure & update tests.
Browse files Browse the repository at this point in the history
Also includes Travis CI integration.
  • Loading branch information
mtyaka committed Mar 4, 2015
1 parent b0746b6 commit 3f71b0a
Show file tree
Hide file tree
Showing 17 changed files with 115 additions and 158 deletions.
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
language: python
python:
- "2.7"
before_install:
- "export DISPLAY=:99"
- "sh -e /etc/init.d/xvfb start"
install:
- "sh install_test_deps.sh"
- "pip uninstall -y xblock-drag-and-drop-v2"
- "python setup.py sdist"
- "pip install dist/xblock-drag-and-drop-v2-0.1.tar.gz"
script: python run_tests.py
notifications:
email: false
6 changes: 6 additions & 0 deletions install_test_deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Installs xblock-sdk and dependencies needed to run the tests suite.
# Run this script inside a fresh virtual environment.
pip install -e git://github.com/edx/xblock-sdk.git#egg=xblock-sdk
pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements.txt
pip install -r $VIRTUAL_ENV/src/xblock-sdk/test-requirements.txt
python setup.py develop
30 changes: 30 additions & 0 deletions run_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env python
"""
Run tests for the Drag and Drop V2 XBlock.
This script is required to run our selenium tests inside the xblock-sdk workbench
because the workbench SDK's settings file is not inside any python module.
"""

import os
import sys
import workbench

if __name__ == "__main__":
# Find the location of the XBlock SDK. Note: it must be installed in development mode.
# ('python setup.py develop' or 'pip install -e')
xblock_sdk_dir = os.path.dirname(os.path.dirname(workbench.__file__))
sys.path.append(xblock_sdk_dir)

# Use the workbench settings file:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
# Configure a range of ports in case the default port of 8081 is in use
os.environ.setdefault("DJANGO_LIVE_TEST_SERVER_ADDRESS", "localhost:8081-8099")

from django.core.management import execute_from_command_line
args = sys.argv[1:]
paths = [arg for arg in args if arg[0] != '-']
if not paths:
paths = ["tests/"]
options = [arg for arg in args if arg not in paths]
execute_from_command_line([sys.argv[0], "test"] + paths + options)
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ def package_data(pkg, root_list):
packages=['drag_and_drop_v2'],
install_requires=[
'XBlock',
'xblock-utils',
'ddt'
],
dependency_links = ['http://github.com/edx/xblock-utils/tarball/master#egg=xblock-utils'],
entry_points={
'xblock.v1': 'drag-and-drop-v2 = drag_and_drop_v2:DragAndDropBlock',
},
Expand Down
4 changes: 3 additions & 1 deletion tests/data/test_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@
"start": "Intro Feed",
"finish": "Final Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
}
4 changes: 3 additions & 1 deletion tests/data/test_get_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,7 @@
"feedback": {
"start": "Intro Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
}
4 changes: 3 additions & 1 deletion tests/data/test_get_html_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,7 @@
"feedback": {
"start": "Intro Feed"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
}
4 changes: 3 additions & 1 deletion tests/data/test_html_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@
"start": "Intro Feed",
"finish": "Final <b>Feed</b>"
},
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png"
"targetImg": "http://i0.kym-cdn.com/photos/images/newsfeed/000/030/404/1260585284155.png",
"title": "Drag and Drop",
"question_text": ""
}
40 changes: 8 additions & 32 deletions tests/integration/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,21 @@
from tests.utils import load_resource

from workbench import scenarios
from workbench.test.selenium_test import SeleniumTest

from xblockutils.base_test import SeleniumBaseTest

# Classes ###########################################################


class BaseIntegrationTest(SeleniumTest):
class BaseIntegrationTest(SeleniumBaseTest):
default_css_selector = 'section.xblock--drag-and-drop'
module_name = __name__

_additional_escapes = {
'"': "&quot;",
"'": "&apos;"
}

def setUp(self):
super(BaseIntegrationTest, self).setUp()

# Use test scenarios
self.browser.get(self.live_server_url) # Needed to load tests once
scenarios.SCENARIOS.clear()

# Suzy opens the browser to visit the workbench
self.browser.get(self.live_server_url)

# She knows it's the site by the header
header1 = self.browser.find_element_by_css_selector('h1')
self.assertEqual(header1.text, 'XBlock scenarios')

def _make_scenario_xml(self, display_name, question_text, completed):
return """
<vertical_demo>
Expand All @@ -48,34 +37,22 @@ def _add_scenario(self, identifier, title, xml):
self.addCleanup(scenarios.remove_scenario, identifier)

def _get_items(self):
items_container = self._page.find_element_by_css_selector('ul.items')
return items_container.find_elements_by_css_selector('li.option')
items_container = self._page.find_element_by_css_selector('.items')
return items_container.find_elements_by_css_selector('.option')

def _get_zones(self):
return self._page.find_elements_by_css_selector(".drag-container .zone")

def _get_feedback_message(self):
return self._page.find_element_by_css_selector(".feedback .message")

def go_to_page(self, page_name, css_selector='section.xblock--drag-and-drop'):
"""
Navigate to the page `page_name`, as listed on the workbench home
Returns the DOM element on the visited page located by the `css_selector`
"""
self.browser.get(self.live_server_url)
self.browser.find_element_by_link_text(page_name).click()
return self.browser.find_element_by_css_selector(css_selector)

def get_element_html(self, element):
return element.get_attribute('innerHTML').strip()

def get_element_classes(self, element):
return element.get_attribute('class').split()

def scroll_to(self, y):
self.browser.execute_script('window.scrollTo(0, {0})'.format(y))

def wait_until_contains_html(self, html, elem):
def wait_until_html_in(self, html, elem):
wait = WebDriverWait(elem, 2)
wait.until(lambda e: html in e.get_attribute('innerHTML'),
u"{} should be in {}".format(html, elem.get_attribute('innerHTML')))
Expand All @@ -84,4 +61,3 @@ def wait_until_has_class(self, class_name, elem):
wait = WebDriverWait(elem, 2)
wait.until(lambda e: class_name in e.get_attribute('class').split(),
u"Class name {} not in {}".format(class_name, elem.get_attribute('class')))

32 changes: 15 additions & 17 deletions tests/integration/test_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ def setUp(self):
scenario_xml = self._get_scenario_xml()
self._add_scenario(self.PAGE_ID, self.PAGE_TITLE, scenario_xml)

self.browser.get(self.live_server_url)
self._page = self.go_to_page(self.PAGE_TITLE)
# Resize window so that the entire drag container is visible.
# Selenium has issues when dragging to an area that is off screen.
self.browser.set_window_size(1024, 800)

def _get_item_by_value(self, item_value):
items_container = self._page.find_element_by_css_selector('ul.items')
return items_container.find_elements_by_xpath("//li[@data-value='{item_id}']".format(item_id=item_value))[0]
items_container = self._page.find_element_by_css_selector('.items')
return items_container.find_elements_by_xpath("//div[@data-value='{item_id}']".format(item_id=item_value))[0]

def _get_zone_by_id(self, zone_id):
zones_container = self._page.find_element_by_css_selector('div.target')
zones_container = self._page.find_element_by_css_selector('.target')
return zones_container.find_elements_by_xpath("//div[@data-zone='{zone_id}']".format(zone_id=zone_id))[0]

def _get_input_div_by_value(self, item_value):
Expand All @@ -68,19 +70,18 @@ def _send_input(self, item_value, value):
element.find_element_by_class_name('input').send_keys(value)
element.find_element_by_class_name('submit-input').click()


def drag_item_to_zone(self, item_value, zone_id):
element = self._get_item_by_value(item_value)
target = self._get_zone_by_id(zone_id)
action_chains = ActionChains(self.browser)
action_chains.drag_and_drop(element, target).perform()

def test_item_positive_feedback_on_good_move(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content")
for definition in self._get_correct_item_for_zone().values():
if not definition.input:
self.drag_item_to_zone(definition.item_id, definition.zone_id)
self.wait_until_contains_html(definition.feedback_positive, feedback_popup)
feedback_popup = self._page.find_element_by_css_selector(".popup-content")
self.wait_until_html_in(definition.feedback_positive, feedback_popup)

def test_item_positive_feedback_on_good_input(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content")
Expand All @@ -90,7 +91,7 @@ def test_item_positive_feedback_on_good_input(self):
self._send_input(definition.item_id, definition.input)
input_div = self._get_input_div_by_value(definition.item_id)
self.wait_until_has_class('correct', input_div)
self.wait_until_contains_html(definition.feedback_positive, feedback_popup)
self.wait_until_html_in(definition.feedback_positive, feedback_popup)

def test_item_negative_feedback_on_bad_move(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content")
Expand All @@ -100,7 +101,7 @@ def test_item_negative_feedback_on_bad_move(self):
if zone == definition.zone_id:
continue
self.drag_item_to_zone(definition.item_id, zone)
self.wait_until_contains_html(definition.feedback_negative, feedback_popup)
self.wait_until_html_in(definition.feedback_negative, feedback_popup)

def test_item_positive_feedback_on_bad_input(self):
feedback_popup = self._page.find_element_by_css_selector(".popup-content")
Expand All @@ -110,7 +111,7 @@ def test_item_positive_feedback_on_bad_input(self):
self._send_input(definition.item_id, '1999999')
input_div = self._get_input_div_by_value(definition.item_id)
self.wait_until_has_class('incorrect', input_div)
self.wait_until_contains_html(definition.feedback_negative, feedback_popup)
self.wait_until_html_in(definition.feedback_negative, feedback_popup)

def test_final_feedback_and_reset(self):
feedback_message = self._get_feedback_message()
Expand All @@ -128,16 +129,13 @@ def test_final_feedback_and_reset(self):
input_div = self._get_input_div_by_value(item_key)
self.wait_until_has_class('correct', input_div)

self.wait_until_contains_html(self.feedback['final'], feedback_message)
self.wait_until_exists('.reset-button')
self.wait_until_html_in(self.feedback['final'], self._get_feedback_message())

# scrolling to have `reset` visible, otherwise it does not receive a click
# this is due to xblock workbench header that consumes top 40px - selenium scrolls so page so that target
# element is a the very top.
self.scroll_to(100)
reset = self._page.find_element_by_css_selector(".reset-button")
reset = self._page.find_element_by_css_selector('.reset-button')
reset.click()

self.wait_until_contains_html(self.feedback['intro'], feedback_message)
self.wait_until_html_in(self.feedback['intro'], self._get_feedback_message())

locations_after_reset = get_locations()
for item_key in items.keys():
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ def test_zones(self):
def test_feedback(self):
feedback_message = self._get_feedback_message()

self.assertEqual(feedback_message.text, "Intro Feed")
self.assertEqual(feedback_message.text, "Intro Feed")
8 changes: 5 additions & 3 deletions tests/integration/test_title_and_question.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from nose_parameterized import parameterized
from ddt import ddt, unpack, data
from tests.integration.test_base import BaseIntegrationTest
from workbench import scenarios


@ddt
class TestDragAndDropTitleAndQuestion(BaseIntegrationTest):
@parameterized.expand([
@unpack
@data(
('plain1', 'title1', 'question1'),
('plain2', 'title2', 'question2'),
('html1', 'title with <i>HTML</i>', 'Question with <i>HTML</i>'),
('html2', '<span style="color:red">Title: HTML?</span>', '<span style="color:red">Span question</span>'),
])
)
def test_title_and_question_parameters(self, _, display_name, question_text):
const_page_name = 'Test block parameters'
const_page_id = 'test_block_title'
Expand Down
10 changes: 0 additions & 10 deletions tests/manage.py

This file was deleted.

27 changes: 0 additions & 27 deletions tests/requirements.txt

This file was deleted.

39 changes: 0 additions & 39 deletions tests/settings.py

This file was deleted.

Loading

0 comments on commit 3f71b0a

Please sign in to comment.