diff -up evolution-3.13.4/tests/addressbook.feature.missing-tests evolution-3.13.4/tests/addressbook.feature --- evolution-3.13.4/tests/addressbook.feature.missing-tests 2014-07-30 11:21:47.031172903 +0200 +++ evolution-3.13.4/tests/addressbook.feature 2014-07-30 11:21:47.031172903 +0200 @@ -0,0 +1,101 @@ +Feature: Addressbook: File: Create contacts + + Background: + * Open Evolution and setup fake account + * Open "Contacts" section + * Select "Personal" addressbook + * Change categories view to "Any Category" + * Delete all contacts containing "Doe" + + @addressbook_contacts + Scenario: Create a simple contact + * Create a new contact + * Set "Full Name..." in contact editor to "John Doe" + * Save the contact + * Refresh addressbook + * Select "Doe, John" contact + * Open contact editor for selected contact + Then "Full Name..." property is set to "John Doe" + + @addressbook_contacts + Scenario: Create a new contact with data + * Create a new contact + * Set "Full Name..." in contact editor to "Jimmy Doe" + * Set "Nickname:" in contact editor to "Unknown" + * Set emails in contact editor to + | Field | Value | + | Work Email | jimmy.doe@company.com | + | Home Email | jimmy_doe_72@gmail.com | + | Other Email | jimmydoe72@yahoo.com | + | Other Email | xxjimmyxx@free_email.com | + * Tick "Wants to receive HTML mail" checkbox + * Set phones in contact editor to + | Field | Value | + | Assistant Phone | 123 | + | Business Phone | 234 | + | Business Fax | 345 | + | Callback Phone | 456 | + | Car Phone | 567 | + | Company Phone | 678 | + | Home Phone | 789 | + | Home Fax | 890 | + | ISDN | 123 | + | Mobile Phone | 234 | + | Other Phone | 345 | + | Other Fax | 456 | + | Pager | 567 | + | Primary Phone | 678 | + | Radio | 789 | + | Telex | 890 | + * Set IMs in contact editor to + | Field | Value | + | AIM | 123 | + | Jabber | 234 | + | Yahoo | 345 | + | Gadu-Gadu | 456 | + | MSN | 123 | + | ICQ | 234 | + | GroupWise | 345 | + | Skype | jimmy.doe | + | Twitter | @jimmydoe | + * Save the contact + * Refresh addressbook + * Select "Doe, Jimmy" contact + * Open contact editor for selected contact + Then "Nickname:" property is set to "Unknown" + And Emails are set to + | Field | Value | + | Work Email | jimmy.doe@company.com | + | Home Email | jimmy_doe_72@gmail.com | + | Other Email | jimmydoe72@yahoo.com | + | Other Email | xxjimmyxx@free_email.com | + And "Wants to receive HTML mail" checkbox is ticked + And Phones are set to + | Field | Value | + | Assistant Phone | 123 | + | Business Phone | 234 | + | Business Fax | 345 | + | Callback Phone | 456 | + | Car Phone | 567 | + | Company Phone | 678 | + | Home Phone | 789 | + | Home Fax | 890 | + | ISDN | 123 | + | Mobile Phone | 234 | + | Other Phone | 345 | + | Other Fax | 456 | + | Pager | 567 | + | Primary Phone | 678 | + | Radio | 789 | + | Telex | 890 | + And IMs are set to + | Field | Value | + | AIM | 123 | + | Jabber | 234 | + | Yahoo | 345 | + | Gadu-Gadu | 456 | + | MSN | 123 | + | ICQ | 234 | + | GroupWise | 345 | + | Skype | jimmy.doe | + | Twitter | @jimmydoe | diff -up evolution-3.13.4/tests/common_steps.py.missing-tests evolution-3.13.4/tests/common_steps.py --- evolution-3.13.4/tests/common_steps.py.missing-tests 2014-07-30 11:21:47.031172903 +0200 +++ evolution-3.13.4/tests/common_steps.py 2014-07-30 11:21:47.031172903 +0200 @@ -0,0 +1,238 @@ +# -*- coding: UTF-8 -*- +from dogtail.utils import isA11yEnabled, enableA11y +if isA11yEnabled() is False: + enableA11y(True) + +from time import time, sleep +from functools import wraps +from os import strerror, errno, system +from signal import signal, alarm, SIGALRM +from subprocess import Popen, PIPE +from behave import step +from gi.repository import GLib, Gio +import fcntl, os + +from dogtail.rawinput import keyCombo, absoluteMotion, pressKey +from dogtail.tree import root +from unittest import TestCase + + +# Create a dummy unittest class to have nice assertions +class dummy(TestCase): + def runTest(self): # pylint: disable=R0201 + assert True + + +def wait_until(my_lambda, element, timeout=30, period=0.25): + """ + This function keeps running lambda with specified params until the result is True + or timeout is reached + Sample usages: + * wait_until(lambda x: x.name != 'Loading...', context.app) + Pause until window title is not 'Loading...'. + Return False if window title is still 'Loading...' + Throw an exception if window doesn't exist after default timeout + + * wait_until(lambda element, expected: x.text == expected, element, ('Expected text')) + Wait until element text becomes the expected (passed to the lambda) + + """ + exception_thrown = None + mustend = int(time()) + timeout + while int(time()) < mustend: + try: + if my_lambda(element): + return True + except Exception as e: + # If lambda has thrown the exception we'll re-raise it later + # and forget about if lambda passes + exception_thrown = e + sleep(period) + if exception_thrown: + raise exception_thrown + else: + return False + + +class TimeoutError(Exception): + """ + Timeout exception class for limit_execution_time_to function + """ + pass + + +def limit_execution_time_to( + seconds=10, error_message=strerror(errno.ETIME)): + """ + Decorator to limit function execution to specified limit + """ + def decorator(func): + def _handle_timeout(signum, frame): + raise TimeoutError(error_message) + + def wrapper(*args, **kwargs): + signal(SIGALRM, _handle_timeout) + alarm(seconds) + try: + result = func(*args, **kwargs) + finally: + alarm(0) + return result + + return wraps(func)(wrapper) + + return decorator + + +class App(object): + """ + This class does all basic events with the app + """ + def __init__( + self, appName, shortcut='', a11yAppName=None, + forceKill=True, parameters='', recordVideo=False): + """ + Initialize object App + appName command to run the app + shortcut default quit shortcut + a11yAppName app's a11y name is different than binary + forceKill is the app supposed to be kill before/after test? + parameters has the app any params needed to start? (only for startViaCommand) + recordVideo start gnome-shell recording while running the app + """ + self.appCommand = appName + self.shortcut = shortcut + self.forceKill = forceKill + self.parameters = parameters + self.internCommand = self.appCommand.lower() + self.a11yAppName = a11yAppName + self.recordVideo = recordVideo + self.pid = None + + # a way of overcoming overview autospawn when mouse in 1,1 from start + pressKey('Esc') + absoluteMotion(100, 100, 2) + + # attempt to make a recording of the test + if self.recordVideo: + keyCombo('R') + + def isRunning(self): + """ + Is the app running? + """ + if self.a11yAppName is None: + self.a11yAppName = self.internCommand + + # Trap weird bus errors + for attempt in xrange(0, 30): + sleep(1) + try: + return self.a11yAppName in [x.name for x in root.applications()] + except GLib.GError: + continue + raise Exception("10 at-spi errors, seems that bus is blocked") + + def kill(self): + """ + Kill the app via 'killall' + """ + if self.recordVideo: + keyCombo('R') + + try: + self.process.kill() + except: + # Fall back to killall + Popen("killall " + self.appCommand, shell=True).wait() + + def startViaCommand(self): + """ + Start the app via command + """ + if self.forceKill and self.isRunning(): + self.kill() + assert not self.isRunning(), "Application cannot be stopped" + + #command = "%s %s" % (self.appCommand, self.parameters) + #self.pid = run(command, timeout=5) + self.process = Popen(self.appCommand.split() + self.parameters.split(), + stdout=PIPE, stderr=PIPE, bufsize=0) + self.pid = self.process.pid + + assert self.isRunning(), "Application failed to start" + return root.application(self.a11yAppName) + + def closeViaShortcut(self): + """ + Close the app via shortcut + """ + if not self.isRunning(): + raise Exception("App is not running") + + keyCombo(self.shortcut) + assert not self.isRunning(), "Application cannot be stopped" + + +@step(u'Start a new Evolution instance') +def start_new_evolution_instance(context): + context.app = context.app_class.startViaCommand() + + +def cleanup(): + # Remove cached data and settings + folders = ['~/.local/share/evolution', '~/.cache/evolution', '~/.config/evolution'] + for folder in folders: + system("rm -rf %s > /dev/null" % folder) + + # Clean up goa data + system("rm -rf ~/.config/goa-1.0/accounts.conf") + system("killall goa-daemon 2&> /dev/null") + + # Reset GSettings + schemas = [x for x in Gio.Settings.list_schemas() if 'evolution' in x.lower()] + for schema in schemas: + system("gsettings reset-recursively %s" % schema) + + # Skip warning dialog + system("gsettings set org.gnome.evolution.shell skip-warning-dialog true") + # Show switcher buttons as icons (to minimize tree scrolling) + system("gsettings set org.gnome.evolution.shell buttons-style icons") + # Skip default mailer handler dialog + system("gsettings set org.gnome.evolution.mail prompt-check-if-default-mailer false") + + +def check_for_errors(context): + """Check that no error is displayed on Evolution UI""" + # Don't try to check for errors on dead app + if not context.app or context.app.dead: + return + alerts = context.app.findChildren(lambda x: x.roleName == 'alert') + if not alerts: + # alerts can also return None + return + alerts = filter(lambda x: x.showing, alerts) + if len(alerts) > 0: + labels = alerts[0].findChildren(lambda x: x.roleName == 'label') + messages = [x.name for x in labels] + + if alerts[0].name != 'Error' and alerts[0].showing: + # Erase the configuration and start all over again + system("evolution --force-shutdown &> /dev/null") + + # Remove previous data + folders = ['~/.local/share/evolution', '~/.cache/evolution', '~/.config/evolution'] + for folder in folders: + system("rm -rf %s > /dev/null" % folder) + + raise RuntimeError("Error occurred: %s" % messages) + + +def non_block_read(output): + fd = output.fileno() + fl = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) + try: + return output.read() + except: + return "" diff -up evolution-3.13.4/tests/environment.py.missing-tests evolution-3.13.4/tests/environment.py --- evolution-3.13.4/tests/environment.py.missing-tests 2014-07-30 11:21:47.031172903 +0200 +++ evolution-3.13.4/tests/environment.py 2014-07-30 11:21:47.031172903 +0200 @@ -0,0 +1,82 @@ +# -*- coding: UTF-8 -*- + +from time import sleep, localtime, strftime +from dogtail.utils import isA11yEnabled, enableA11y +if not isA11yEnabled(): + enableA11y(True) + +from common_steps import App, dummy, cleanup, non_block_read +from dogtail.config import config +import os + + +def before_all(context): + """Setup evolution stuff + Being executed once before any test + """ + + try: + # Close running evo instances + os.system("evolution --force-shutdown > /dev/null") + + # Skip dogtail actions to print to stdout + config.logDebugToStdOut = False + config.typingDelay = 0.2 + + # Include assertion object + context.assertion = dummy() + + # Cleanup existing data before any test + cleanup() + + # Store scenario start time for session logs + context.log_start_time = strftime("%Y-%m-%d %H:%M:%S", localtime()) + + context.app_class = App('evolution') + + except Exception as e: + print("Error in before_all: %s" % e.message) + + +def after_step(context, step): + try: + if step.status == 'failed' and hasattr(context, "embed"): + # Embed screenshot if HTML report is used + os.system("dbus-send --print-reply --session --type=method_call " + + "--dest='org.gnome.Shell.Screenshot' " + + "'/org/gnome/Shell/Screenshot' " + + "org.gnome.Shell.Screenshot.Screenshot " + + "boolean:true boolean:false string:/tmp/screenshot.png") + context.embed('image/png', open("/tmp/screenshot.png", 'r').read()) + except Exception as e: + print("Error in after_step: %s" % str(e)) + + +def after_scenario(context, scenario): + """Teardown for each scenario + Kill evolution (in order to make this reliable we send sigkill) + """ + try: + # Attach journalctl logs + if hasattr(context, "embed"): + os.system("journalctl /usr/bin/gnome-session --no-pager -o cat --since='%s'> /tmp/journal-session.log" % context.log_start_time) + data = open("/tmp/journal-session.log", 'r').read() + if data: + context.embed('text/plain', data) + + context.app_class.kill() + + stdout = non_block_read(context.app_class.process.stdout) + stderr = non_block_read(context.app_class.process.stderr) + + if stdout: + context.embed('text/plain', stdout) + + if stderr: + context.embed('text/plain', stderr) + + # Make some pause after scenario + sleep(1) + except Exception as e: + # Stupid behave simply crashes in case exception has occurred + print("Error in after_scenario: %s" % e.message) diff -up evolution-3.13.4/tests/shortcuts.feature.missing-tests evolution-3.13.4/tests/shortcuts.feature --- evolution-3.13.4/tests/shortcuts.feature.missing-tests 2014-07-30 11:21:47.032172902 +0200 +++ evolution-3.13.4/tests/shortcuts.feature 2014-07-30 11:21:47.031172903 +0200 @@ -0,0 +1,135 @@ +Feature: Shortcuts + + Background: + * Open Evolution and setup fake account + + @general_shortcuts + Scenario: Ctrl-Q to quit application - two instances + * Start a new Evolution instance + * Press "Q" + Then Evolution is closed + + @general_shortcuts + Scenario: F1 to launch help + * Press "" + Then Help section "Evolution Mail and Calendar" is displayed + + @general_shortcuts + Scenario: Shift-Ctrl-W to open a new window + * Press "W" + Then Evolution has 2 windows opened + + @general_shortcuts + Scenario: Ctrl-W to close a window + * Press "W" + * Press "W" + Then Evolution has 1 window opened + + @general_shortcuts + Scenario: Ctrl-Shift-S to open Preferences + * Press "S" + Then Preferences dialog is opened + + @mail_shortcuts + Scenario: Mail: Ctrl-Shift-M to compose new message + * Open "Mail" section + * Press "M" + Then Message composer with title "Compose Message" is opened + + @contacts_shortcuts + Scenario: Contacts: Ctrl-Shift-C to create new contact + * Open "Contacts" section + * Press "C" + Then Contact editor window is opened + + @contacts_shortcuts + Scenario: Contacts: Ctrl-Shift-L to create new contact list + * Open "Contacts" section + * Press "L" + Then Contact List editor window is opened + + @calendar_shortcuts + Scenario: Calendar: Ctrl-Shift-A to create new appointment + * Open "Calendar" section + * Press "A" + Then Event editor with title "Appointment - No Summary" is displayed + + @calendar_shortcuts + Scenario: Calendar: Ctrl-Shift-E to create new meeting + * Open "Calendar" section + * Press "E" + Then Event editor with title "Meeting - No Summary" is displayed + + @calendar_shortcuts + Scenario: Tasks: Ctrl-Shift-T to create new task + * Open "Tasks" section + * Press "T" + Then Task editor with title "Task - No Summary" is opened + + @memos_shortcuts + Scenario: Memos: Ctrl-Shift-O to create new memo + * Open "Memos" section + * Press "O" + Then Memo editor with title "Memo - No Summary" is opened + + @memos_shortcuts + Scenario: Memos: Ctrl-Shift-O to create new task + * Open "Memos" section + * Press "O" + Then Shared memo editor with title "Memo - No Summary" is opened + + @view_shortcuts + Scenario Outline: Ctrl+<1-5> to switch views + * Press "" + Then "
" view is opened + + Examples: + | shortcut | section | + | 1 | Mail | + | 2 | Contacts | + | 3 | Calendar | + | 4 | Tasks | + | 5 | Memos | + + @menu_shortcuts + Scenario Outline: Menu shortcuts on all views + * Open "
" section + * Press "" + Then "" menu is opened + + Examples: + | section | shortcut | menu | + | Mail | F | File | + | Mail | E | Edit | + | Mail | V | View | + | Mail | O | Folder | + | Mail | M | Message | + | Mail | S | Search | + | Mail | H | Help | + + | Contacts | F | File | + | Contacts | E | Edit | + | Contacts | V | View | + | Contacts | A | Actions | + | Contacts | S | Search | + | Contacts | H | Help | + + | Calendar | F | File | + | Calendar | E | Edit | + | Calendar | V | View | + | Calendar | A | Actions | + | Calendar | S | Search | + | Calendar | H | Help | + + | Tasks | F | File | + | Tasks | E | Edit | + | Tasks | V | View | + | Tasks | A | Actions | + | Tasks | S | Search | + | Tasks | H | Help | + + | Memos | F | File | + | Memos | E | Edit | + | Memos | V | View | + | Memos | S | Search | + | Memos | H | Help | diff -up evolution-3.13.4/tests/steps/addressbook_steps.py.missing-tests evolution-3.13.4/tests/steps/addressbook_steps.py --- evolution-3.13.4/tests/steps/addressbook_steps.py.missing-tests 2014-07-30 11:21:47.032172902 +0200 +++ evolution-3.13.4/tests/steps/addressbook_steps.py 2014-07-30 11:21:47.032172902 +0200 @@ -0,0 +1,391 @@ +# -*- coding: UTF-8 -*- +from behave import step, then +from common_steps import wait_until +from dogtail.predicate import GenericPredicate +from dogtail.rawinput import keyCombo +from time import time, sleep +from gi.repository import GLib +import pyatspi + + +@step(u'Select "{name}" addressbook') +def select_addressbook(context, name, password=None): + cells = context.app.findChildren( + GenericPredicate(name=name, roleName='table cell')) + visible_cells = filter(lambda x: x.showing, cells) + if visible_cells == []: + raise RuntimeError("Cannot find addressbook '%s'" % name) + visible_cells[0].click() + # Wait for addressbook to load + try: + spinner = context.app.findChild( + GenericPredicate(name='Spinner'), retry=False, requireResult=False) + if spinner: + start_time = time() + while spinner.showing: + sleep(1) + if (time() - start_time) > 180: + raise RuntimeError("Contacts take too long to synchronize") + except (GLib.GError, TypeError): + pass + + +@step(u'Change categories view to "{category}"') +def change_categories_view(context, category): + labels = context.app.findChildren( + lambda x: x.labeller.name == 'Show:' and x.showing) + if labels == []: + raise RuntimeError("Cannot find category switcher") + labels[0].combovalue = category + + +@step(u'Delete selected contact') +def delete_selected_contact(context): + context.app.menu('Edit').click() + mnu = context.app.menu('Edit').menuItem("Delete Contact") + if pyatspi.STATE_ENABLED in mnu.getState().getStates(): + context.app.menu('Edit').menuItem("Delete Contact").click() + + alert = context.app.child(roleName='alert', name='Question') + alert.button('Delete').click() + context.execute_steps(u"* Wait for email to synchronize") + + +@step(u'Delete all contacts containing "{part}"') +def delete_all_contacts_containing(context, part): + context.app.search_bar.grab_focus() + for attempts in range(0, 10): + try: + context.app.search_bar.text = part + break + except (GLib.GError, AttributeError): + sleep(0.1) + continue + keyCombo("") + context.execute_steps(u"* Wait for email to synchronize") + context.app.search_bar.grab_focus() + keyCombo("") + sleep(3) + heading = context.app.findChild( + GenericPredicate(roleName='heading'), + retry=False, requireResult=False) + if heading: + keyCombo("a") + context.execute_steps(u"* Delete selected contact") + sleep(3) + + +@step(u'Create a new contact') +def create_a_new_contact(context): + context.app.menu('File').click() + context.app.menu('File').menu('New').point() + context.app.menu('File').menu('New').menuItem("Contact").click() + context.execute_steps(u"Then Contact editor window is opened") + + +def get_element_by_name(contact_editor, name, section=None): + """Get a field object by name in section (if specified)""" + element = None + if section: + panel = contact_editor.findChild( + GenericPredicate(roleName='panel', name=section), retry=False, requireResult=False) + if not panel: + # Other section is not a panel, but a toggle button + panel = contact_editor.child(roleName='toggle button', name=section) + element = panel.childLabelled(name) + else: + label = contact_editor.findChild( + GenericPredicate(label=name), retry=False, requireResult=False) + if not label: + # In case childLabelled is missing + # Find a filler with this name and get its text child + element = contact_editor.child( + roleName='filler', name=name).child(roleName='text') + else: + element = contact_editor.childLabelled(name) + if element: + return element + else: + raise RuntimeError("Cannot find element named '%s' in section '%s'" % ( + name, section)) + + +@step(u'Set "{field_name}" in contact editor to "{field_value}"') +def set_field_to_value(context, field_name, field_value): + element = get_element_by_name(context.app.contact_editor, field_name) + if element.roleName == "text": + element.text = field_value + elif element.roleName == "combo box": + if element.combovalue != field_value: + element.combovalue = field_value + + +@step(u'Save the contact') +def save_contact(context): + context.app.contact_editor.button('Save').click() + assert wait_until(lambda x: not x.showing, context.app.contact_editor),\ + "Contact Editor was not hidden" + assert wait_until(lambda x: x.dead, context.app.contact_editor),\ + "Contact Editor was not closed" + context.app.contact_editor = None + + +@step(u'Refresh addressbook') +def refresh_addressbook(context): + #Clear the search + icons = context.app.search_bar.findChildren(lambda x: x.roleName == 'icon') + if icons != []: + icons[-1].click() + else: + for attempts in range(0, 10): + try: + context.app.search_bar.text = '' + break + except (GLib.GError, AttributeError): + sleep(0.1) + continue + context.app.search_bar.grab_focus() + keyCombo('') + context.execute_steps(u"* Wait for email to synchronize") + + +@step(u'Select "{contact_name}" contact list') +@step(u'Select "{contact_name}" contact') +def select_contact_with_name(context, contact_name): + # heading shows the name of currently selected contact + # We have to keep on pressing Tab to select the next contact + # Until we meet the first contact + # WARNING - what if we will have two identical contacts? + fail = False + selected_contact = None + + # HACK + # To make the contact table appear + # we need to focus on search window + # and send Tabs to have the first contact focused + context.app.search_bar.grab_focus() + sleep(0.1) + # Switch to 'Any field contains' (not reachable in 3.6) + icons = context.app.search_bar.findChildren(GenericPredicate(roleName='icon')) + + if icons != []: + icons[0].click() + wait_until(lambda x: x.findChildren( + GenericPredicate(roleName='check menu item', name='Any field contains')) != [], + context.app) + context.app.menuItem('Any field contains').click() + for attempts in range(0, 10): + try: + context.app.search_bar.text = contact_name + break + except (GLib.GError, AttributeError): + sleep(0.1) + continue + keyCombo("") + context.app.search_bar.grab_focus() + + keyCombo("") + first_contact_name = context.app.child(roleName='heading').text + + while True: + selected_contact = context.app.child(roleName='heading') + if selected_contact.text == contact_name: + fail = False + break + keyCombo("") + # Wait until contact data is being rendered + sleep(1) + if first_contact_name == selected_contact.text: + fail = True + break + + context.assertion.assertFalse( + fail, "Can't find contact named '%s'" % contact_name) + context.selected_contact_text = selected_contact.text + + +@step(u'Open contact editor for selected contact') +def open_contact_editor_for_selected_contact(context): + context.app.menu('File').click() + context.app.menu('File').menuItem('Open Contact').click() + context.execute_steps(u""" + Then Contact editor window with title "Contact Editor - %s" is opened + """ % context.selected_contact_text) + + +@then(u'"{field}" property is set to "{expected}"') +def property_in_contact_window_is_set_to(context, field, expected): + element = get_element_by_name(context.app.contact_editor, field) + actual = None + if element.roleName == "text": + actual = element.text + elif element.roleName == "combo box": + actual = element.combovalue + if actual == '': + actual = element.textentry('').text + assert unicode(actual) == expected, "Incorrect value" + + +def get_combobox_textbox_object(contact_editor, section, scroll_to_bottom=True): + """Get a list of paired 'combobox-textbox' objects in contact editor""" + section_names = { + 'Ims': 'Instant Messaging', + 'Phones': 'Telephone', + 'Emails': 'Email'} + section = section_names[section.capitalize()] + lbl = contact_editor.child(roleName='label', name=section) + panel = lbl.findAncestor(GenericPredicate(roleName='panel')) + textboxes = panel.findChildren(GenericPredicate(roleName='text')) + + # Scroll to the bottom of the page if needed + pagetab = panel.findAncestor(GenericPredicate(roleName='page tab')) + for scroll in pagetab.findChildren(lambda x: x.roleName == 'scroll bar'): + if scroll_to_bottom: + scroll.value = scroll.maxValue + else: + scroll.value = 0 + + # Expand section if button exists + button = panel.findChild( + GenericPredicate(roleName='push button', name=section), + retry=False, requireResult=False) + # Expand button if any of textboxes is not visible + if button and (False in [x.showing for x in textboxes]): + button.click() + + comboboxes = panel.findChildren(GenericPredicate(roleName='combo box')) + + # Rearrange comboboxes and textboxes according to their position + result = [] + for combo in comboboxes: + combo_row = combo.position[1] + matching_textboxes = [ + x for x in textboxes + if ((x.position[1] - combo_row) == 0) and (x.position[0] > combo.position[0])] + if (matching_textboxes != []): + correct_textbox = min(matching_textboxes, key=lambda x: x.position[0]) + result.append((combo, correct_textbox)) + + comboboxes = [x[0] for x in result][::-1] + textboxes = [x[1] for x in result][::-1] + + return (textboxes, comboboxes, button) + + +@step(u'Set {section} in contact editor to') +def set_contact_emails_to_value(context, section): + (textboxes, comboboxes, collapse_button) = get_combobox_textbox_object( + context.app.contact_editor, section) + + # clear existing data + for textbox in textboxes: + textbox.text = "" + + for index, row in enumerate(context.table.rows): + # Check that we have sufficient amount of textboxes + # If not - click plus buttons until we have enough + if index == len(textboxes): + textboxes[0].parent.child(roleName="push button").click() + (textboxes, comboboxes, collapse_button) = get_combobox_textbox_object( + context.app.contact_editor, section) + textboxes[index].text = row['Value'] + if comboboxes[index].combovalue != row['Field']: + comboboxes[index].combovalue = row['Field'] + + +@then(u'{section} are set to') +def emails_are_set_to(context, section): + (textboxes, comboboxes, collapse_button) = get_combobox_textbox_object( + context.app.contact_editor, section, section == 'IMs') + + actual = [] + for index, textbox in enumerate(textboxes): + combo_value = textbox.text + if combo_value.strip() != '': + type_value = comboboxes[index].combovalue + actual.append({'Field': unicode(type_value), 'Value': unicode(combo_value)}) + actual = sorted(actual) + + expected = [] + for row in context.table: + expected.append({'Field': row['Field'], 'Value': row['Value']}) + expected = sorted(expected) + + assert actual == expected, "Incorrect %s value:\nexpected:%s\n but was:%s" % ( + row['Field'], expected, actual) + + # Collapse the section after check + collapse_button.click() + + +@step(u'Tick "Wants to receive HTML mail" checkbox') +def tick_checkbox(context): + context.app.contact_editor.childNamed("Wants to receive HTML mail").click() + + +@step(u'"Wants to receive HTML mail" checkbox is ticked') +def checkbox_is_ticked(context): + check_state = context.app.childNamed("Wants to receive HTML mail").checked + assert check_state, "Incorrect checkbox state" + + +@step(u'Switch to "{name}" tab in contact editor') +def switch_to_tab(context, name): + context.app.contact_editor.tab(name).click() + + +@step(u'Set the following properties in contact editor') +def set_properties(context): + for row in context.table.rows: + context.execute_steps(u""" + * Set "%s" in contact editor to "%s" + """ % (row['Field'], row['Value'])) + + +@step(u'The following properties in contact editor are set') +def verify_properties(context): + for row in context.table.rows: + context.execute_steps(u""" + Then "%s" property is set to "%s" + """ % (row['Field'], row['Value'])) + + +@step(u'Set the following properties in "{section}" section of contact editor') +def set_properties_in_section(context, section): + for row in context.table.rows: + context.execute_steps(u""" + * Set "%s" in "%s" section of contact editor to "%s" + """ % (row['Field'], section, row['Value'])) + + +@step(u'The following properties in "{section}" section of contact editor are set') +def verify_properties_in_section(context, section): + for row in context.table.rows: + context.execute_steps(u""" + Then "%s" property in "%s" section is set to "%s" + """ % (row['Field'], section, row['Value'])) + + +@step(u'Set the following note for the contact') +def set_note_for_contact(context): + context.app.contact_editor.child( + roleName='page tab', name='Notes').textentry('').text = context.text + + +@then(u'The following note is set for the contact') +def verify_note_set_for_contact(context): + actual = context.app.contact_editor.child( + roleName='page tab', name='Notes').textentry('').text + expected = context.text + assert actual == expected,\ + "Incorrect note value:\nexpected:%s\n but was:%s" % (expected, actual) + + +@step(u'Set "{field_name}" in "{section}" section of contact editor to "{field_value}"') +def set_field_in_section_to_value(context, field_name, section, field_value): + element = get_element_by_name( + context.app.contact_editor, field_name, section=section) + if element.roleName == "text": + element.text = field_value + elif element.roleName == "combo box": + element.combovalue = field_value diff -up evolution-3.13.4/tests/steps/initial_setup_steps.py.missing-tests evolution-3.13.4/tests/steps/initial_setup_steps.py --- evolution-3.13.4/tests/steps/initial_setup_steps.py.missing-tests 2014-07-30 11:21:47.032172902 +0200 +++ evolution-3.13.4/tests/steps/initial_setup_steps.py 2014-07-30 11:21:47.032172902 +0200 @@ -0,0 +1,130 @@ +# -*- coding: UTF-8 -*- +from behave import step + +from common_steps import check_for_errors +from dogtail.tree import root +from os import system +from pyatspi import STATE_SENSITIVE +from time import sleep + + +@step(u'Open Evolution and setup fake account') +def open_evolution_and_setup_fake_account(context): + system("evolution --force-shutdown 2&> /dev/null") + context.execute_steps(u'* Start a new Evolution instance') + window = context.app.child(roleName='frame') + if window.name == 'Welcome': + context.execute_steps(u""" + * Complete Welcome dialog in Evolution Account Assistant + * Complete Restore from Backup dialog in Evolution Account Assistant + * Complete Identity dialog setting name to "GNOME QE User" and email address to "test@test" + * Wait for account is being looked up dialog in Evolution Account Assistant + * Complete Receiving Email dialog of Evolution Account Assistant setting + | Field | Value | + | Server Type: | None | + * Complete Sending Email dialog of Evolution Account Assistant setting + | Field | Value | + | Server Type: | Sendmail | + * Complete Account Summary in Evolution Account Assistant + * Complete Done dialog in Evolution Account Assistant + """) + + +@step(u'Complete Receiving Options in Evolution Account Assistant') +@step(u'Complete Account Summary in Evolution Account Assistant') +@step(u'Complete Restore from Backup dialog in Evolution Account Assistant') +@step(u'Complete Welcome dialog in Evolution Account Assistant') +def evo_account_assistant_dummy_dialogs(context): + # nothing to do here, skip it + window = context.app.child(roleName='frame') + click_next(window) + + +@step(u'Complete Identity dialog setting name to "{name}" and email address to "{email}"') +def evo_account_assistant_identity_dialog(context, name, email): + # nothing to do here, skip it + window = context.app.child(roleName='frame') + window.childLabelled("Full Name:").text = name + window.childLabelled("Email Address:").text = email + click_next(window) + + +@step(u"Wait for account is being looked up dialog in Evolution Account Assistant") +def wait_for_account_to_be_looked_up(context): + window = context.app.child(roleName='frame') + skip_lookup = window.findChildren(lambda x: x.name == 'Skip Lookup') + visible_skip_lookup = [x for x in skip_lookup if x.showing] + if len(visible_skip_lookup) > 0: + visible_skip_lookup = visible_skip_lookup[0] + # bug https://bugzilla.gnome.org/show_bug.cgi?id=726539: Skip Lookup is not being removed + #assert wait_until(lambda x: not x.showing, visible_skip_lookup),\ + # "Skip Lookup button didn't dissappear" + + +def click_next(window): + # As initial wizard dialog creates a bunch of 'Next' buttons + # We have to click to the visible and enabled one + buttons = window.findChildren(lambda x: x.name == 'Next' and x.showing and + STATE_SENSITIVE in x.getState().getStates()) + if buttons == []: + raise Exception("Enabled Next button was not found") + else: + buttons[0].click() + + +@step(u'Complete {sending_or_receiving} Email dialog of Evolution Account Assistant setting') +def evo_account_assistant_receiving_email_dialog_from_table(context, sending_or_receiving): + window = context.app.child(roleName='frame') + for row in context.table: + label = str(row['Field']) + value = str(row['Value']) + filler = window.child(roleName='filler', name='%s Email' % sending_or_receiving) + widgets = filler.findChildren(lambda x: x.showing) + visible_widgets = [x for x in widgets if x.labeller and x.labeller.name == label] + if len(visible_widgets) == 0: + raise RuntimeError("Cannot find visible widget labelled '%s'" % label) + widget = visible_widgets[0] + if widget.roleName == 'combo box': + if label != 'Port:': + widget.click() + widget.menuItem(value).click() + else: + # Port is a combobox, but you can type your port there + widget.textentry('').text = value + widget.textentry('').grab_focus() + widget.textentry('').keyCombo("") + if widget.roleName == 'text': + widget.text = value + + # Check for password here and accept self-generated certificate (if appears) + btns = window.findChildren(lambda x: x.name == 'Check for Supported Types') + visible_btns = [w for w in btns if w.showing] + if visible_btns == []: + click_next(window) + return + visible_btns[0].click() + + # Confirm all certificates by clicking 'Accept Permanently' until dialog is visible + apps = [x.name for x in root.applications()] + if 'evolution-user-prompter' in apps: + prompter = root.application('evolution-user-prompter') + dialog = prompter.child(roleName='dialog') + while dialog.showing: + if prompter.findChild(lambda x: x.name == 'Accept Permanently', retry=False, requireResult=False): + prompter.button('Accept Permanently').click() + else: + sleep(0.1) + + # Wait until Cancel button disappears + cancel = filler.findChildren(lambda x: x.name == 'Cancel')[0] + while cancel.showing: + sleep(0.1) + check_for_errors(context) + click_next(window) + + +@step(u'Complete Done dialog in Evolution Account Assistant') +def evo_account_assistant_done_dialog(context): + # nothing to do here, skip it + window = context.app.child(roleName='frame') + window.button('Apply').click() diff -up evolution-3.13.4/tests/steps/steps.py.missing-tests evolution-3.13.4/tests/steps/steps.py --- evolution-3.13.4/tests/steps/steps.py.missing-tests 2014-07-30 11:21:47.032172902 +0200 +++ evolution-3.13.4/tests/steps/steps.py 2014-07-30 11:21:47.032172902 +0200 @@ -0,0 +1,177 @@ +# -*- coding: UTF-8 -*- +from behave import step, then +from common_steps import wait_until +from dogtail.tree import root +from dogtail.rawinput import keyCombo +from time import sleep, time +from os import system +from gi.repository import Gio, GLib + + +@step(u'Help section "{name}" is displayed') +def help_is_displayed(context, name): + try: + context.yelp = root.application('yelp') + frame = context.yelp.child(roleName='frame') + wait_until(lambda x: x.showing, frame) + sleep(1) + context.assertion.assertEquals(name, frame.name) + finally: + system("killall yelp") + + +@step(u'Evolution has {num:d} window opened') +@step(u'Evolution has {num:d} windows opened') +def evolution_has_num_windows_opened(context, num): + windows = context.app.findChildren(lambda x: x.roleName == 'frame') + context.assertion.assertEqual(len(windows), num) + + +@step(u'Preferences dialog is opened') +def preferences_dialog_opened(context): + context.app.window('Evolution Preferences') + + +@step(u'"{name}" view is opened') +def view_is_opened(context, name): + if name != 'Mail': + window_name = context.app.children[0].name + context.assertion.assertEquals(window_name, "%s - Evolution" % name) + else: + # A special case for Mail + context.assertion.assertTrue(context.app.menu('Message').showing) + + +def get_visible_searchbar(context): + """Wait for searchbar to become visible""" + def get_searchbars(): + return context.app.findChildren(lambda x: x.labeller.name == 'Search:' and x.showing) + assert wait_until(lambda x: len(x()) > 0, get_searchbars), "No visible searchbars found" + return get_searchbars()[0] + + +@step(u'Open "{section_name}" section') +def open_section_by_name(context, section_name): + wait_until(lambda x: x.showing, context.app.menu('View')) + sleep(0.2) + context.app.menu('View').click() + context.app.menu('View').menu('Window').point() + context.app.menu('View').menu('Window').menuItem(section_name).click() + + # Find a search bar + context.app.search_bar = get_visible_searchbar(context) + + # Check that service required for this sections is running + required_services = { + 'Mail': 'org.gnome.evolution.dataserver.Sources', + 'Calendar': 'org.gnome.evolution.dataserver.Calendar', + 'Tasks': 'org.gnome.evolution.dataserver.Calendar', + 'Memos': 'org.gnome.evolution.dataserver.Calendar', + 'Contacts': 'org.gnome.evolution.dataserver.AddressBook', + } + required_service = required_services[section_name] + bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) + dbus_proxy = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, + 'org.freedesktop.DBus', + '/org/freedesktop/DBus', + 'org.freedesktop.DBus', None) + for attempt in xrange(0, 10): + result = dbus_proxy.call_sync( + 'ListNames', None, Gio.DBusCallFlags.NO_AUTO_START, 500, None) + sleep(1) + if True in [required_service in x for x in result[0]]: + return + raise RuntimeError("%s service was not found" % required_service) + + +@step(u'"{name}" menu is opened') +def menu_is_opened(context, name): + sleep(0.5) + menu = context.app.menu(name) + children_displayed = [x.showing for x in menu.children] + context.assertion.assertTrue(True in children_displayed, "Menu '%s' is not opened" % name) + + +@step(u'Press "{sequence}"') +def press_button_sequence(context, sequence): + keyCombo(sequence) + sleep(0.5) + + +@then(u'Evolution is closed') +def evolution_is_closed(context): + assert wait_until(lambda x: x.dead, context.app),\ + "Evolution window is opened" + context.assertion.assertFalse(context.app_class.isRunning(), "Evolution is in the process list") + + +@step(u'Message composer with title "{name}" is opened') +def message_composer_is_opened(context, name): + context.app.composer = context.app.window(name) + + +@then(u'Contact editor window with title "{title}" is opened') +def contact_editor_with_label_is_opened(context, title): + context.app.contact_editor = context.app.dialog(title) + context.assertion.assertIsNotNone( + context.app.contact_editor, "Contact Editor was not found") + context.assertion.assertTrue( + context.app.contact_editor.showing, "Contact Editor didn't appear") + + +@then(u'Contact editor window is opened') +def contact_editor_is_opened(context): + context.execute_steps(u'Then Contact editor window with title "Contact Editor" is opened') + + +@then(u'Contact List editor window is opened') +def contact_list_editor_is_opened(context): + context.execute_steps( + u'Then Contact List editor window with title "Contact List Editor" is opened') + + +@then(u'Contact List editor window with title "{name}" is opened') +def contact_list_editor__with_name_is_opened(context, name): + context.app.contact_list_editor = context.app.dialog(name) + + +@step(u'Memo editor with title "{name}" is opened') +def memo_editor_is_opened(context, name): + context.execute_steps(u'* Task editor with title "%s" is opened' % name) + + +@step(u'Shared Memo editor with title "{name}" is opened') +def shared_memo_editor_is_opened(context, name): + context.execute_steps(u'* Task editor with title "%s" is opened' % name) + + +@step(u'Task editor with title "{title}" is opened') +def task_editor_with_title_is_opened(context, title): + context.app.task_editor = context.app.window(title) + # Spoof event_editor for assigned tasks + if 'Assigned' in title: + context.app.event_editor = context.app.task_editor + + +@step(u'Event editor with title "{name}" is displayed') +def event_editor_with_name_displayed(context, name): + context.app.event_editor = context.app.window(name) + + +@step(u'Wait for email to synchronize') +def wait_for_mail_folder_to_synchronize(context): + # Wait until Google calendar is loaded + for attempt in range(0, 10): + start_time = time() + try: + spinners = context.app.findChildren(lambda x: x.name == 'Spinner') + for spinner in spinners: + try: + while spinner.showing: + sleep(0.1) + if (time() - start_time) > 180: + raise RuntimeError("Mail takes too long to synchronize") + except GLib.GError: + continue + except (GLib.GError, TypeError): + continue