diff --git a/requirements/test.in b/requirements/test.in
index 08aaab81f..26cc962f1 100644
--- a/requirements/test.in
+++ b/requirements/test.in
@@ -7,7 +7,6 @@ pytest-cov # pytest extension for code coverage statistics
pytest-django # pytest extension for better Django support
ddt # data-driven tests
-bok-choy # integration tests
selenium # integration tests
mock # required by the workbench
openedx-django-pyfs # required by the workbench
diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py
index 75a55358b..e1fb662a5 100644
--- a/tests/integration/test_base.py
+++ b/tests/integration/test_base.py
@@ -4,22 +4,17 @@
from __future__ import absolute_import
-import json
from collections import namedtuple
-from xml.sax.saxutils import escape
-from bok_choy.promise import EmptyPromise
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from six.moves import range
-from workbench import scenarios
-from xblock.utils.base_test import SeleniumBaseTest
from xblock.utils.resources import ResourceLoader
from drag_and_drop_v2.default_data import (BOTTOM_ZONE_ID, BOTTOM_ZONE_TITLE,
- DEFAULT_DATA, FINISH_FEEDBACK,
+ FINISH_FEEDBACK,
ITEM_ANY_ZONE_FEEDBACK,
ITEM_ANY_ZONE_NAME,
ITEM_BOTTOM_ZONE_NAME,
@@ -33,13 +28,11 @@
ITEM_TOP_ZONE_NAME, MIDDLE_ZONE_ID,
MIDDLE_ZONE_TITLE, START_FEEDBACK,
TOP_ZONE_ID, TOP_ZONE_TITLE)
-from drag_and_drop_v2.utils import Constants
# Globals ###########################################################
loader = ResourceLoader(__name__)
-
# Classes ###########################################################
ItemDefinition = namedtuple( # pylint: disable=invalid-name
@@ -56,179 +49,6 @@
)
-class BaseIntegrationTest(SeleniumBaseTest):
- default_css_selector = '.themed-xblock.xblock--drag-and-drop'
- module_name = __name__
-
- _additional_escapes = {
- '"': """,
- "'": "'"
- }
-
- # pylint: disable=too-many-arguments
- # pylint: disable=bad-continuation
- @classmethod
- def _make_scenario_xml(
- cls, display_name="Test DnDv2", show_title=True, problem_text="Question", completed=False,
- show_problem_header=True, max_items_per_zone=0, data=None, mode=Constants.STANDARD_MODE
- ):
- if not data:
- data = json.dumps(DEFAULT_DATA)
- return """
-
-
-
- """.format(
- display_name=escape(display_name),
- show_title=show_title,
- problem_text=escape(problem_text),
- show_problem_header=show_problem_header,
- completed=completed,
- max_items_per_zone=max_items_per_zone,
- mode=mode,
- data=escape(data, cls._additional_escapes)
- )
-
- def _get_custom_scenario_xml(self, filename):
- data = loader.load_unicode(filename)
- return "".format(
- data=escape(data, self._additional_escapes)
- )
-
- def _add_scenario(self, identifier, title, xml):
- scenarios.add_xml_scenario(identifier, title, xml)
- self.addCleanup(scenarios.remove_scenario, identifier)
-
- def _get_items(self):
- items_container = self._page.find_element_by_css_selector('.item-bank')
- 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_popup(self):
- return self._page.find_element_by_css_selector(".popup")
-
- def _get_popup_wrapper(self):
- return self._page.find_element_by_css_selector(".popup-wrapper")
-
- def _get_popup_content(self):
- return self._page.find_element_by_css_selector(".popup .popup-content")
-
- def _get_keyboard_help(self):
- return self._page.find_element_by_css_selector(".keyboard-help")
-
- def _get_keyboard_help_button(self):
- return self._page.find_element_by_css_selector(".keyboard-help-button")
-
- def _get_keyboard_help_dialog(self):
- return self._page.find_element_by_css_selector(".keyboard-help-dialog")
-
- def _get_go_to_beginning_button(self):
- return self._page.find_element_by_css_selector('.go-to-beginning-button')
-
- def _get_reset_button(self):
- return self._page.find_element_by_css_selector('.problem-action-button-wrapper .reset')
-
- def _get_show_answer_button(self):
- return self._page.find_element_by_css_selector('.problem-action-button-wrapper .show')
-
- def _get_submit_button(self):
- return self._page.find_element_by_css_selector('.submit-attempt-container .submit')
-
- def _get_attempts_info(self):
- return self._page.find_element_by_css_selector('.submission-feedback')
-
- def _get_feedback(self):
- return self._page.find_element_by_css_selector(".feedback-content")
-
- def _get_feedback_message(self):
- return self._page.find_element_by_css_selector(".feedback .message")
-
- def _get_explanation(self):
- return self._page.find_element_by_css_selector(".solution-span")
-
- def scroll_down(self, pixels=50):
- self.browser.execute_script("$(window).scrollTop({})".format(pixels))
-
- def is_element_in_viewport(self, element):
- """Determines if the element lies at least partially in the viewport."""
- viewport = self.browser.execute_script(
- "return {"
- "top: window.scrollY,"
- "left: window.scrollX,"
- "bottom: window.scrollY + window.outerHeight,"
- "right: window.scrollX + window.outerWidth"
- "};"
- )
-
- return all([
- any([
- viewport["top"] <= element.rect["y"] <= viewport["bottom"],
- viewport["top"] <= element.rect["y"] + element.rect["height"] <= viewport["bottom"]
- ]),
- any([
- viewport["left"] <= element.rect["x"] <= viewport["right"],
- viewport["left"] <= element.rect["x"] + element.rect["width"] <= viewport["right"]
- ])
- ])
-
- def _get_style(self, selector, style, computed=True):
- if computed:
- query = 'return getComputedStyle($("{selector}").get(0)).{style}'
- else:
- query = 'return $("{selector}").get(0).style.{style}'
- return self.browser.execute_script(query.format(selector=selector, style=style))
-
- def assertFocused(self, element):
- focused_element = self.browser.switch_to.active_element
- self.assertTrue(element == focused_element, 'expected element to have focus')
-
- def assertNotFocused(self, element):
- focused_element = self.browser.switch_to.active_element
- self.assertTrue(element != focused_element, 'expected element to not have focus')
-
- @staticmethod
- def get_element_html(element):
- return element.get_attribute('innerHTML').strip()
-
- @staticmethod
- def get_element_classes(element):
- return element.get_attribute('class').split()
-
- 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')))
-
- @staticmethod
- def wait_until_has_class(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')))
-
- def wait_for_ajax(self, timeout=15):
- """
- Wait for jQuery to be loaded and for all ajax requests to finish.
- Same as bok-choy's PageObject.wait_for_ajax()
- """
- def is_ajax_finished():
- """ Check if all the ajax calls on the current page have completed. """
- return self.browser.execute_script("return typeof(jQuery)!='undefined' && jQuery.active==0")
-
- EmptyPromise(is_ajax_finished, "Finished waiting for ajax requests.", timeout=timeout).fulfill()
-
-
class DefaultDataTestMixin(object):
"""
Provides a test scenario with default options.
diff --git a/tests/integration/test_studio.py b/tests/integration/test_studio.py
deleted file mode 100644
index b9ad65a57..000000000
--- a/tests/integration/test_studio.py
+++ /dev/null
@@ -1,289 +0,0 @@
-from __future__ import absolute_import
-
-import time
-
-import six
-from six.moves import range
-from xblock.utils.studio_editable_test import StudioEditableBaseTest
-
-
-class TestStudio(StudioEditableBaseTest):
- """
- Tests that cover the editing interface in the Studio.
- """
-
- def load_scenario(self, xml=''):
- self.set_scenario_xml(xml)
- self.element = self.go_to_view('studio_view')
- self.fix_js_environment()
-
- def click_continue(self):
- continue_button = self.element.find_element_by_css_selector('.continue-button')
- self.scroll_into_view(continue_button)
- continue_button.click()
-
- def scroll_into_view(self, element):
- """
- Scrolls to the element and places cursor above it.
- Useful when you want to click an element that is scrolled off
- the visible area of the screen.
- """
- # We have to use block: 'end' rather than the default 'start' because there's a fixed
- # title bar in the studio view in the workbench that can obstruct the element.
- script = "arguments[0].scrollIntoView({behavior: 'instant', block: 'end'})"
- self.browser.execute_script(script, element)
-
- @property
- def feedback_tab(self):
- return self.element.find_element_by_css_selector('.feedback-tab')
-
- @property
- def zones_tab(self):
- return self.element.find_element_by_css_selector('.zones-tab')
-
- @property
- def items_tab(self):
- return self.element.find_element_by_css_selector('.items-tab')
-
- @property
- def background_image_type_radio_buttons(self):
- radio_buttons = self.zones_tab.find_elements_by_css_selector('.background-image-type input[type="radio"]')
- self.assertEqual(len(radio_buttons), 2)
- self.assertEqual(radio_buttons[0].get_attribute('value'), 'manual')
- self.assertEqual(radio_buttons[1].get_attribute('value'), 'auto')
- return {'manual': radio_buttons[0], 'auto': radio_buttons[1]}
-
- @property
- def display_labels_checkbox(self):
- return self.zones_tab.find_element_by_css_selector('.display-labels')
-
- @property
- def background_image_url_field(self):
- return self.zones_tab.find_element_by_css_selector('.background-manual .background-url')
-
- @property
- def background_image_url_button(self):
- return self.zones_tab.find_element_by_css_selector('.background-manual button')
-
- @property
- def autozone_cols_field(self):
- return self.zones_tab.find_element_by_css_selector('.background-auto .autozone-layout-cols')
-
- @property
- def autozone_rows_field(self):
- return self.zones_tab.find_element_by_css_selector('.background-auto .autozone-layout-rows')
-
- @property
- def autozone_width_field(self):
- return self.zones_tab.find_element_by_css_selector('.background-auto .autozone-size-width')
-
- @property
- def autozone_height_field(self):
- return self.zones_tab.find_element_by_css_selector('.background-auto .autozone-size-height')
-
- @property
- def autozone_generate_button(self):
- return self.zones_tab.find_element_by_css_selector('.background-auto button')
-
- @property
- def target_preview_img(self):
- return self.zones_tab.find_element_by_css_selector('.target-img')
-
- @property
- def zones(self):
- return self.zones_tab.find_elements_by_css_selector('.zone-row')
-
- def go_to_view(self, view_name='student_view', student_id="student_1"):
- element = super().go_to_view(view_name, student_id)
- time.sleep(0.1) # This method is unreliable without a delay.
- return element
-
- def test_defaults(self):
- """
- Basic test to verify stepping through the editor steps and saving works.
- """
- self.load_scenario()
- # We start on the feedback tab.
- self.assertTrue(self.feedback_tab.is_displayed())
- self.assertFalse(self.zones_tab.is_displayed())
- self.assertFalse(self.items_tab.is_displayed())
- # Continue to the zones tab.
- self.click_continue()
- self.assertFalse(self.feedback_tab.is_displayed())
- self.assertTrue(self.zones_tab.is_displayed())
- self.assertFalse(self.items_tab.is_displayed())
- # And finally to the items tab.
- self.click_continue()
- self.assertFalse(self.feedback_tab.is_displayed())
- self.assertFalse(self.zones_tab.is_displayed())
- self.assertTrue(self.items_tab.is_displayed())
- # Save the block and expect success.
- self.click_save(expect_success=True)
-
- def test_custom_image(self):
- """"
- Verify user can provide a custom background image URL.
- """
- default_bg_img_src = 'http://localhost:8081/resource/drag-and-drop-v2/public/img/triangle.png'
-
- self.load_scenario()
- # Go to zones tab.
- self.click_continue()
- radio_buttons = self.background_image_type_radio_buttons
- # Manual mode should be selected by default.
- self.assertTrue(radio_buttons['manual'].is_selected())
- self.assertFalse(radio_buttons['auto'].is_selected())
- url_field = self.background_image_url_field
- self.assertEqual(url_field.get_attribute('value'), '')
- self.assertIn(
- default_bg_img_src.split('http://localhost:8081/')[1], self.target_preview_img.get_attribute('src')
- )
-
- custom_bg_img_src = '{}?my-custom-image=true'.format(self.target_preview_img.get_attribute('src'))
-
- url_field.send_keys(custom_bg_img_src)
- self.scroll_into_view(self.background_image_url_button)
- self.background_image_url_button.click()
- self.assertEqual(self.target_preview_img.get_attribute('src'), custom_bg_img_src)
- self.click_continue()
- self.click_save(expect_success=True)
-
- # Verify the custom image src was saved successfully.
- self.element = self.go_to_view('student_view')
- target_img = self.element.find_element_by_css_selector('.target-img')
- self.assertEqual(target_img.get_attribute('src'), custom_bg_img_src)
-
- # Verify the background image URL field is set to custom image src when we go back to studio view.
- self.element = self.go_to_view('studio_view')
- self.click_continue()
- self.assertEqual(self.background_image_url_field.get_attribute('value'), custom_bg_img_src)
-
- def _verify_autogenerated_zones(self, cols, rows, zone_width, zone_height, padding):
- zones = self.zones
- self.assertEqual(len(zones), rows * cols)
- for col in range(cols):
- for row in range(rows):
- idx = col + (row * cols)
- zone = zones[idx]
- expected_values = {
- 'zone-title': 'Zone {}'.format(idx + 1),
- 'zone-width': zone_width,
- 'zone-height': zone_height,
- 'zone-x': (zone_width * col) + (padding * (col + 1)),
- 'zone-y': (zone_height * row) + (padding * (row + 1)),
- }
- for name, expected_value in six.iteritems(expected_values):
- field = zone.find_element_by_css_selector('.' + name)
- self.assertEqual(field.get_attribute('value'), str(expected_value))
-
- def test_auto_generated_image(self):
- """
- Verify that background image and zones get generated successfully.
- """
- cols = 3
- rows = 2
- zone_width = 150
- zone_height = 100
- padding = 20
-
- self.load_scenario()
- # Go to zones tab.
- self.click_continue()
- radio_buttons = self.background_image_type_radio_buttons
- self.scroll_into_view(radio_buttons['auto'])
- radio_buttons['auto'].click()
- # Manual background controls should be hidden.
- self.assertFalse(self.background_image_url_field.is_displayed())
- self.assertFalse(self.background_image_url_button.is_displayed())
- # Display labels checkbox should be unchecked by default.
- self.assertFalse(self.display_labels_checkbox.is_selected())
- # Enter zone properties for automatic generation.
- self.autozone_cols_field.clear()
- self.autozone_cols_field.send_keys(cols)
- self.autozone_rows_field.clear()
- self.autozone_rows_field.send_keys(rows)
- self.autozone_width_field.clear()
- self.autozone_width_field.send_keys(zone_width)
- self.autozone_height_field.clear()
- self.autozone_height_field.send_keys(zone_height)
- # Click the generate button.
- self.scroll_into_view(self.autozone_generate_button)
- self.autozone_generate_button.click()
- # Verify generated data-uri was set successfully.
- generated_url = self.target_preview_img.get_attribute('src')
- self.assertTrue(generated_url.startswith('data:image/svg+xml;'))
- expected_width = (zone_width * cols) + (padding * (cols + 1))
- expected_height = (zone_height * rows) + (padding * (rows + 1))
- self.assertEqual(self.target_preview_img.get_attribute('naturalWidth'), str(expected_width))
- self.assertEqual(self.target_preview_img.get_attribute('naturalHeight'), str(expected_height))
- # Display labels checkbox should be automatically selected.
- self.assertTrue(self.display_labels_checkbox.is_selected())
- # Verify there are exactly 6 zones, and their properties are correct.
- self._verify_autogenerated_zones(cols, rows, zone_width, zone_height, padding)
-
- # Fill in zone descriptions to make the form valid (zone descriptions are required).
- for zone in self.zones:
- zone.find_element_by_css_selector('.zone-description').send_keys('Description')
-
- # Save the block.
- self.click_continue()
- self.click_save(expect_success=True)
-
- # Verify the custom image src was saved successfully.
- self.element = self.go_to_view('student_view')
- target_img = self.element.find_element_by_css_selector('.target-img')
- self.assertTrue(target_img.get_attribute('src').startswith('data:image/svg+xml'))
- self.assertEqual(target_img.get_attribute('naturalWidth'), str(expected_width))
- self.assertEqual(target_img.get_attribute('naturalHeight'), str(expected_height))
-
- # Verify the background image URL field is set to custom image src when we go back to studio view.
- self.element = self.go_to_view('studio_view')
- self.click_continue()
- radio_buttons = self.background_image_type_radio_buttons
- self.assertFalse(radio_buttons['manual'].is_selected())
- self.assertTrue(radio_buttons['auto'].is_selected())
- self.assertEqual(self.autozone_cols_field.get_attribute('value'), str(cols))
- self.assertEqual(self.autozone_rows_field.get_attribute('value'), str(rows))
- self.assertEqual(self.autozone_width_field.get_attribute('value'), str(zone_width))
- self.assertEqual(self.autozone_height_field.get_attribute('value'), str(zone_height))
-
- def test_autozone_parameter_validation(self):
- """
- Test that autozone parameters are verified to be valid.
- """
- self.load_scenario()
- # Go to zones tab.
- self.click_continue()
- radio_buttons = self.background_image_type_radio_buttons
- self.scroll_into_view(radio_buttons['auto'])
- radio_buttons['auto'].click()
- # All fields are valid initially.
- self.assertFalse('field-error' in self.autozone_cols_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_rows_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_width_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_height_field.get_attribute('class'))
- # Set two of the fields to invalid values.
- self.autozone_cols_field.clear()
- self.autozone_cols_field.send_keys('2.5')
- self.autozone_height_field.clear()
- self.autozone_height_field.send_keys('100A')
- # Try to generate the image.
- self.scroll_into_view(self.autozone_generate_button)
- self.autozone_generate_button.click()
- # The two bad fields should show errors.
- self.assertTrue('field-error' in self.autozone_cols_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_rows_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_width_field.get_attribute('class'))
- self.assertTrue('field-error' in self.autozone_height_field.get_attribute('class'))
- # Fix the faulty values.
- self.autozone_cols_field.clear()
- self.autozone_cols_field.send_keys('2')
- self.autozone_height_field.clear()
- self.autozone_height_field.send_keys('100')
- self.scroll_into_view(self.autozone_generate_button)
- self.autozone_generate_button.click()
- # All good now.
- self.assertFalse('field-error' in self.autozone_cols_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_rows_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_width_field.get_attribute('class'))
- self.assertFalse('field-error' in self.autozone_height_field.get_attribute('class'))