diff options
author | Vadim Rutkovsky <vrutkovs@redhat.com> | 2014-04-10 18:13:19 +0200 |
---|---|---|
committer | Felix Riemann <friemann@gnome.org> | 2014-04-28 22:42:53 +0200 |
commit | 8dffb2f1c34c25ccfd48c2a45acd035e386e7ace (patch) | |
tree | 529603c8cd67bf539204a7f9e91f166e0dd87890 | |
parent | c9cfd7c85836a19b07d9edfca47692fd2f694e89 (diff) | |
download | eog-8dffb2f1c34c25ccfd48c2a45acd035e386e7ace.tar.gz |
Add initial installed tests
-rw-r--r-- | tests/actions.feature | 67 | ||||
-rw-r--r-- | tests/common_steps.py | 245 | ||||
-rw-r--r-- | tests/environment.py | 47 | ||||
-rw-r--r-- | tests/gnome-logo.png | bin | 0 -> 11104 bytes | |||
-rw-r--r-- | tests/steps/steps.py | 131 |
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 Binary files differnew file mode 100644 index 00000000..e5ea7792 --- /dev/null +++ b/tests/gnome-logo.png 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() + |