summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Rutkovsky <vrutkovs@redhat.com>2014-04-10 18:13:19 +0200
committerFelix Riemann <friemann@gnome.org>2014-04-28 22:42:53 +0200
commit8dffb2f1c34c25ccfd48c2a45acd035e386e7ace (patch)
tree529603c8cd67bf539204a7f9e91f166e0dd87890
parentc9cfd7c85836a19b07d9edfca47692fd2f694e89 (diff)
downloadeog-8dffb2f1c34c25ccfd48c2a45acd035e386e7ace.tar.gz
Add initial installed tests
-rw-r--r--tests/actions.feature67
-rw-r--r--tests/common_steps.py245
-rw-r--r--tests/environment.py47
-rw-r--r--tests/gnome-logo.pngbin0 -> 11104 bytes
-rw-r--r--tests/steps/steps.py131
5 files changed, 490 insertions, 0 deletions
diff --git a/tests/actions.feature b/tests/actions.feature
new file mode 100644
index 00000000..33847cc6
--- /dev/null
+++ b/tests/actions.feature
@@ -0,0 +1,67 @@
+Feature: Smoke tests
+
+ Background:
+ * Make sure that eog is running
+
+ @about
+ Scenario: About dialog
+ * Open About dialog
+ Then Website link to wiki is displayed
+ And GPL 2.0 link is displayed
+
+ @undo @undo_via_toolbar
+ Scenario: Undo via toolbar
+ * Open "/tmp/gnome-logo.png" via menu
+ Then image size is 199x76
+ * Rotate the image clockwise
+ Then image size is 76x199
+ * Select "Edit -> Undo" menu
+ Then image size is 199x76
+
+ @undo @undo_via_shortcut
+ Scenario: Undo via shortcut
+ * Open "/tmp/gnome-logo.png" via menu
+ Then image size is 199x76
+ * Rotate the image clockwise
+ Then image size is 76x199
+ * Press "<Ctrl>z"
+ Then image size is 199x76
+
+ @sidepane @sidepane_via_menu
+ Scenario: Sidepanel via menu
+ * Open "/tmp/gnome-logo.png" via menu
+ Then sidepanel is displayed
+ * Select "View -> Side Pane" menu
+ Then sidepanel is hidden
+
+ @sidepane @sidepane_via_shortcut
+ Scenario: Slideshow via shortcut
+ * Open "/tmp/gnome-logo.png" via menu
+ * Press "<Ctrl><F9>"
+ Then sidepanel is hidden
+ * Select "View -> Side Pane" menu
+ Then sidepanel is displayed
+
+ @fullscreen @fullscreen_via_menu
+ Scenario: Fullscreen via menu
+ * Open "/tmp/gnome-logo.png" via menu
+ * Select "View -> Fullscreen" menu
+ Then application is displayed fullscreen
+ * Press "<Esc>"
+ Then application is not fullscreen anymore
+
+ @fullscreen @fullscreen_via_shortcut
+ Scenario: Fullscreen via shortcut
+ * Open "/tmp/gnome-logo.png" via menu
+ * Press "<F11>"
+ Then application is displayed fullscreen
+ * Press "<Esc>"
+ Then application is not fullscreen anymore
+
+ @wallpaper
+ Scenario: Set as wallpaper
+ * Open "/tmp/gnome-logo.png" via menu
+ * Select "Set as Wallpaper" from context menu
+ * Click "Hide" in wallpaper popup
+ Then wallpaper is set to "gnome-logo.png"
+
diff --git a/tests/common_steps.py b/tests/common_steps.py
new file mode 100644
index 00000000..adda7b52
--- /dev/null
+++ b/tests/common_steps.py
@@ -0,0 +1,245 @@
+# -*- 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, path, getcwd
+from signal import signal, alarm, SIGALRM, SIGKILL
+from subprocess import Popen
+from behave import step
+from gi.repository import GLib
+
+from dogtail.rawinput import keyCombo, absoluteMotion, pressKey
+from dogtail.tree import root
+from dogtail.utils import run
+from dogtail.predicate import GenericPredicate
+import pyatspi
+
+
+def cleanup():
+ for schema in ['org.gnome.eog.fullscreen', 'org.gnome.eog.plugins', 'org.gnome.eog.ui', 'org.gnome.eog.view']:
+ system("gsettings reset-recursively %s" % schema)
+
+ # Remove all the remains of other files
+ system("rm /tmp/gnome-logo.bmp -rf")
+
+ # Make sure we have a test file present
+ testfile_path = path.join(path.dirname(path.realpath(__file__)), "gnome-logo.png")
+ system("cp %s /tmp" % testfile_path)
+
+
+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.instance)
+ 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'Make sure that {app} is running')
+def ensure_app_running(context, app):
+ context.app = context.app_class.startViaCommand()
+
+
+@step(u'Press "{sequence}"')
+def press_button_sequence(context, sequence):
+ keyCombo(sequence)
+ sleep(0.5)
+
+
+@step(u'Folder select dialog with name "{name}" is displayed')
+def has_folder_select_dialog_with_name(context, name):
+ has_files_select_dialog_with_name(context, name)
+
+
+@step(u'Folder select dialog is displayed')
+def has_folder_select_dialog(context):
+ context.execute_steps(
+ u'Then folder select dialog with name "Select Folder" is displayed')
+
+
+@step(u'In folder select dialog choose "{name}"')
+def select_folder_in_dialog(context, name):
+ select_file_in_dialog(context, name)
+
+
+@step(u'file select dialog with name "{name}" is displayed')
+def has_files_select_dialog_with_name(context, name):
+ context.app.dialog = context.app.child(name=name,
+ roleName='file chooser')
+
+
+@step(u'File select dialog is displayed')
+def has_files_select_dialog(context):
+ context.execute_steps(
+ u'Then file select dialog with name "Select Files" is displayed')
+
+@step(u'In file select dialog select "{name}"')
+def select_file_in_dialog(context, name):
+ # Find an appropriate button to click
+ # It will be either 'Home' or 'File System'
+
+ home_folder = context.app.dialog.findChild(GenericPredicate(name='Home'),
+ retry=False,
+ requireResult=False)
+ if home_folder:
+ home_folder.click()
+ else:
+ context.app.dialog.childNamed('File System').click()
+ location_button = context.app.dialog.child('Type a file name')
+ if not pyatspi.STATE_ARMED in location_button.getState().getStates():
+ location_button.click()
+
+ context.app.dialog.childLabelled('Location:').set_text_contents(name)
+ sleep(0.1)
+ context.app.dialog.childLabelled('Location:').grab_focus()
+ keyCombo('<Enter>')
+ assert wait_until(lambda x: x.dead, context.app.dialog), "Dialog was not closed"
+
+
+@step(u'In file save dialog save file to "{path}" clicking "{button}"')
+def file_save_to_path(context, path, button):
+ context.app.dialog.childLabelled('Name:').set_text_contents(path)
+ context.app.dialog.childNamed(button).click()
+ assert wait_until(lambda x: x.dead, context.app.dialog), "Dialog was not closed"
diff --git a/tests/environment.py b/tests/environment.py
new file mode 100644
index 00000000..08dc06db
--- /dev/null
+++ b/tests/environment.py
@@ -0,0 +1,47 @@
+# -*- coding: UTF-8 -*-
+
+from time import sleep
+from dogtail.utils import isA11yEnabled, enableA11y
+if not isA11yEnabled():
+ enableA11y(True)
+
+from common_steps import App, cleanup
+from dogtail.config import config
+
+
+def before_all(context):
+ """Setup eog stuff
+ Being executed before all features
+ """
+
+ try:
+ # Skip dogtail actions to print to stdout
+ config.logDebugToStdOut = False
+ config.typingDelay = 0.2
+
+ context.app_class = App('eog')
+
+ except Exception as e:
+ print("Error in before_all: %s" % e.message)
+
+def before_scenario(context, scenario):
+ """ Cleanup previous settings and make sure we have test files in /tmp """
+ try:
+ cleanup()
+ except Exception as e:
+ print("Error in before_scenario: %s" % e.message)
+
+
+def after_scenario(context, scenario):
+ """Teardown for each scenario
+ Kill eog (in order to make this reliable we send sigkill)
+ """
+ try:
+ # Stop gnome-calculator
+ 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/gnome-logo.png b/tests/gnome-logo.png
new file mode 100644
index 00000000..e5ea7792
--- /dev/null
+++ b/tests/gnome-logo.png
Binary files differ
diff --git a/tests/steps/steps.py b/tests/steps/steps.py
new file mode 100644
index 00000000..9010c5d3
--- /dev/null
+++ b/tests/steps/steps.py
@@ -0,0 +1,131 @@
+# -*- coding: UTF-8 -*-
+from behave import step, then
+
+from dogtail.tree import root
+from dogtail.rawinput import typeText
+from common_steps import *
+from time import sleep
+from dogtail.rawinput import keyCombo
+from subprocess import Popen, PIPE
+
+
+@step(u'Open About dialog')
+def open_about_dialog(context):
+ context.app.menu('Help').click()
+ context.app.menu('Help').menuItem('About').click()
+ context.about_dialog = context.app.dialog('About Image Viewer')
+
+@then(u'Website link to wiki is displayed')
+def website_link_to_wiki_is_displayed(context):
+ assert context.about_dialog.child('Website').showing
+
+@then(u'GPL 2.0 link is displayed')
+def gpl_license_link_is_displayed(context):
+ assert context.about_dialog.child("Image Viewer").showing, "App name is not displayed"
+ assert context.about_dialog.child("The GNOME image viewer.").showing, "App description is not displayed"
+ assert context.about_dialog.child("Website").showing, "Website link is not displayed"
+ assert context.about_dialog.child(roleName='radio button', name="About").checked, "About tab is not selected"
+ assert not context.about_dialog.child(roleName='radio button', name="Credits").checked, "Credits tab is selected"
+
+@step(u'Open "{filename}" via menu')
+def open_file_via_menu(context, filename):
+ keyCombo("<Ctrl>O")
+ context.execute_steps(u"""
+ * file select dialog with name "Open Image" is displayed
+ * In file select dialog select "%s"
+ """ % filename)
+ sleep(0.5)
+
+@then(u'image size is {width:d}x{height:d}')
+def image_size_is(context, width, height):
+ for attempt in xrange(0, 10):
+ width_text = context.app.child(roleName='page tab list').child('Width:').parent.children[-1].text
+ if width_text == '':
+ sleep(0.5)
+ continue
+ else:
+ break
+ height_text = context.app.child(roleName='page tab list').child('Height:').parent.children[-1].text
+ try:
+ actual_width = int(width_text.split(' ')[0])
+ actual_height = int(height_text.split(' ')[0])
+ except Exception:
+ raise Exception("Incorrect width/height is been displayed")
+ assert actual_width == width
+ assert actual_height == height
+
+@step(u'Rotate the image clockwise')
+def rotate_image_clockwise(context):
+ context.app.child(roleName='tool bar').child('Right').click()
+
+@step(u'Select "{item}" from context menu')
+def select_item_from_context_menu(context, item):
+ context.app.child(roleName='drawing area').click(button=3)
+ sleep(0.1)
+ context.app.child(roleName='window').menuItem(item).click()
+
+@then(u'sidepanel is {state:w}')
+def sidepanel_displayed(context, state):
+ sleep(0.5)
+ assert state in ['displayed', 'hidden'], "Incorrect state: %s" % state
+ actual = context.app.child(roleName='page tab list').showing
+ assert actual == (state == 'displayed')
+
+def app_is_not_fullscreen(context):
+ import ipdb; ipdb.set_trace()
+
+@then(u'application is {negative:w} fullscreen anymore')
+@then(u'application is displayed fullscreen')
+def app_displayed_fullscreen(context, negative=None):
+ sleep(0.5)
+ actual = not context.app.child(roleName='menu bar').showing
+ assert actual == (negative is None)
+
+@step(u'Wait a second')
+def wait_a_second(context):
+ sleep(1)
+
+@step(u'Click "Hide" in wallpaper popup')
+def hide_wallapper_popup(context):
+ context.app.button('Hide').click()
+
+@then(u'wallpaper is set to "{filename}"')
+def wallpaper_is_set_to(context, filename):
+ wallpaper_path = Popen(["gsettings", "get", "org.gnome.desktop.background", "picture-uri"], stdout=PIPE).stdout.read()
+ actual_filename = wallpaper_path.split('/')[-1].split("'")[0]
+ assert filename == actual_filename
+
+@then(u'"{filename}" file exists')
+def file_exists(context, filename):
+ assert os.path.isfile(os.path.expanduser(filename))
+
+@then(u'image type is "{mimetype}"')
+def image_type_is(context, mimetype):
+ imagetype = context.app.child(roleName='page tab list').child('Type:').parent.children[-1].text
+ assert imagetype == mimetype
+
+@step(u'Select "{menu}" menu')
+def select_menuitem(context, menu):
+ menu_item = menu.split(' -> ')
+ # First level menu
+ current = context.app.menu(menu_item[0])
+ current.click()
+ if len(menu_item) == 1:
+ return
+ # Intermediate menus - point them but don't click
+ for item in menu_item[1:-1]:
+ current = context.app.menu(item)
+ current.point()
+ # Last level menu item
+ current.menuItem(menu_item[-1]).click()
+
+@step(u'Open "Image -> Save As" menu')
+def open_save_as_menu(context):
+ context.app.menu("Image").click()
+ context.app.menu("Image").findChildren(lambda x: 'Save As' in x.name)[0].click()
+
+@step(u'Select "{name}" window')
+def select_name_window(context, name):
+ context.app = context.app.child(roleName='frame', name=name)
+ context.app.grab_focus()
+