[evince/wip/gpoo/initial-test-suite-for-evince] tests: Add tests for Evince




commit 1c0dbebfc01c909f9a269e5370c631fdf3e0a4cf
Author: Shivani Poddar <shivani poddar92 gmail com>
Date:   Tue Aug 19 01:18:40 2014 +0530

    tests: Add tests for Evince
    
    Commit 267172418c004ab9f62d6821e0ce9eac2b31ddae removed previous
    tests because they were done for Gtk2.
    
    Here we provide a new initial test suite for Evince to be integrated
    in continuous integration later on.
    
    Fixes #585

 test/basic-ui-testing.feature |  50 +++++++
 test/behave_common_steps.py   | 321 ++++++++++++++++++++++++++++++++++++++++++
 test/cleanup.py               |   8 ++
 test/environment.py           |  90 ++++++++++++
 test/general.feature          |  23 +++
 test/steps/__init__.py        |   0
 test/steps/steps.py           |  54 +++++++
 test/test-page-labels.pdf     | Bin 0 -> 10150 bytes
 8 files changed, 546 insertions(+)
---
diff --git a/test/basic-ui-testing.feature b/test/basic-ui-testing.feature
new file mode 100644
index 000000000..57b052069
--- /dev/null
+++ b/test/basic-ui-testing.feature
@@ -0,0 +1,50 @@
+Feature: Basic UI Actions
+
+  @test_file_open_button
+  Scenario: Open new file
+    * Make sure that Evince is running
+    * Press "<Ctrl>o"
+    Then Open document window should be open
+
+  @test_document_viewer_settings
+  Scenario: Edit viewer settings
+    * Make sure that Evince is running
+    * Open Document Viewer Settings
+
+  @test_reload_doc
+  Scenario: Check reloading
+    * Make sure that Evince is running
+    * Copy test document to ~/Documents
+    * Press "<Ctrl>o"
+    * Press "<enter>"
+    * Press "<Ctrl>r"
+    #Add the remove part in cleanup eventually
+    * Remove test document from ~/Documents
+
+
+  @test_print_file
+  Scenario: Check Printing of Opened File
+    * Make sure that Evince is running
+    * Copy test document to ~/Documents
+    * Press "<Ctrl>o"
+    * Press "<enter>"
+    * Press "<Ctrl>p"
+    Then Print dialogue should appear
+    * Remove test document from ~/Documents
+
+
+
+#  @test_about_dialog_open
+#  Scenario:Open About dialog
+#    * Start Evince via command
+#    * Make sure that Evince is running
+#    * Open About Document Viewer
+#    * Open Credits
+#    Then Credits should show
+
+#  @test_about_dialog_close
+#  Scenario: Open About Document Viewer
+#    * Open Credits
+#    * Close credits
+#    * Close About document viewer
+#    Then About should not show
diff --git a/test/behave_common_steps.py b/test/behave_common_steps.py
new file mode 100644
index 000000000..279dd1926
--- /dev/null
+++ b/test/behave_common_steps.py
@@ -0,0 +1,321 @@
+# -*- coding: UTF-8 -*-
+from dogtail.utils import isA11yEnabled, enableA11y
+if isA11yEnabled() is False:
+    enableA11y(True)
+
+from time import time, sleep
+from functools import wraps
+from os import strerror, errno, system
+from signal import signal, alarm, SIGALRM
+from subprocess import Popen, PIPE
+from behave import step,then
+from gi.repository import GLib, Gio
+import fcntl, os
+from dogtail.rawinput import keyCombo, click, typeText, absoluteMotion, pressKey
+from dogtail.tree import root, SearchError
+from iniparse import ConfigParser
+import traceback
+from unittest import TestCase
+import logging
+
+
+# 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, desktopFileName = None, 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
+        if desktopFileName is None:
+            desktopFileName = self.appCommand
+        self.desktopFileName = desktopFileName
+        # 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, 30):
+            sleep(1)
+            try:
+                return self.a11yAppName in [x.name for x in root.applications()]
+            except GLib.GError:
+                continue
+        raise Exception("10 at-spi errors, seems that bus is blocked")
+
+    def getDashIconPosition(name):
+        """Get a position of miniature on Overview"""
+        over = root.application('gnome-shell').child(name='Overview')
+        button = over[2].child(name=name)
+        (x, y) = button.position
+        (a, b) = button.size
+        return (x + a / 2, y + b / 2)
+
+    def parseDesktopFile(self):
+        """
+        Getting all necessary data from *.dektop file of the app
+        """
+        cmd = "rpm -qlf $(which %s)" % self.appCommand
+        cmd += '| grep "^/usr/share/applications/.*%s.desktop$"' % self.desktopFileName
+        proc = Popen(cmd, shell=True, stdout=PIPE)
+        # !HAVE TO check if the command and its desktop file exist
+        if proc.wait() != 0:
+            raise Exception("*.desktop file of the app not found")
+        output = proc.communicate()[0].rstrip()
+        self.desktopConfig = ConfigParser()
+        self.desktopConfig.read(output)
+
+    def kill(self):
+        """
+        Kill the app via 'killall'
+        """
+        if self.recordVideo:
+            keyCombo('<Control><Alt><Shift>R')
+
+        try:
+            self.process.kill()
+        except:
+            # Fall back to killall
+            Popen("killall " + self.appCommand, shell=True).wait()
+
+    def getName(self):
+        return self.desktopConfig.get('Desktop Entry', 'name')
+
+    def startViaCommand(self):
+        """
+        Start the app via command
+        """
+        if self.forceKill and self.isRunning():
+            self.kill()
+            sleep(2)
+            assert not self.isRunning(), "Application cannot be stopped"
+
+        self.process = Popen(self.appCommand.split() + self.parameters.split(),
+                             stdout=PIPE, stderr=PIPE, bufsize=0)
+        self.pid = self.process.pid
+
+        assert self.isRunning(), "Application failed to start"
+        return root.application(self.a11yAppName)
+
+    def startViaMenu(self, throughCategories = False):
+        self.parseDesktopFile()
+        if self.forceKill and self.isRunning():
+            self.kill()
+            sleep(2)
+            assert not self.isRunning(), "Application cannot be stopped"
+        try:
+            gnomeShell = root.application('gnome-shell')
+            pressKey('Super_L')
+            sleep(6)
+            if throughCategories:
+                # menu Applications
+                x, y = getDashIconPosition('Show Applications')
+                absoluteMotion(x, y)
+                time.sleep(1)
+                click(x, y)
+                time.sleep(4) # time for all the oversized app icons to appear
+
+                # submenu that contains the app
+                submenu = gnomeShell.child(
+                    name=self.getMenuGroups(), roleName='list item')
+                submenu.click()
+                time.sleep(4)
+
+                # the app itself
+                app = gnomeShell.child(
+                    name=self.getName(), roleName='label')
+                app.click()
+            else:
+                typeText(self.getName())
+                sleep(2)
+                pressKey('Enter')
+
+            assert self.isRunning(), "Application failed to start"
+        except SearchError:
+            print("!!! Lookup error while passing the path")
+
+        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'Press "{sequence}"')
+def press_button_sequence(context, sequence):
+    keyCombo(sequence)
+    sleep(0.5)
+
+
+def wait_for_app_to_appear(context, app):
+    # Waiting for a window to appear
+    for attempt in xrange(0, 10):
+        try:
+            context.app.instance = root.application(app.lower())
+            context.app.instance.child(roleName='frame')
+            break
+        except (GLib.GError, SearchError):
+            sleep(1)
+            continue
+    context.execute_steps("Then %s should start" % app)
+
+
+@step(u'Start {app} via {type:w}')
+def start_app_via_command(context, app, type):
+    for attempt in xrange(0, 10):
+        try:
+            if type == 'command':
+                context.app.startViaCommand()
+            if type == 'menu':
+                context.app.startViaMenu()
+            break
+        except GLib.GError:
+            sleep(1)
+            if attempt == 6:
+                # Killall the app processes if app didn't show up after 5 seconds
+                os.system("pkill -f %s 2&> /dev/null" % app.lower())
+                os.system("python cleanup.py")
+                context.execute_steps("* Start %s via command" % app)
+            continue
+
+@step(u'Close app via gnome panel')
+def close_app_via_gnome_panel(context):
+    context.app.closeViaGnomePanel()
+
+@step(u'Click "Quit" in GApplication menu')
+def close_app_via_shortcut(context) :
+    context.app.closeViaShortcut
+
+@step(u'Make sure that {app} is running')
+def ensure_app_running(context, app):
+    start_app_via_command(context, app, 'command')
+    wait_for_app_to_appear(context, app)
+    logging.debug("app = %s", root.application(app.lower()))
+
+@then(u'{app} should start')
+def test_app_started(context, app):
+    # Dogtail seems to cache applications list
+    # So we should wait for exception here
+    try:
+        root.application(app.lower()).child(roleName='frame')
+    except SearchError:
+        raise RuntimeError("App '%s' is not running" % app)
+
+@then(u"{app} shouldn't be running anymore")
+def then_app_is_dead(context, app):
+    try:
+        root.application(app.lower()).child(roleName='frame')
+        raise RuntimeError("App '%s' is running" % app)
+    except SearchError:
+        pass
+
+def non_block_read(output):
+    fd = output.fileno()
+    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+    try:
+        return output.read()
+    except:
+        return ""
diff --git a/test/cleanup.py b/test/cleanup.py
new file mode 100644
index 000000000..deb5e0826
--- /dev/null
+++ b/test/cleanup.py
@@ -0,0 +1,8 @@
+#!/bin/env python
+
+import os
+from gi.repository import Gio
+
+# Reset GSettings
+for schema in ['org.gnome.Evince']:
+    os.system("gsettings reset-recursively %s" % schema)
diff --git a/test/environment.py b/test/environment.py
new file mode 100644
index 000000000..9f3faac6c
--- /dev/null
+++ b/test/environment.py
@@ -0,0 +1,90 @@
+# -*- coding: UTF-8 -*-
+
+import os
+from behave_common_steps import *
+from dogtail.config import config
+from time import sleep, localtime, strftime
+import problem
+
+
+def before_all(context):
+    """Setup evince stuff
+    Being executed before all features
+    """
+
+    try:
+        # Cleanup abrt crashes
+        [x.delete() for x in problem.list()]
+
+        # Do the cleanup
+        os.system("python cleanup.py > /dev/null")
+
+        # Skip dogtail actions to print to stdout
+        config.logDebugToStdOut = False
+        config.typingDelay = 0.2
+
+        # Kill initial setup
+        os.system("killall /usr/libexec/gnome-initial-setup")
+
+        # Store scenario start time for session logs
+        context.log_start_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
+
+        context.app = App('evince')
+
+    except Exception as e:
+        print("Error in before_all: %s" % e.message)
+
+
+def after_step(context, step):
+    """Teardown after each step.
+    Here we make screenshot and embed it (if one of formatters supports it)
+    """
+    try:
+        if problem.list():
+            problems = problem.list()
+            for crash in problems:
+                if hasattr(context, "embed"):
+                    context.embed('text/plain', "abrt has detected a crash: %s" % crash.reason)
+                else:
+                    print("abrt has detected a crash: %s" % crash.reason)
+
+            # Crash was stored, so it is safe to remove it now
+            [x.delete() for x in problems]
+
+        if step.status == 'failed':
+            # Make screnshot if step has failed
+            if hasattr(context, "embed"):
+                os.system("gnome-screenshot -f /tmp/screenshot.jpg")
+                context.embed('image/jpg', open("/tmp/screenshot.jpg", 'r').read())
+
+            # Test debugging - set DEBUG_ON_FAILURE to drop to ipdb on step failure
+            if os.environ.get('DEBUG_ON_FAILURE'):
+                import ipdb; ipdb.set_trace()  # flake8: noqa
+
+    except Exception as e:
+        print("Error in after_step: %s" % e.message)
+
+
+def after_scenario(context, scenario):
+    """Teardown for each scenario
+    Kill evince (in order to make this reliable we send sigkill)
+    """
+    try:
+        # Stop evince
+        os.system("killall evince &> /dev/null")
+
+        # Attach journalctl logs
+        if hasattr(context, "embed"):
+            os.system("sudo journalctl /usr/bin/gnome-session --no-pager -o cat --since='%s'> 
/tmp/journal-session.log" % context.log_start_time)
+            data = open("/tmp/journal-session.log", 'r').read()
+            if data:
+                context.embed('text/plain', data)
+
+        # Make some pause after scenario
+        sleep(1)
+
+        # Do the cleanup
+        os.system("python cleanup.py")
+    except Exception as e:
+        # Stupid behave simply crashes in case exception has occurred
+        print("Error in after_scenario: %s" % e.message)
diff --git a/test/general.feature b/test/general.feature
new file mode 100644
index 000000000..00bc54c88
--- /dev/null
+++ b/test/general.feature
@@ -0,0 +1,23 @@
+Feature: General actions
+
+  @start_via_command
+  Scenario: Start via command
+    * Start Evince via command
+    Then evince should start
+
+  @start_via_menu
+  Scenario: Start via menu
+    * Start Evince via menu
+    Then evince should start
+
+  @quit_via_shortcut
+  Scenario: Ctrl-Q to quit application
+    * Start Evince via command
+    * Press "<Ctrl>q"
+    Then evince shouldn't be running anymore
+
+  @close_via_gnome_panel
+  Scenario: Close via menu
+    * Start Evince via menu
+    * Click "Quit" in GApplication menu
+    Then Evince shouldn't be running anymore
diff --git a/test/steps/__init__.py b/test/steps/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/steps/steps.py b/test/steps/steps.py
new file mode 100644
index 000000000..ed061b3f9
--- /dev/null
+++ b/test/steps/steps.py
@@ -0,0 +1,54 @@
+from behave import step
+from dogtail.tree import *
+from dogtail.rawinput import *
+from dogtail.utils import *
+from behave_common_steps import *
+
+import os
+os.environ['LANG']='C'
+srcdir = os.getcwd()
+
+@step(u'Open the New file Dialog')
+def open_new_file_dia(context):
+    frame = context.app.instance.child(roleName='frame')
+    filler = frame.child(roleName='filler')
+    toolbar = filler.child(roleName='tool bar')
+    open_button = toolbar[7][0]
+    assert open_button.click(),"Open Dialogue is not clickable"
+
+@then(u'Open document window should be open')
+def check_file_chooser_open(context):
+    file_chooser = context.app.instance.child(roleName='file chooser')
+    assert file_chooser
+
+@step(u'Open Document Viewer Settings')
+def open_settings(context):
+    frame = context.app.instance.child(roleName='frame')
+    filler = frame.child(roleName='filler')
+    toolbar = filler.child(roleName='tool bar')
+    setting_button = toolbar[6][0]
+    assert setting_button.click(), "Settings button is not clickable"
+
+@step(u'Copy test document to ~/Documents')
+def open_new_file(context):
+    global srcdir
+    print srcdir+'/test-page-labels.pdf'
+    os.rename(srcdir+'/test-page-labels.pdf',os.environ["HOME"]+'/Documents/test-page-labels.pdf')
+
+@step(u'Remove test document from ~/Documents')
+def remove_test_doc(context):
+    global srcdir
+    os.rename(os.environ["HOME"]+'/Documents/test-page-labels.pdf', srcdir+'/test-page-labels.pdf')
+
+@then(u'Print dialogue should appear')
+def check_print_dialog(context):
+    print_dialog = context.app.instance.child(roleName='dialog')
+    assert print_dialog
+@step(u'Open About Document Viewer')
+def open_doc_view(context):
+    frame = context.app.instance.child(roleName='frame')
+    filler = frame.child(roleName='filler')
+    toolbar = filler.child(roleName='tool bar')
+    open_button = toolbar[7][0]
+    open_button.click()
+    open_button.pressKey('enter')
diff --git a/test/test-page-labels.pdf b/test/test-page-labels.pdf
new file mode 100644
index 000000000..c7d12671e
Binary files /dev/null and b/test/test-page-labels.pdf differ


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