[evince/wip/gpoo/initial-test-suite-for-evince] tests: Add tests for Evince
- From: Germán Poo-Caamaño <gpoo src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evince/wip/gpoo/initial-test-suite-for-evince] tests: Add tests for Evince
- Date: Wed, 2 Mar 2022 13:53:58 +0000 (UTC)
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]