From c21dbe4cd25eb4e3c9d3096745d4152929f141e4 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Thu, 17 Jul 2014 11:47:23 -0400 Subject: [PATCH] Improvements to clicking and synchronization in Studio bok-choy tests. --- .../test/acceptance/pages/studio/container.py | 14 ++++- common/test/acceptance/pages/studio/utils.py | 54 ++++++++++++------- .../tests/test_studio_split_test.py | 3 ++ requirements/edx/github.txt | 2 +- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/common/test/acceptance/pages/studio/container.py b/common/test/acceptance/pages/studio/container.py index 9da474ee79..43f156f03a 100644 --- a/common/test/acceptance/pages/studio/container.py +++ b/common/test/acceptance/pages/studio/container.py @@ -103,10 +103,21 @@ class ContainerPage(PageObject): def delete(self, source_index): """ Delete the item with index source_index (based on vertical placement in page). + Only visible items are counted in the source_index. + The index of the first item is 0. """ + # Click the delete button click_css(self, 'a.delete-button', source_index, require_notification=False) + + # Wait for the warning prompt to appear + self.wait_for_element_visibility('#prompt-warning', 'Deletion warning prompt is visible') + + # Make sure the delete button is there + confirmation_button_css = '#prompt-warning a.button.action-primary' + self.wait_for_element_visibility(confirmation_button_css, 'Confirmation dialog button is visible') + # Click the confirmation dialog button - click_css(self, 'a.button.action-primary', 0) + click_css(self, confirmation_button_css, 0) def edit(self): """ @@ -125,6 +136,7 @@ class ContainerPage(PageObject): def add_missing_groups(self): """ Click the "add missing groups" link. + Note that this does an ajax call. """ self.q(css='.add-missing-groups-button').first.click() diff --git a/common/test/acceptance/pages/studio/utils.py b/common/test/acceptance/pages/studio/utils.py index bb675deb44..a3648f469d 100644 --- a/common/test/acceptance/pages/studio/utils.py +++ b/common/test/acceptance/pages/studio/utils.py @@ -1,22 +1,34 @@ """ Utility methods useful for Studio page tests. """ -from bok_choy.promise import Promise -from selenium.webdriver.common.action_chains import ActionChains +from bok_choy.promise import EmptyPromise def click_css(page, css, source_index=0, require_notification=True): """ Click the button/link with the given css and index on the specified page (subclass of PageObject). - Will only consider buttons with a non-zero size. + Will only consider elements that are displayed and have a height and width greater than zero. If require_notification is False (default value is True), the method will return immediately. Otherwise, it will wait for the "mini-notification" to appear and disappear. """ - buttons = page.q(css=css).filter(lambda el: el.size['width'] > 0) - target = buttons[source_index] - ActionChains(page.browser).click(target).release().perform() + def _is_visible(el): + # Only make the call to size once (instead of once for the height and once for the width) + # because otherwise you will trigger a extra query on a remote element. + return el.is_displayed() and all(size > 0 for size in el.size.itervalues()) + + # Disable jQuery animations for faster testing with more reliable synchronization + page.browser.execute_script("jQuery.fx.off = true;") + + # Click on the element in the browser + page.q(css=css).filter(lambda el: _is_visible(el)).nth(source_index).click() + + # Some buttons trigger ajax posts + # (e.g. .add-missing-groups-button as configured in split_test_author_view.js) + # so after you click anything wait for the ajax call to finish + page.wait_for_ajax() + if require_notification: wait_for_notification(page) @@ -26,15 +38,13 @@ def wait_for_notification(page): Waits for the "mini-notification" to appear and disappear on the given page (subclass of PageObject). """ def _is_saving(): - num_notifications = page.q(css='.wrapper-notification-mini.is-shown').present - return (num_notifications == 1, num_notifications) + return page.q(css='.wrapper-notification-mini.is-shown').present def _is_saving_done(): - num_notifications = page.q(css='.wrapper-notification-mini.is-hiding').present - return (num_notifications == 1, num_notifications) + return page.q(css='.wrapper-notification-mini.is-hiding').present - Promise(_is_saving, 'Notification should have been shown.', timeout=60).fulfill() - Promise(_is_saving_done, 'Notification should have been hidden.', timeout=60).fulfill() + EmptyPromise(_is_saving, 'Notification should have been shown.', timeout=60).fulfill() + EmptyPromise(_is_saving_done, 'Notification should have been hidden.', timeout=60).fulfill() def press_the_notification_button(page, name): @@ -66,18 +76,24 @@ def add_advanced_component(page, menu_index, name): menu_index specifies which instance of the menus should be used (based on vertical placement within the page). """ + # Click on the Advanced icon. click_css(page, 'a>span.large-advanced-icon', menu_index, require_notification=False) - # Sporadically, the advanced component was not getting created after the click_css call on the category (below). - # Try making sure that the menu of advanced components is visible before clicking (the HTML is always on the + # This does an animation to hide the first level of buttons + # and instead show the Advanced buttons that are available. + # We should be OK though because click_css turns off jQuery animations + + # Make sure that the menu of advanced components is visible before clicking (the HTML is always on the # page, but will have display none until the large-advanced-icon is clicked). - def is_advanced_components_showing(): - advanced_buttons = page.q(css=".new-component-advanced").filter(lambda el: el.size['width'] > 0) - return (len(advanced_buttons) == 1, len(advanced_buttons)) + page.wait_for_element_visibility('.new-component-advanced', 'Advanced component menu is visible') - Promise(is_advanced_components_showing, "Advanced component menu not showing").fulfill() + # Now click on the component to add it. + component_css = 'a[data-category={}]'.format(name) + page.wait_for_element_visibility(component_css, 'Advanced component {} is visible'.format(name)) - click_css(page, 'a[data-category={}]'.format(name)) + # Adding some components, e.g. the Discussion component, will make an ajax call + # but we should be OK because the click_css method is written to handle that. + click_css(page, component_css, 0) def type_in_codemirror(page, index, text, find_prefix="$"): diff --git a/common/test/acceptance/tests/test_studio_split_test.py b/common/test/acceptance/tests/test_studio_split_test.py index 7254bdec33..df62ac2236 100644 --- a/common/test/acceptance/tests/test_studio_split_test.py +++ b/common/test/acceptance/tests/test_studio_split_test.py @@ -169,6 +169,9 @@ class SplitTest(ContainerBase, SplitTestMixin): Test deleting an inactive group. """ container = self.create_poorly_configured_split_instance() + + # The inactive group is the 2nd group, but it is the first one + # with a visible delete button, so use index 0 container.delete(0) self.verify_groups(container, ['alpha'], [], verify_missing_groups_not_present=False) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 6addd8362e..6602bcefd1 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -24,7 +24,7 @@ -e git+https://github.com/edx/django-waffle.git@823a102e48#egg=django-waffle -e git+https://github.com/edx/event-tracking.git@0.1.0#egg=event-tracking -e git+https://github.com/edx/edx-analytics-api-client.git@0.1.0#egg=analytics-client --e git+https://github.com/edx/bok-choy.git@82b4e82d79b9d4c6d087ebbfa26ea23235728e62#egg=bok_choy +-e git+https://github.com/edx/bok-choy.git@821d3b0ac742c204a93d802a8732be024a0bce22#egg=bok_choy -e git+https://github.com/edx-solutions/django-splash.git@9965a53c269666a30bb4e2b3f6037c138aef2a55#egg=django-splash -e git+https://github.com/edx/acid-block.git@459aff7b63db8f2c5decd1755706c1a64fb4ebb1#egg=acid-xblock -e git+https://github.com/edx/edx-ora2.git@release-2014-07-21T12.20#egg=edx-ora2