summaryrefslogtreecommitdiff
path: root/service/test/functional/features/steps
diff options
context:
space:
mode:
Diffstat (limited to 'service/test/functional/features/steps')
-rw-r--r--service/test/functional/features/steps/attachments.py55
-rw-r--r--service/test/functional/features/steps/common.py69
-rw-r--r--service/test/functional/features/steps/compose.py5
-rw-r--r--service/test/functional/features/steps/data_setup.py10
-rw-r--r--service/test/functional/features/steps/mail_list.py51
-rw-r--r--service/test/functional/features/steps/search.py9
-rw-r--r--service/test/functional/features/steps/tag_list.py31
7 files changed, 180 insertions, 50 deletions
diff --git a/service/test/functional/features/steps/attachments.py b/service/test/functional/features/steps/attachments.py
new file mode 100644
index 00000000..066683bf
--- /dev/null
+++ b/service/test/functional/features/steps/attachments.py
@@ -0,0 +1,55 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+from email.mime.application import MIMEApplication
+from time import sleep
+from leap.mail.mail import Message
+from common import *
+from test.support.integration import MailBuilder
+from behave import given
+from crochet import wait_for
+from uuid import uuid4
+from email.MIMEMultipart import MIMEMultipart
+from email.mime.text import MIMEText
+
+
+@given(u'I have a mail with an attachment in my inbox')
+def add_mail_with_attachment_impl(context):
+ subject = 'Hi! This the subject %s' % uuid4()
+ mail = build_mail_with_attachment(subject)
+ load_mail_into_soledad(context, mail)
+ context.last_subject = subject
+
+
+def build_mail_with_attachment(subject):
+ mail = MIMEMultipart()
+ mail['Subject'] = subject
+ mail.attach(MIMEText(u'a utf8 message', _charset='utf-8'))
+ attachment = MIMEApplication('pretend to be binary attachment data')
+ attachment.add_header('Content-Disposition', 'attachment', filename='filename.txt')
+ mail.attach(attachment)
+
+ return mail
+
+
+@wait_for(timeout=10.0)
+def load_mail_into_soledad(context, mail):
+ return context.client.mail_store.add_mail('INBOX', mail.as_string())
+
+
+@then(u'I see the mail has an attachment')
+def step_impl(context):
+ attachments_list = find_elements_by_css_selector(context, '.attachmentsArea li')
+ assert len(attachments_list) == 1
diff --git a/service/test/functional/features/steps/common.py b/service/test/functional/features/steps/common.py
index 93f4cb1f..9a547375 100644
--- a/service/test/functional/features/steps/common.py
+++ b/service/test/functional/features/steps/common.py
@@ -16,50 +16,77 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
-from selenium.common.exceptions import TimeoutException
-
+from selenium.common.exceptions import TimeoutException, StaleElementReferenceException
+import time
from test.support.integration import MailBuilder
+LOADING = 'loading'
+
+TIMEOUT_IN_S = 20
+
+DEFAULT_IMPLICIT_WAIT_TIMEOUT_IN_S = 10.0
+
+
+class ImplicitWait(object):
+ def __init__(self, context, timeout=5.0):
+ self._context = context
+ self._timeout = timeout
-def wait_until_element_is_invisible_by_locator(context, locator_tuple, timeout=10):
+ def __enter__(self):
+ self._context.browser.implicitly_wait(self._timeout)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self._context.browser.implicitly_wait(DEFAULT_IMPLICIT_WAIT_TIMEOUT_IN_S)
+
+
+def wait_until_element_is_invisible_by_locator(context, locator_tuple, timeout=TIMEOUT_IN_S):
wait = WebDriverWait(context.browser, timeout)
wait.until(EC.invisibility_of_element_located(locator_tuple))
-def wait_until_element_is_deleted(context, locator_tuple, timeout=10):
+def wait_until_element_is_deleted(context, locator_tuple, timeout=TIMEOUT_IN_S):
wait = WebDriverWait(context.browser, timeout)
wait.until(lambda s: len(s.find_elements(locator_tuple[0], locator_tuple[1])) == 0)
-def wait_for_user_alert_to_disapear(context, timeout=10):
+def wait_for_loading_to_finish(context, timeout=TIMEOUT_IN_S):
+ wait_until_element_is_invisible_by_locator(context, (By.ID, 'loading'), timeout)
+
+
+def wait_for_user_alert_to_disapear(context, timeout=TIMEOUT_IN_S):
wait_until_element_is_invisible_by_locator(context, (By.ID, 'user-alerts'), timeout)
-def wait_until_elements_are_visible_by_locator(context, locator_tuple, timeout=10):
+def wait_until_elements_are_visible_by_locator(context, locator_tuple, timeout=TIMEOUT_IN_S):
wait = WebDriverWait(context.browser, timeout)
wait.until(EC.presence_of_all_elements_located(locator_tuple))
return context.browser.find_elements(locator_tuple[0], locator_tuple[1])
-def wait_until_elements_are_visible_by_xpath(context, locator_tuple, timeout=10):
+def wait_until_elements_are_visible_by_xpath(context, locator_tuple, timeout=TIMEOUT_IN_S):
wait = WebDriverWait(context.browser, timeout)
wait.until(EC.presence_of_all_elements_located(locator_tuple))
return context.browser.find_elements(locator_tuple[0], locator_tuple[1])
-def wait_until_element_is_visible_by_locator(context, locator_tuple, timeout=10):
+def wait_until_element_is_visible_by_locator(context, locator_tuple, timeout=TIMEOUT_IN_S):
wait = WebDriverWait(context.browser, timeout)
wait.until(EC.visibility_of_element_located(locator_tuple))
return context.browser.find_element(locator_tuple[0], locator_tuple[1])
+def wait_for_condition(context, predicate_func, timeout=TIMEOUT_IN_S, poll_frequency=0.1):
+ wait = WebDriverWait(context.browser, timeout, poll_frequency=poll_frequency)
+ wait.until(predicate_func)
+
+
def fill_by_xpath(context, xpath, text):
field = context.browser.find_element_by_xpath(xpath)
field.send_keys(text)
def fill_by_css_selector(context, css_selector, text):
- field = context.browser.find_element_by_css_selector(css_selector)
+ field = find_element_by_css_selector(context, css_selector)
field.send_keys(text)
@@ -92,8 +119,12 @@ def find_element_by_css_selector(context, css_selector):
return wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, css_selector))
-def find_elements_by_css_selector(context, css_selector):
- return wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, css_selector))
+def find_element_by_class_name(context, class_name):
+ return wait_until_element_is_visible_by_locator(context, (By.CLASS_NAME, class_name))
+
+
+def find_elements_by_css_selector(context, css_selector, timeout=TIMEOUT_IN_S):
+ return wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, css_selector), timeout=timeout)
def find_elements_by_xpath(context, xpath):
@@ -109,17 +140,31 @@ def element_should_have_content(context, css_selector, content):
assert e.text == content
-def wait_until_button_is_visible(context, title, timeout=10):
+def wait_until_button_is_visible(context, title, timeout=TIMEOUT_IN_S):
wait = WebDriverWait(context.browser, timeout)
locator_tuple = (By.XPATH, ("//%s[contains(.,'%s')]" % ('button', title)))
wait.until(EC.visibility_of_element_located(locator_tuple))
+def execute_ignoring_staleness(func, timeout=TIMEOUT_IN_S):
+ end_time = time.time() + timeout
+ while time.time() <= end_time:
+ try:
+ return func()
+ except StaleElementReferenceException:
+ pass
+ raise TimeoutException('did not solve stale state until timeout %f' % timeout)
+
+
def click_button(context, title, element='button'):
button = find_element_containing_text(context, title, element_type=element)
button.click()
+def mail_list_with_subject_exists(context, subject):
+ return find_element_by_xpath(context, "//*[@class='subject-and-tags' and contains(.,'%s')]" % subject)
+
+
def mail_subject(context):
e = find_element_by_css_selector(context, '#mail-view .subject')
return e.text
diff --git a/service/test/functional/features/steps/compose.py b/service/test/functional/features/steps/compose.py
index 668579b1..e93468a2 100644
--- a/service/test/functional/features/steps/compose.py
+++ b/service/test/functional/features/steps/compose.py
@@ -49,9 +49,8 @@ def save_impl(context):
@when('I send it')
def send_impl(context):
- assert page_has_css(context, '#send-button[disabled]') is False
- context.browser.find_element(By.ID, 'send-button').click()
- # wait_until_element_is_deleted(context, (By.ID, 'send-button'), timeout=120)
+ send_button = wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, '#send-button:enabled'))
+ send_button.click()
def _enter_recipient(context, recipients_field, to_type):
diff --git a/service/test/functional/features/steps/data_setup.py b/service/test/functional/features/steps/data_setup.py
index 2a3876fc..fb825aba 100644
--- a/service/test/functional/features/steps/data_setup.py
+++ b/service/test/functional/features/steps/data_setup.py
@@ -13,11 +13,19 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+from uuid import uuid4
from test.support.integration import MailBuilder
from behave import given
+from common import wait_for_condition
@given('I have a mail in my inbox')
def add_mail_impl(context):
- input_mail = MailBuilder().build_input_mail()
+ subject = 'Hi! This the subject %s' % uuid4()
+
+ input_mail = MailBuilder().with_subject(subject).build_input_mail()
context.client.add_mail_to_inbox(input_mail)
+
+ wait_for_condition(context, lambda _: context.client.search_engine.search(subject)[1] > 0, poll_frequency=0.1)
+
+ context.last_subject = subject
diff --git a/service/test/functional/features/steps/mail_list.py b/service/test/functional/features/steps/mail_list.py
index 5f0a0116..1b850578 100644
--- a/service/test/functional/features/steps/mail_list.py
+++ b/service/test/functional/features/steps/mail_list.py
@@ -15,10 +15,10 @@
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
from common import *
from selenium.common.exceptions import NoSuchElementException
-from time import sleep
def find_current_mail(context):
+ print 'searching for mail [%s]' % context.current_mail_id
return find_element_by_id(context, '%s' % context.current_mail_id)
@@ -27,11 +27,14 @@ def check_current_mail_is_visible(context):
def open_current_mail(context):
- sleep(2)
e = find_current_mail(context)
e.click()
+def get_first_email(context):
+ return wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li span a'))[0]
+
+
@then('I see that mail under the \'{tag}\' tag')
def impl(context, tag):
context.execute_steps("when I select the tag '%s'" % tag)
@@ -40,16 +43,14 @@ def impl(context, tag):
@when('I open that mail')
def impl(context):
- sleep(3)
find_current_mail(context).click()
@when('I open the first mail in the mail list')
def impl(context):
- first_email = wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li span a'))[0]
- context.current_mail_id = 'mail-' + first_email.get_attribute('href').split('/')[-1]
- first_email.click()
- sleep(5)
+ # it seems page is often still loading so staleness exceptions happen often
+ context.current_mail_id = 'mail-' + execute_ignoring_staleness(lambda: get_first_email(context).get_attribute('href').split('/')[-1])
+ execute_ignoring_staleness(lambda: get_first_email(context).click())
@when('I open the first mail in the \'{tag}\'')
@@ -71,7 +72,7 @@ def impl(context):
@then('the deleted mail is there')
def impl(context):
- find_current_mail(context)
+ mail_list_with_subject_exists(context, context.last_subject)
@given('I have mails')
@@ -86,22 +87,35 @@ def impl(context):
for email in emails:
if 'status-read' not in email.get_attribute('class'):
+ context.current_mail_id = email.get_attribute('id') # we need to get the mail id before manipulating the page
email.find_element_by_tag_name('input').click()
find_element_by_id(context, 'mark-selected-as-read').click()
- context.current_mail_id = email.get_attribute('id')
break
- sleep(2)
- assert 'status-read' in context.browser.find_element_by_id(context.current_mail_id).get_attribute('class')
+ wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#%s.status-read' % context.current_mail_id))
@when('I delete the email')
def impl(context):
def last_email():
- return wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li'))[0]
- context.current_mail_id = last_email().get_attribute('id')
- last_email().find_element_by_tag_name('input').click()
+ return wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, '#mail-list li'))
+ mail = last_email()
+ context.current_mail_id = mail.get_attribute('id')
+ mail.find_element_by_tag_name('input').click()
find_element_by_id(context, 'delete-selected').click()
- assert context.current_mail_id != find_elements_by_css_selector(context, '#mail-list li span a')[0]
+ _wait_for_mail_list_to_be_empty(context)
+
+
+def _wait_for_mail_list_to_be_empty(context):
+ wait_for_loading_to_finish(context)
+
+ def mail_list_is_empty(_):
+ with ImplicitWait(context, timeout=0.1):
+ try:
+ return 0 == len(context.browser.find_elements_by_css_selector('#mail-list li'))
+ except TimeoutException:
+ return False
+
+ wait_for_condition(context, mail_list_is_empty)
@when('I check all emails')
@@ -116,9 +130,4 @@ def impl(context):
@then('I should not see any email')
def impl(context):
- try:
- context.browser.find_element(By.CSS_SELECTOR, '#mail-list li span a')
- except NoSuchElementException:
- assert True
- except:
- assert False
+ _wait_for_mail_list_to_be_empty(context)
diff --git a/service/test/functional/features/steps/search.py b/service/test/functional/features/steps/search.py
index e653c3ed..879e834d 100644
--- a/service/test/functional/features/steps/search.py
+++ b/service/test/functional/features/steps/search.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from time import sleep
+from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from common import *
@@ -22,9 +22,10 @@ from common import *
@when('I search for a mail with the words "{search_term}"')
def impl(context, search_term):
search_field = find_element_by_css_selector(context, '#search-trigger input[type="search"]')
- search_field.send_keys(search_term)
- search_field.send_keys(Keys.ENTER)
- sleep(1)
+ ActionChains(context.browser)\
+ .send_keys_to_element(search_field, search_term)\
+ .send_keys_to_element(search_field, Keys.ENTER)\
+ .perform()
@then('I see one or more mails in the search results')
diff --git a/service/test/functional/features/steps/tag_list.py b/service/test/functional/features/steps/tag_list.py
index 443c5173..a3315835 100644
--- a/service/test/functional/features/steps/tag_list.py
+++ b/service/test/functional/features/steps/tag_list.py
@@ -17,20 +17,20 @@ from common import *
def click_first_element_with_class(context, classname):
- elements = context.browser.find_elements_by_class_name(classname)
- elements[0].click()
+ element = find_element_by_class_name(context, classname)
+ element.click()
def is_side_nav_expanded(context):
- e = context.browser.find_elements_by_class_name('content')[0].get_attribute('class').count(u'move-right') == 1
- return e
+ e = find_element_by_class_name(context, 'content')
+ return u'move-right' in e.get_attribute("class")
def expand_side_nav(context):
if is_side_nav_expanded(context):
return
- toggle = context.browser.find_elements_by_class_name('side-nav-toggle')[0]
+ toggle = find_element_by_class_name(context, 'side-nav-toggle')
toggle.click()
@@ -39,11 +39,24 @@ def impl(context, tag):
wait_for_user_alert_to_disapear(context)
expand_side_nav(context)
- wait_until_element_is_visible_by_locator(context, (By.ID, 'tag-%s' % tag), 20)
+ # try this multiple times as there are some race conditions
+ try_again = 2
+ success = False
+ while (not success) and (try_again > 0):
+ try:
+ wait_until_element_is_visible_by_locator(context, (By.ID, 'tag-%s' % tag), timeout=20)
- e = find_element_by_id(context, 'tag-%s' % tag)
- e.click()
- wait_until_elements_are_visible_by_locator(context, (By.CSS_SELECTOR, "#mail-list li span a[href*='%s']" % tag))
+ e = find_element_by_id(context, 'tag-%s' % tag)
+ e.click()
+
+ wait_until_element_is_visible_by_locator(context, (By.CSS_SELECTOR, "#mail-list li span a[href*='%s']" % tag), timeout=20)
+ success = True
+ except TimeoutException:
+ pass
+ finally:
+ try_again -= 1
+
+ assert success
@when('I am in \'{tag}\'')