diff --git a/run_tests.py b/run_tests.py index c9e880748..0cf0bd5b6 100755 --- a/run_tests.py +++ b/run_tests.py @@ -18,7 +18,7 @@ sys.path.append(xblock_sdk_dir) # Use the workbench settings file: - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "workbench.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") diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py index ab71d1e8c..35e25610b 100644 --- a/tests/integration/test_base.py +++ b/tests/integration/test_base.py @@ -3,6 +3,7 @@ from xml.sax.saxutils import escape from selenium.webdriver.support.ui import WebDriverWait +from bok_choy.promise import EmptyPromise from workbench import scenarios from xblockutils.resources import ResourceLoader @@ -122,3 +123,14 @@ 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() diff --git a/tests/integration/test_interaction.py b/tests/integration/test_interaction.py index 2f1e96565..487bf769f 100644 --- a/tests/integration/test_interaction.py +++ b/tests/integration/test_interaction.py @@ -3,7 +3,7 @@ from ddt import ddt, data, unpack from mock import Mock, patch -from selenium.common.exceptions import NoSuchElementException +from selenium.common.exceptions import NoSuchElementException, WebDriverException from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait @@ -154,11 +154,11 @@ def move_item_to_zone(self, item_value, zone_id, action_key): Place item to descired zone using keybard interaction. zone_id=None means place item back into the item bank. """ - # Focus on the item: + # Focus on the item, then press the action key: item = self._get_item_by_value(item_value) - ActionChains(self.browser).move_to_element(item).perform() - # Press the action key: - item.send_keys(action_key) # Focus is on first *zone* now + item.send_keys("") + item.send_keys(action_key) + # Focus is on first *zone* now self.assert_grabbed_item(item) # Get desired zone and figure out how many times we have to press Tab to focus the zone. if zone_id is None: # moving back to the bank @@ -173,8 +173,9 @@ def move_item_to_zone(self, item_value, zone_id, action_key): # position of the zone (zero presses for first zone, one press for second zone, etc). tab_press_count = self._get_zone_position(zone_id) for _ in range(tab_press_count): - self._page.send_keys(Keys.TAB) + ActionChains(self.browser).send_keys(Keys.TAB).perform() zone.send_keys(action_key) + self.wait_for_ajax() def assert_grabbed_item(self, item): self.assertEqual(item.get_attribute('aria-grabbed'), 'true') @@ -324,10 +325,8 @@ def parameterized_cannot_move_items_between_zones(self, items_map, all_zones, sc # When using the keyboard, ensure that dropped items cannot get "grabbed". # Assert item has no tabindex. self.assertIsNone(item.get_attribute('tabindex')) - # Focus on the item: - ActionChains(self.browser).move_to_element(item).perform() - # Press the action key: - item.send_keys(action_key) + # Focus on the item, then press the action key: + ActionChains(self.browser).move_to_element(item).send_keys(action_key).perform() # Assert item is not grabbed. self.assertEqual(item.get_attribute('aria-grabbed'), 'false') else: @@ -417,7 +416,7 @@ def interact_with_keyboard_help(self, scroll_down=250, use_keyboard=False): self.assertTrue(dialog_modal_overlay.is_displayed()) self.assertTrue(dialog_modal.is_displayed()) - self._page.send_keys(Keys.ESCAPE) + ActionChains(self.browser).send_keys(Keys.ESCAPE).perform() self.assertFalse(dialog_modal_overlay.is_displayed()) self.assertFalse(dialog_modal.is_displayed()) @@ -497,6 +496,7 @@ def click_submit(self): self._wait_until_enabled(submit_button) submit_button.click() + self.wait_for_ajax() @ddt @@ -693,6 +693,7 @@ def test_multiple_positive_feedback(self): self.assertEqual(popup.get_attribute('class'), 'popup') self.assert_placed_item(item.item_id, item.zone_title[i]) reset.click() + self.wait_until_disabled(reset) def _get_scenario_xml(self): return self._get_custom_scenario_xml("data/test_multiple_options_data.json") @@ -773,11 +774,18 @@ class PreventSpaceBarScrollTest(DefaultDataTestMixin, InteractionTestBase, BaseI def get_scroll(self): return self.browser.execute_script('return $(window).scrollTop()') + def hit_spacebar(self): + """ Send a spacebar event to the page/browser """ + try: + self._page.send_keys(Keys.SPACE) # Firefox (chrome doesn't allow sending keys to non-focusable elements) + except WebDriverException: + ActionChains(self.browser).send_keys(Keys.SPACE).perform() # Chrome (Firefox types this into the URL bar) + def test_space_bar_scroll(self): # Window should not be scrolled at first. self.assertEqual(self.get_scroll(), 0) # Pressing space bar while no zone is focused should scroll the window down (default browser action). - self._page.send_keys(Keys.SPACE) + self.hit_spacebar() # Window should be scrolled down a bit. wait = WebDriverWait(self, 2) # While the XHR is in progress, a spinner icon is shown inside the item. diff --git a/tests/integration/test_sizing.py b/tests/integration/test_sizing.py index 25acc534e..f7af93bbc 100644 --- a/tests/integration/test_sizing.py +++ b/tests/integration/test_sizing.py @@ -111,9 +111,10 @@ def test_square_image_desktop(self): self._check_sizes(1, self.EXPECTATIONS, expected_img_width=500) def _size_for_mobile(self): - self.browser.set_window_size(375, 627) # iPhone 6 viewport size + width, height = 400, 627 # iPhone 6 viewport size is 375x627; this is the closest Chrome can get + self.browser.set_window_size(width, height) wait = WebDriverWait(self.browser, 2) - wait.until(lambda browser: browser.get_window_size()["width"] == 375) + wait.until(lambda browser: browser.get_window_size()["width"] == width) # Fix platform inconsistencies caused by scrollbar size: self.browser.execute_script('$("body").css("margin-right", "40px")') scrollbar_width = self.browser.execute_script( @@ -188,16 +189,20 @@ def _check_sizes(self, block_index, expectations, expected_img_width=None, is_de item_bank = self._page.find_element_by_css_selector('.item-bank') item_bank_width = item_bank.size["width"] item_bank_height = item_bank.size["height"] + page_width = self._page.size["width"] # self._page is the .xblock--drag-and-drop div if is_desktop: # If using a desktop-sized window, we can know the exact dimensions of various containers: - self.assertEqual(self._page.size["width"], 770) # self._page is the .xblock--drag-and-drop div - self.assertEqual(target_img_width, expected_img_width or 755) - self.assertEqual(item_bank_width, 755) + self.assertEqual(page_width, 770) # div has max-width: 770px else: - self.assertEqual(self._page.size["width"], 335) # self._page is the .xblock--drag-and-drop div - self.assertEqual(target_img_width, expected_img_width or 328) - self.assertEqual(item_bank_width, 328) + window_width = self.browser.get_window_size()["width"] + self.assertLessEqual(window_width, 400) + self.assertEqual(page_width, window_width - 40) + + # The item bank and other elements are inside a wrapper with 'padding: 1%', so we expect + # their width to be 98% of item_bank_width in general + self.assertAlmostEqual(target_img_width, expected_img_width or (page_width * 0.98), delta=1) + self.assertAlmostEqual(item_bank_width, page_width * 0.98, delta=1) # Test each element, before it is placed (while it is in the item bank). for expect in expectations: