[evolution] Add installed tests using --enable-installed-tests switch



commit 399b1a6fc593a9c5e692923120346e0386f578e5
Author: Vadim Rutkovsky <vrutkovs redhat com>
Date:   Fri Mar 21 16:15:00 2014 +0100

    Add installed tests using --enable-installed-tests switch
    
    https://bugzilla.gnome.org/show_bug.cgi?id=726832

 Makefile.am                        |    5 +
 configure.ac                       |    6 +
 m4/behave-installed-test.m4        |  135 ++++++++++++++++++++++
 tests/common_steps.py              |  223 ++++++++++++++++++++++++++++++++++++
 tests/environment.py               |   46 ++++++++
 tests/shortcuts.feature            |  135 ++++++++++++++++++++++
 tests/steps/initial_setup_steps.py |  138 ++++++++++++++++++++++
 tests/steps/steps.py               |  122 ++++++++++++++++++++
 8 files changed, 810 insertions(+), 0 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index db35826..521fe03 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -147,4 +147,9 @@ DISTCHECK_CONFIGURE_FLAGS =         \
        --disable-weather               \
        --with-help
 
+ BEHAVE_INSTALLED_TESTS_RULE@
+INSTALLED_TESTS=general_shortcuts mail_shortcuts contacts_shortcuts    \
+       calendar_shortcuts memos_shortcuts view_shortcuts menu_shortcuts
+INSTALLED_TESTS_TYPE=session-exclusive
+
 -include $(top_srcdir)/git.mk
diff --git a/configure.ac b/configure.ac
index f4c9f70..023a126 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1411,6 +1411,11 @@ if test "$with_catalog" = "yes"; then
 fi
 AM_CONDITIONAL(GLADE_CATALOG, test "x$with_catalog" != "xno")
 
+dnl ********************************************
+dnl Installed tests
+dnl ********************************************
+BEHAVE_INSTALLED_TESTS
+
 dnl ******************************
 dnl Makefiles
 dnl ******************************
@@ -1592,4 +1597,5 @@ echo "
        Highlight support:      $msg_text_highlight
        Plugins:                $msg_plugins
        User documentation:     $with_help
+       Installed tests:        $enable_installed_tests
 "
diff --git a/m4/behave-installed-test.m4 b/m4/behave-installed-test.m4
new file mode 100644
index 0000000..f1f13cf
--- /dev/null
+++ b/m4/behave-installed-test.m4
@@ -0,0 +1,135 @@
+# How to use the installed tests m4
+#
+#   Place BEHAVE_INSTALLED_TESTS somewhere in configure.ac
+#
+#   Writing your Makefile.am
+#   ~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#   Somewhere in your Makefile.am in this test directory, you need to declare
+#   the following variables:
+#
+#       INSTALLED_TESTS=list of tags for tests to install
+#       INSTALLED_TESTS_TYPE=session-exclusive
+#
+#   First the list of tests which should be installed, followed by
+#   the type of test they should be configured as. The type can
+#   be 'session' or 'session-exclusive'
+#
+#   More information about valid types can be found here:
+#      https://wiki.gnome.org/GnomeGoals/InstalledTests
+#
+#   The last variable is optional, but can be useful to configure
+#   your test program to run in the installed environment as opposed
+#   to the normal `make check' run.
+#
+#   Then place this somewhere in your Makefile.am
+#
+#       @BEHAVE_INSTALLED_TESTS_RULE@
+#
+#   And the following in configure.ac
+#
+#       BEHAVE_INSTALLED_TESTS
+#
+#   And that's it, now your unit tests will be installed along with
+#   a .test metadata file into $(pkglibexecdir) if --enable-installed-tests
+#   is passed to your configure script, and will be run automatically
+#   by the continuous integration servers.
+#
+#   FIXME: Change the above link to point to real documentation, not
+#   a gnome goal page which might disappear at some point.
+#
+# BUGS: This macro hooks into install-exec-am and install-data-am
+# which are internals of Automake. This is because Automake doesnt
+# consider the regular install-exec-local / install-exec-hook or
+# data install components unless variables have been setup for them
+# in advance.
+#
+# This doesnt seem to present a problem, but it is depending on
+# internals of Automake instead of clear documented API.
+
+# Place this in configure.ac to enable
+# the installed tests option.
+
+AC_DEFUN([BEHAVE_INSTALLED_TESTS], [
+AC_PREREQ([2.50])dnl
+AC_REQUIRE([AM_NLS])dnl
+
+  AC_PROG_INSTALL
+  AC_PROG_MKDIR_P
+  AC_PROG_LIBTOOL
+
+  AC_ARG_ENABLE(installed-tests,
+               [AC_HELP_STRING([--enable-installed-tests],
+                               [enable installed unit tests [default=no]])],,
+               [enable_installed_tests="no"])
+
+  AM_CONDITIONAL([BEHAVE_INSTALLED_TESTS_ENABLED],[test "x$enable_installed_tests" = "xyes"])
+  AC_SUBST([BEHAVE_INSTALLED_TESTS_ENABLED], [$enable_installed_tests])
+
+  # Define the rule for makefiles
+  BEHAVE_INSTALLED_TESTS_RULE='
+
+ifeq ($(BEHAVE_INSTALLED_TESTS_ENABLED),yes)
+
+install-exec-am: installed-tests-exec-hook
+install-data-am: installed-tests-data-hook
+uninstall-am: uninstall-tests-hook
+
+META_DIRECTORY=${DESTDIR}/${datadir}/installed-tests/${PACKAGE}
+EXEC_DIRECTORY=${DESTDIR}/${pkglibexecdir}/installed-tests
+
+FINAL_TEST_ENVIRONMENT=
+ifneq ($(INSTALLED_TESTS_ENVIRONMENT),)
+      FINAL_TEST_ENVIRONMENT="env $(INSTALLED_TESTS_ENVIRONMENT)"
+endif
+BEHAVE_FEATURES=$(notdir $(wildcard tests/*.feature))
+BEHAVE_STEP_DEFINITION=$(notdir $(wildcard tests/steps/*.py))
+BEHAVE_COMMON_FILES=environment.py common_steps.py
+
+installed-tests-exec-hook:
+       @$(MKDIR_P) $(EXEC_DIRECTORY);
+       @for feature in $(BEHAVE_FEATURES); do                                                                
                  \
+           $(LIBTOOL) --mode=install $(INSTALL) --mode=777 tests/$$feature $(EXEC_DIRECTORY);\
+       done
+       @for common_file in $(BEHAVE_COMMON_FILES); do                                                        
                  \
+           $(LIBTOOL) --mode=install $(INSTALL) --mode=777 tests/$$common_file $(EXEC_DIRECTORY);\
+       done
+       @$(MKDIR_P) $(EXEC_DIRECTORY)/steps;
+       @for step_definition in $(BEHAVE_STEP_DEFINITION); do                                                 
                  \
+           $(LIBTOOL) --mode=install $(INSTALL) --mode=777 tests/steps/$$step_definition 
$(EXEC_DIRECTORY)/steps;\
+       done
+
+
+installed-tests-data-hook:
+       @$(MKDIR_P) $(META_DIRECTORY);
+       @for test in $(INSTALLED_TESTS); do                                                     \
+           echo "Installing $$test.test to $(META_DIRECTORY)";                                 \
+           echo m4_escape([[Test]]) > $(META_DIRECTORY)/$$test.test;                           \
+           echo "Exec=behave $(pkglibexecdir)/installed-tests -t $$test -k -f plain"   \
+                                                  >> $(META_DIRECTORY)/$$test.test;            \
+           echo "Type=$(INSTALLED_TESTS_TYPE)" >> $(META_DIRECTORY)/$$test.test;               \
+       done
+
+uninstall-tests-hook:
+       @for feature in $(BEHAVE_FEATURES); do\
+           echo "Removing feature $(EXEC_DIRECTORY) $$feature";\
+           $(LIBTOOL) --mode=uninstall $(RM) $(EXEC_DIRECTORY)/$$feature;\
+       done
+       @for common_file in $(BEHAVE_COMMON_FILES); do\
+           echo "Removing feature $(EXEC_DIRECTORY) $$common_file";\
+           $(LIBTOOL) --mode=uninstall $(RM) $(EXEC_DIRECTORY)/$$common_file;\
+       done
+       @for step_definition in $(BEHAVE_STEP_DEFINITION); do\
+           echo "Removing feature $(EXEC_DIRECTORY)/steps $$step_definition";\
+           $(LIBTOOL) --mode=uninstall $(RM) $(EXEC_DIRECTORY)/steps/$$step_definition;\
+       done
+       @for test in $(INSTALLED_TESTS); do\
+           $(LIBTOOL) --mode=uninstall $(RM) $(META_DIRECTORY)/$$test.test;\
+       done
+endif
+'
+
+  # substitute @BEHAVE_INSTALLED_TESTS_RULE@ in Makefiles
+  AC_SUBST([BEHAVE_INSTALLED_TESTS_RULE])
+  m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([BEHAVE_INSTALLED_TESTS_RULE])])
+])
\ No newline at end of file
diff --git a/tests/common_steps.py b/tests/common_steps.py
new file mode 100644
index 0000000..3752142
--- /dev/null
+++ b/tests/common_steps.py
@@ -0,0 +1,223 @@
+# -*- 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, kill, system
+from signal import signal, alarm, SIGALRM, SIGKILL
+from subprocess import Popen
+from behave import step
+from gi.repository import GLib, Gio
+
+from dogtail.rawinput import keyCombo, absoluteMotion, pressKey
+from dogtail.tree import root
+from dogtail.utils import run
+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='<Control><Q>', 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('<Control><Alt><Shift>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, 10):
+            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('<Control><Alt><Shift>R')
+
+        try:
+            kill(self.pid, SIGKILL)
+        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=1)
+
+        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")
+
+
+
+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)
diff --git a/tests/environment.py b/tests/environment.py
new file mode 100644
index 0000000..2d6a09f
--- /dev/null
+++ b/tests/environment.py
@@ -0,0 +1,46 @@
+# -*- coding: UTF-8 -*-
+
+from time import sleep
+from dogtail.utils import isA11yEnabled, enableA11y
+if not isA11yEnabled():
+    enableA11y(True)
+
+from common_steps import App, dummy, cleanup
+from dogtail.config import config
+
+
+def before_all(context):
+    """Setup evolution stuff
+    Being executed once before any test
+    """
+
+    try:
+        # 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()
+
+        context.app_class = App('evolution')
+
+    except Exception as e:
+        print("Error in before_all: %s" % e.message)
+
+
+def after_scenario(context, scenario):
+    """Teardown for each scenario
+    Kill evolution (in order to make this reliable we send sigkill)
+    """
+    try:
+        # Stop evolution
+        context.app_class.kill()
+
+        # 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 --git a/tests/shortcuts.feature b/tests/shortcuts.feature
new file mode 100644
index 0000000..0f9989a
--- /dev/null
+++ b/tests/shortcuts.feature
@@ -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 "<Control>Q"
+    Then Evolution is closed
+
+  @general_shortcuts
+  Scenario: F1 to launch help
+    * Press "<F1>"
+    Then Help section "Evolution Mail and Calendar" is displayed
+
+  @general_shortcuts
+  Scenario: Shift-Ctrl-W to open a new window
+    * Press "<Control><Shift>W"
+    Then Evolution has 2 windows opened
+
+  @general_shortcuts
+  Scenario: Ctrl-W to close a window
+    * Press "<Control><Shift>W"
+    * Press "<Control>W"
+    Then Evolution has 1 window opened
+
+  @general_shortcuts
+  Scenario: Ctrl-Shift-S to open Preferences
+    * Press "<Control><Shift>S"
+    Then Preferences dialog is opened
+
+  @mail_shortcuts
+  Scenario: Mail: Ctrl-Shift-M to compose new message
+    * Open "Mail" section
+    * Press "<Control><Shift>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 "<Control><Shift>C"
+    Then Contact editor window is opened
+
+  @contacts_shortcuts
+  Scenario: Contacts: Ctrl-Shift-L to create new contact list
+    * Open "Contacts" section
+    * Press "<Control><Shift>L"
+    Then Contact List editor window is opened
+
+  @calendar_shortcuts
+  Scenario: Calendar: Ctrl-Shift-A to create new appointment
+    * Open "Calendar" section
+    * Press "<Control><Shift>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 "<Control><Shift>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 "<Control><Shift>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 "<Control><Shift>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 "<Control><Shift>O"
+    Then Shared memo editor with title "Memo - No Summary" is opened
+
+  @view_shortcuts
+  Scenario Outline: Ctrl+<1-5> to switch views
+    * Press "<shortcut>"
+    Then "<section>" view is opened
+
+    Examples:
+      | shortcut | section  |
+      | <Ctrl>1  | Mail     |
+      | <Ctrl>2  | Contacts |
+      | <Ctrl>3  | Calendar |
+      | <Ctrl>4  | Tasks    |
+      | <Ctrl>5  | Memos    |
+
+  @menu_shortcuts
+  Scenario Outline: Menu shortcuts on all views
+    * Open "<section>" section
+    * Press "<shortcut>"
+    Then "<menu>" menu is opened
+
+    Examples:
+      | section | shortcut | menu    |
+      | Mail    | <Alt>F   | File    |
+      | Mail    | <Alt>E   | Edit    |
+      | Mail    | <Alt>V   | View    |
+      | Mail    | <Alt>O   | Folder  |
+      | Mail    | <Alt>M   | Message |
+      | Mail    | <Alt>S   | Search  |
+      | Mail    | <Alt>H   | Help    |
+
+      | Contacts | <Alt>F   | File    |
+      | Contacts | <Alt>E   | Edit    |
+      | Contacts | <Alt>V   | View    |
+      | Contacts | <Alt>A   | Actions |
+      | Contacts | <Alt>S   | Search  |
+      | Contacts | <Alt>H   | Help    |
+
+      | Calendar | <Alt>F   | File    |
+      | Calendar | <Alt>E   | Edit    |
+      | Calendar | <Alt>V   | View    |
+      | Calendar | <Alt>A   | Actions |
+      | Calendar | <Alt>S   | Search  |
+      | Calendar | <Alt>H   | Help    |
+
+      | Tasks | <Alt>F | File    |
+      | Tasks | <Alt>E | Edit    |
+      | Tasks | <Alt>V | View    |
+      | Tasks | <Alt>A | Actions |
+      | Tasks | <Alt>S | Search  |
+      | Tasks | <Alt>H | Help    |
+
+      | Memos | <Alt>F | File    |
+      | Memos | <Alt>E | Edit    |
+      | Memos | <Alt>V | View    |
+      | Memos | <Alt>S | Search  |
+      | Memos | <Alt>H | Help    |
diff --git a/tests/steps/initial_setup_steps.py b/tests/steps/initial_setup_steps.py
new file mode 100644
index 0000000..ec97e45
--- /dev/null
+++ b/tests/steps/initial_setup_steps.py
@@ -0,0 +1,138 @@
+# -*- coding: UTF-8 -*-
+from behave import step
+
+from common_steps import wait_until, 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 == 'Evolution Account Assistant':
+        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
+            """)
+        # Evo doesn't create default addressbook immidiately
+        # We should restart it
+        system("evolution --force-shutdown 2&> /dev/null")
+        context.execute_steps(u'* Start a new Evolution instance')
+
+
+ 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('Evolution Account Assistant')
+    click_continue(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('Evolution Account Assistant')
+    window.childLabelled("Full Name:").text = name
+    window.childLabelled("Email Address:").text = email
+    click_continue(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('Evolution Account Assistant')
+    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]
+        assert wait_until(lambda x: not x.showing, visible_skip_lookup),\
+            "Skip Lookup button didn't dissappear"
+
+
+def click_continue(window):
+    # As initial wizard dialog creates a bunch of 'Continue' buttons
+    # We have to click to the visible and enabled one
+    button = None
+    for attempt in xrange(0, 10):
+        btns = window.findChildren(lambda x: x.name == 'Continue')
+        visible_and_enabled = [x for x in btns if x.showing and STATE_SENSITIVE in x.getState().getStates()]
+        if visible_and_enabled == []:
+            sleep(0.1)
+            continue
+        else:
+            button = visible_and_enabled[0]
+            break
+    button.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('Evolution Account Assistant')
+    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("<Enter>")
+        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_continue(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_continue(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('Evolution Account Assistant')
+    window.button('Apply').click()
diff --git a/tests/steps/steps.py b/tests/steps/steps.py
new file mode 100644
index 0000000..dac984f
--- /dev/null
+++ b/tests/steps/steps.py
@@ -0,0 +1,122 @@
+# -*- 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
+from os import system
+
+
+ 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)
+
+
+ step(u'Open "{section_name}" section')
+def open_section_by_name(context, section_name):
+    context.app.menu('View').click()
+    context.app.menu('View').menu('Window').point()
+    context.app.menu('View').menu('Window').menuItem(section_name).click()
+
+
+ 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)
\ No newline at end of file


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]