Commit ef636fe9 by Braden MacDonald

Fix flaky tests

parent 7b5bc7d5
...@@ -18,7 +18,7 @@ if __name__ == "__main__": ...@@ -18,7 +18,7 @@ if __name__ == "__main__":
sys.path.append(xblock_sdk_dir) sys.path.append(xblock_sdk_dir)
# Use the workbench settings file: # 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 # 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") os.environ.setdefault("DJANGO_LIVE_TEST_SERVER_ADDRESS", "localhost:8081-8099")
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from bok_choy.promise import EmptyPromise
from workbench import scenarios from workbench import scenarios
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
...@@ -122,3 +123,14 @@ class BaseIntegrationTest(SeleniumBaseTest): ...@@ -122,3 +123,14 @@ class BaseIntegrationTest(SeleniumBaseTest):
wait = WebDriverWait(elem, 2) wait = WebDriverWait(elem, 2)
wait.until(lambda e: class_name in e.get_attribute('class').split(), wait.until(lambda e: class_name in e.get_attribute('class').split(),
u"Class name {} not in {}".format(class_name, elem.get_attribute('class'))) 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()
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
from ddt import ddt, data, unpack from ddt import ddt, data, unpack
from mock import Mock, patch 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 import ActionChains
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
...@@ -135,6 +135,7 @@ class InteractionTestBase(object): ...@@ -135,6 +135,7 @@ class InteractionTestBase(object):
self.drag_item_to_zone(item_value, zone_id) self.drag_item_to_zone(item_value, zone_id)
else: else:
self.move_item_to_zone(item_value, zone_id, action_key) self.move_item_to_zone(item_value, zone_id, action_key)
self.wait_for_ajax()
def drag_item_to_zone(self, item_value, zone_id): def drag_item_to_zone(self, item_value, zone_id):
""" """
...@@ -154,11 +155,11 @@ class InteractionTestBase(object): ...@@ -154,11 +155,11 @@ class InteractionTestBase(object):
Place item to descired zone using keybard interaction. Place item to descired zone using keybard interaction.
zone_id=None means place item back into the item bank. 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) item = self._get_item_by_value(item_value)
ActionChains(self.browser).move_to_element(item).perform() item.send_keys("")
# Press the action key: item.send_keys(action_key)
item.send_keys(action_key) # Focus is on first *zone* now # Focus is on first *zone* now
self.assert_grabbed_item(item) self.assert_grabbed_item(item)
# Get desired zone and figure out how many times we have to press Tab to focus the zone. # 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 if zone_id is None: # moving back to the bank
...@@ -173,7 +174,7 @@ class InteractionTestBase(object): ...@@ -173,7 +174,7 @@ class InteractionTestBase(object):
# position of the zone (zero presses for first zone, one press for second zone, etc). # position of the zone (zero presses for first zone, one press for second zone, etc).
tab_press_count = self._get_zone_position(zone_id) tab_press_count = self._get_zone_position(zone_id)
for _ in range(tab_press_count): 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) zone.send_keys(action_key)
def assert_grabbed_item(self, item): def assert_grabbed_item(self, item):
...@@ -324,10 +325,8 @@ class InteractionTestBase(object): ...@@ -324,10 +325,8 @@ class InteractionTestBase(object):
# When using the keyboard, ensure that dropped items cannot get "grabbed". # When using the keyboard, ensure that dropped items cannot get "grabbed".
# Assert item has no tabindex. # Assert item has no tabindex.
self.assertIsNone(item.get_attribute('tabindex')) self.assertIsNone(item.get_attribute('tabindex'))
# Focus on the item: # Focus on the item, then press the action key:
ActionChains(self.browser).move_to_element(item).perform() ActionChains(self.browser).move_to_element(item).send_keys(action_key).perform()
# Press the action key:
item.send_keys(action_key)
# Assert item is not grabbed. # Assert item is not grabbed.
self.assertEqual(item.get_attribute('aria-grabbed'), 'false') self.assertEqual(item.get_attribute('aria-grabbed'), 'false')
else: else:
...@@ -417,7 +416,7 @@ class InteractionTestBase(object): ...@@ -417,7 +416,7 @@ class InteractionTestBase(object):
self.assertTrue(dialog_modal_overlay.is_displayed()) self.assertTrue(dialog_modal_overlay.is_displayed())
self.assertTrue(dialog_modal.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_overlay.is_displayed())
self.assertFalse(dialog_modal.is_displayed()) self.assertFalse(dialog_modal.is_displayed())
...@@ -497,6 +496,7 @@ class AssessmentTestMixin(object): ...@@ -497,6 +496,7 @@ class AssessmentTestMixin(object):
self._wait_until_enabled(submit_button) self._wait_until_enabled(submit_button)
submit_button.click() submit_button.click()
self.wait_for_ajax()
@ddt @ddt
...@@ -693,6 +693,7 @@ class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestB ...@@ -693,6 +693,7 @@ class MultipleValidOptionsInteractionTest(DefaultDataTestMixin, InteractionTestB
self.assertEqual(popup.get_attribute('class'), 'popup') self.assertEqual(popup.get_attribute('class'), 'popup')
self.assert_placed_item(item.item_id, item.zone_title[i]) self.assert_placed_item(item.item_id, item.zone_title[i])
reset.click() reset.click()
self.wait_until_disabled(reset)
def _get_scenario_xml(self): def _get_scenario_xml(self):
return self._get_custom_scenario_xml("data/test_multiple_options_data.json") return self._get_custom_scenario_xml("data/test_multiple_options_data.json")
...@@ -773,11 +774,18 @@ class PreventSpaceBarScrollTest(DefaultDataTestMixin, InteractionTestBase, BaseI ...@@ -773,11 +774,18 @@ class PreventSpaceBarScrollTest(DefaultDataTestMixin, InteractionTestBase, BaseI
def get_scroll(self): def get_scroll(self):
return self.browser.execute_script('return $(window).scrollTop()') 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): def test_space_bar_scroll(self):
# Window should not be scrolled at first. # Window should not be scrolled at first.
self.assertEqual(self.get_scroll(), 0) self.assertEqual(self.get_scroll(), 0)
# Pressing space bar while no zone is focused should scroll the window down (default browser action). # 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. # Window should be scrolled down a bit.
wait = WebDriverWait(self, 2) wait = WebDriverWait(self, 2)
# While the XHR is in progress, a spinner icon is shown inside the item. # While the XHR is in progress, a spinner icon is shown inside the item.
...@@ -984,3 +992,4 @@ class ZoneAlignInteractionTest(InteractionTestBase, BaseIntegrationTest): ...@@ -984,3 +992,4 @@ class ZoneAlignInteractionTest(InteractionTestBase, BaseIntegrationTest):
self.scroll_down(pixels=200) self.scroll_down(pixels=200)
reset.click() reset.click()
self.scroll_down(pixels=0) self.scroll_down(pixels=0)
self.wait_until_disabled(reset)
...@@ -111,9 +111,10 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest): ...@@ -111,9 +111,10 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest):
self._check_sizes(1, self.EXPECTATIONS, expected_img_width=500) self._check_sizes(1, self.EXPECTATIONS, expected_img_width=500)
def _size_for_mobile(self): 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 = 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: # Fix platform inconsistencies caused by scrollbar size:
self.browser.execute_script('$("body").css("margin-right", "40px")') self.browser.execute_script('$("body").css("margin-right", "40px")')
scrollbar_width = self.browser.execute_script( scrollbar_width = self.browser.execute_script(
...@@ -188,16 +189,20 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest): ...@@ -188,16 +189,20 @@ class SizingTests(InteractionTestBase, BaseIntegrationTest):
item_bank = self._page.find_element_by_css_selector('.item-bank') item_bank = self._page.find_element_by_css_selector('.item-bank')
item_bank_width = item_bank.size["width"] item_bank_width = item_bank.size["width"]
item_bank_height = item_bank.size["height"] 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 is_desktop:
# If using a desktop-sized window, we can know the exact dimensions of various containers: # 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(page_width, 770) # div has max-width: 770px
self.assertEqual(target_img_width, expected_img_width or 755)
self.assertEqual(item_bank_width, 755)
else: else:
self.assertEqual(self._page.size["width"], 335) # self._page is the .xblock--drag-and-drop div window_width = self.browser.get_window_size()["width"]
self.assertEqual(target_img_width, expected_img_width or 328) self.assertLessEqual(window_width, 400)
self.assertEqual(item_bank_width, 328) 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). # Test each element, before it is placed (while it is in the item bank).
for expect in expectations: for expect in expectations:
......
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