From f965b15845b8d7ad02dc09b03009d3f880d4d809 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Tue, 17 Sep 2013 10:32:43 -0400 Subject: [PATCH 1/5] LMS: resolves direct class name references to extends that have been converted to placeholder Sass syntax --- lms/templates/discussion/_thread_list_template.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/templates/discussion/_thread_list_template.html b/lms/templates/discussion/_thread_list_template.html index 9cceac0b24..8475387b74 100644 --- a/lms/templates/discussion/_thread_list_template.html +++ b/lms/templates/discussion/_thread_list_template.html @@ -4,13 +4,13 @@
- ${_("Discussion Home")} + ${_("Discussion Home")}
- ${_("Discussion Topics")} + ${_("Discussion Topics")} ${_("Show All Discussions")}
From c3a957b4d2c73e25604c82584956ea65275bf7be Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 17 Sep 2013 09:02:28 -0400 Subject: [PATCH 2/5] Improve retry logic for Stale Element Exceptions Better exception retry logic Change methods to lambdas --- .../contentstore/features/static-pages.py | 2 +- common/djangoapps/terrain/ui_helpers.py | 73 ++++++++++++------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/cms/djangoapps/contentstore/features/static-pages.py b/cms/djangoapps/contentstore/features/static-pages.py index 3e4054b9ad..b904347980 100644 --- a/cms/djangoapps/contentstore/features/static-pages.py +++ b/cms/djangoapps/contentstore/features/static-pages.py @@ -67,7 +67,7 @@ def get_index(name): page_name_css = 'section[data-type="HTMLModule"]' all_pages = world.css_find(page_name_css) for i in range(len(all_pages)): - if all_pages[i].html == '\n {name}\n'.format(name=name): + if world.retry_on_exception(lambda: all_pages[i].html) == '\n {name}\n'.format(name=name): return i return None diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py index 0ba2dfba18..e4e0626779 100644 --- a/common/djangoapps/terrain/ui_helpers.py +++ b/common/djangoapps/terrain/ui_helpers.py @@ -6,6 +6,7 @@ import time import platform from urllib import quote_plus from selenium.common.exceptions import WebDriverException, TimeoutException +from selenium.common.exceptions import StaleElementReferenceException from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait @@ -45,18 +46,25 @@ def css_has_text(css_selector, text, index=0): @world.absorb def wait_for(func, timeout=5): - WebDriverWait(world.browser.driver, timeout).until(func) + WebDriverWait( + driver=world.browser.driver, + timeout=timeout, + ignored_exceptions=(StaleElementReferenceException) + ).until(func) def wait_for_present(css_selector, timeout=30): """ Waiting for the element to be present in the DOM. - Throws an error if the wait_for time expires. + Throws an error if the WebDriverWait timeout clock expires. Otherwise this method will return None """ try: - WebDriverWait(driver=world.browser.driver, - timeout=60).until(EC.presence_of_element_located((By.CSS_SELECTOR, css_selector,))) + WebDriverWait( + driver=world.browser.driver, + timeout=timeout, + ignored_exceptions=(StaleElementReferenceException) + ).until(EC.presence_of_element_located((By.CSS_SELECTOR, css_selector,))) except TimeoutException: raise TimeoutException("Timed out waiting for {} to be present.".format(css_selector)) @@ -65,12 +73,15 @@ def wait_for_present(css_selector, timeout=30): def wait_for_visible(css_selector, timeout=30): """ Waiting for the element to be visible in the DOM. - Throws an error if the wait_for time expires. + Throws an error if the WebDriverWait timeout clock expires. Otherwise this method will return None """ try: - WebDriverWait(driver=world.browser.driver, - timeout=timeout).until(EC.visibility_of_element_located((By.CSS_SELECTOR, css_selector,))) + WebDriverWait( + driver=world.browser.driver, + timeout=timeout, + ignored_exceptions=(StaleElementReferenceException) + ).until(EC.visibility_of_element_located((By.CSS_SELECTOR, css_selector,))) except TimeoutException: raise TimeoutException("Timed out waiting for {} to be visible.".format(css_selector)) @@ -79,12 +90,15 @@ def wait_for_visible(css_selector, timeout=30): def wait_for_invisible(css_selector, timeout=30): """ Waiting for the element to be either invisible or not present on the DOM. - Throws an error if the wait_for time expires. + Throws an error if the WebDriverWait timeout clock expires. Otherwise this method will return None """ try: - WebDriverWait(driver=world.browser.driver, - timeout=timeout).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,))) + WebDriverWait( + driver=world.browser.driver, + timeout=timeout, + ignored_exceptions=(StaleElementReferenceException) + ).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, css_selector,))) except TimeoutException: raise TimeoutException("Timed out waiting for {} to be invisible.".format(css_selector)) @@ -93,14 +107,17 @@ def wait_for_invisible(css_selector, timeout=30): def wait_for_clickable(css_selector, timeout=30): """ Waiting for the element to be present and clickable. - Throws an error if the wait_for time expires. + Throws an error if the WebDriverWait timeout clock expires. Otherwise this method will return None. """ # Sometimes the element is clickable then gets obscured. # In this case, pause so that it is not reported clickable too early try: - WebDriverWait(world.browser.driver, - timeout=timeout).until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector,))) + WebDriverWait( + driver=world.browser.driver, + timeout=timeout, + ignored_exceptions=(StaleElementReferenceException) + ).until(EC.element_to_be_clickable((By.CSS_SELECTOR, css_selector,))) except TimeoutException: raise TimeoutException("Timed out waiting for {} to be clickable.".format(css_selector)) @@ -133,7 +150,7 @@ def css_click(css_selector, index=0, wait_time=30): # another element might be on top of it. In this case, try # clicking in the upper left corner. try: - return world.css_find(css_selector)[index].click() + return retry_on_exception(lambda: world.css_find(css_selector)[index].click()) except WebDriverException: return css_click_at(css_selector, index=index) @@ -177,19 +194,19 @@ def id_click(elem_id): @world.absorb def css_fill(css_selector, text, index=0): - css_find(css_selector)[index].fill(text) + retry_on_exception(lambda: css_find(css_selector)[index].fill(text)) @world.absorb def click_link(partial_text, index=0): - world.browser.find_link_by_partial_text(partial_text)[index].click() + retry_on_exception(lambda: world.browser.find_link_by_partial_text(partial_text)[index].click()) @world.absorb def css_text(css_selector, index=0, timeout=30): # Wait for the css selector to appear if is_css_present(css_selector): - return css_find(css_selector, wait_time=timeout)[index].text + return retry_on_exception(lambda: css_find(css_selector, wait_time=timeout)[index].text) else: return "" @@ -198,7 +215,7 @@ def css_text(css_selector, index=0, timeout=30): def css_value(css_selector, index=0): # Wait for the css selector to appear if is_css_present(css_selector): - return css_find(css_selector)[index].value + return retry_on_exception(lambda: css_find(css_selector)[index].value) else: return "" @@ -209,18 +226,18 @@ def css_html(css_selector, index=0): Returns the HTML of a css_selector """ assert is_css_present(css_selector) - return css_find(css_selector)[index].html + return retry_on_exception(lambda: css_find(css_selector)[index].html) @world.absorb def css_has_class(css_selector, class_name, index=0): - return css_find(css_selector)[index].has_class(class_name) + return retry_on_exception(lambda: css_find(css_selector)[index].has_class(class_name)) @world.absorb def css_visible(css_selector, index=0): assert is_css_present(css_selector) - return css_find(css_selector)[index].visible + return retry_on_exception(lambda: css_find(css_selector)[index].visible) @world.absorb @@ -277,15 +294,21 @@ def trigger_event(css_selector, event='change', index=0): @world.absorb -def retry_on_exception(func, max_attempts=5): +def retry_on_exception(func, max_attempts=5, ignored_exceptions=StaleElementReferenceException): + """ + Retry the interaction, ignoring the passed exceptions. + By default ignore StaleElementReferenceException, which happens often in our application + when the DOM is being manipulated by client side JS. + Note that ignored_exceptions is passed directly to the except block, and as such can be + either a single exception or multiple exceptions as a parenthesized tuple. + """ attempt = 0 while attempt < max_attempts: try: return func() break - except WebDriverException: + except ignored_exceptions: world.wait(1) attempt += 1 - except: - attempt += 1 + assert_true(attempt < max_attempts, 'Ran out of attempts to execute {}'.format(func)) From 6513ded95a900c14fa04eaae05b1d39a91c8c8e4 Mon Sep 17 00:00:00 2001 From: cahrens Date: Tue, 17 Sep 2013 12:55:33 -0400 Subject: [PATCH 3/5] Update changelong for due_date_display_format. --- CHANGELOG.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0ee8e6175a..3db747a883 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,10 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Studio/LMS: Added ability to set due date formatting through Studio's Advanced Settings. +The key is due_date_display_format, and the value should be a format supported by Python's +strftime function. + Common: Added configurable backends for tracking events. Tracking events using the python logging module is the default backend. Support for MongoDB and a Django database is also available. From 56a808c7aeff7b3257dffa21378de58c577b5fce Mon Sep 17 00:00:00 2001 From: Frances Botsford Date: Tue, 17 Sep 2013 13:57:13 -0400 Subject: [PATCH 4/5] a few missed placeholder conversions in verification sass --- lms/static/sass/views/_verification.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index 0f8c658e56..4aa5f88e8b 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -1661,13 +1661,13 @@ margin: 0 flex-gutter() 0 0; .title { - @extend .hd-lv4; + @extend %hd-lv4; margin-bottom: ($baseline/4); } .copy { - @extend .t-copy-sub1; - @extend .t-weight3; + @extend %t-copy-sub1; + @extend %t-weight3; } .list-actions { @@ -1675,7 +1675,7 @@ } .action-verify label { - @extend .t-copy-sub1; + @extend %t-copy-sub1; } } From 1d03f4aab222580b831fd4e1a491fde9a31c0ba3 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Wed, 18 Sep 2013 13:40:22 +0300 Subject: [PATCH 5/5] Revert "reformat for readability" This reverts commit acd23ad933664400fc6d81c67bfab168e4bad339. --- common/lib/xmodule/xmodule/lti_module.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py index a7ccb728d0..e2c4832251 100644 --- a/common/lib/xmodule/xmodule/lti_module.py +++ b/common/lib/xmodule/xmodule/lti_module.py @@ -227,17 +227,13 @@ class LTIModule(LTIFields, XModule): body=body, headers=headers) except ValueError: # scheme not in url - # Stubbing headers for now: + #https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136 + #Stubbing headers for now: headers = { u'Content-Type': u'application/x-www-form-urlencoded', - u'Authorization': u'oAuth ' # cont.. - u'oauth_nonce="80966668944732164491378916897", ' # cont.. - u'oauth_timestamp="1378916897", ' # cont.. - u'oauth_version="1.0", ' # cont.. - u'oauth_signature_method="HMAC-SHA1", ' # cont.. - u'oauth_consumer_key="", ' # cont.. - u'oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"', - } + u'Authorization': u'OAuth oauth_nonce="80966668944732164491378916897", \ +oauth_timestamp="1378916897", oauth_version="1.0", oauth_signature_method="HMAC-SHA1", \ +oauth_consumer_key="", oauth_signature="frVp4JuvT1mVXlxktiAUjQ7%2F1cw%3D"'} params = headers['Authorization'] # parse headers to pass to template as part of context: