Add installed tests using --enable-installed-tests switch
https://bugzilla.gnome.org/show_bug.cgi?id=726832
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
"
|
||||
|
135
m4/behave-installed-test.m4
Normal file
135
m4/behave-installed-test.m4
Normal file
@ -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])])
|
||||
])
|
223
tests/common_steps.py
Normal file
223
tests/common_steps.py
Normal file
@ -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)
|
46
tests/environment.py
Normal file
46
tests/environment.py
Normal file
@ -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)
|
135
tests/shortcuts.feature
Normal file
135
tests/shortcuts.feature
Normal file
@ -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 |
|
138
tests/steps/initial_setup_steps.py
Normal file
138
tests/steps/initial_setup_steps.py
Normal file
@ -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()
|
122
tests/steps/steps.py
Normal file
122
tests/steps/steps.py
Normal file
@ -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)
|
Reference in New Issue
Block a user