diff options
Diffstat (limited to 'service/test/functional/features')
10 files changed, 238 insertions, 54 deletions
diff --git a/service/test/functional/features/attachments.feature b/service/test/functional/features/attachments.feature new file mode 100644 index 00000000..19834a9d --- /dev/null +++ b/service/test/functional/features/attachments.feature @@ -0,0 +1,27 @@ +# +# 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/>. + +Feature: Attachments + As a user of Pixelated + I want to download attachments of mails I received + So that my peers are able to send me any kind of content, not just text + + Scenario: User opens a mail attachment + Given I have a mail with an attachment in my inbox + When I open the first mail in the 'inbox' + Then I see the mail has an attachment + #When I open click on the first attachment + #Then the browser downloaded a file diff --git a/service/test/functional/features/checkboxes_and_mailboxes.feature b/service/test/functional/features/checkboxes_and_mailboxes.feature index 47ea806d..09710040 100644 --- a/service/test/functional/features/checkboxes_and_mailboxes.feature +++ b/service/test/functional/features/checkboxes_and_mailboxes.feature @@ -21,7 +21,8 @@ Feature: Checkboxes Scenario: User has a list of emails in each mailboxes that needs to be managed Given I have a mail in my inbox - When I mark the first unread email as read + When I select the tag 'inbox' + And I mark the first unread email as read And I delete the email When I select the tag 'trash' Then the deleted mail is there diff --git a/service/test/functional/features/environment.py b/service/test/functional/features/environment.py index 437529b8..53b13047 100644 --- a/service/test/functional/features/environment.py +++ b/service/test/functional/features/environment.py @@ -16,25 +16,37 @@ import logging import uuid +from crochet import setup, wait_for +from leap.common.events.server import ensure_server +from twisted.internet import defer from test.support.dispatcher.proxy import Proxy from test.support.integration import AppTestClient from selenium import webdriver from pixelated.resources.features_resource import FeaturesResource +from steps.common import * + +setup() + + +@wait_for(timeout=5.0) +def start_app_test_client(client): + ensure_server() + return client.start_client() def before_all(context): logging.disable('INFO') client = AppTestClient() + start_app_test_client(client) + client.listenTCP() proxy = Proxy(proxy_port='8889', app_port='4567') FeaturesResource.DISABLED_FEATURES.append('autoRefresh') context.client = client context.call_to_terminate_proxy = proxy.run_on_a_thread() - context.call_to_terminate = client.run_on_a_thread(logfile='/tmp/behave-tests.log') def after_all(context): - context.call_to_terminate() context.call_to_terminate_proxy() @@ -42,7 +54,7 @@ def before_feature(context, feature): # context.browser = webdriver.Firefox() context.browser = webdriver.PhantomJS() context.browser.set_window_size(1280, 1024) - context.browser.implicitly_wait(10) + context.browser.implicitly_wait(DEFAULT_IMPLICIT_WAIT_TIMEOUT_IN_S) context.browser.set_page_load_timeout(60) # wait for data context.browser.get('http://localhost:8889/') @@ -57,6 +69,20 @@ def after_step(context, step): def after_feature(context, feature): context.browser.quit() + cleanup_all_mails(context) + context.last_mail = None + + +@wait_for(timeout=10.0) +def cleanup_all_mails(context): + @defer.inlineCallbacks + def _delete_all_mails(): + mails = yield context.client.mail_store.all_mails() + for mail in mails: + yield context.client.mail_store.delete_mail(mail.ident) + + return _delete_all_mails() + def save_source(context, filename='/tmp/source.html'): with open(filename, 'w') as out: 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}\'') |