Skip to content

Commit

Permalink
automatic screenshot capture on test failure
Browse files Browse the repository at this point in the history
  • Loading branch information
bubenkoff committed Sep 25, 2014
1 parent 055fe56 commit 300077b
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 68 deletions.
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

1.2.0
-----

- automatic screenshot capture on test failure (bubenkoff)
- improvements to the browser preparation procedure (bubenkoff)
- boolean config options made more clear (bubenkoff)


1.1.1
-----

Expand Down
40 changes: 30 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Splinter plugin for the py.test runner
=========================================================
======================================

.. image:: https://api.travis-ci.org/paylogic/pytest-splinter.png
:target: https://travis-ci.org/paylogic/pytest-splinter
Expand All @@ -13,22 +13,22 @@ Splinter plugin for the py.test runner


Install pytest-splinter
===========================
-----------------------

::

pip install pytest-splinter


Features
========
--------

The plugin provides a set of fixtures to use `splinter <http://splinter.cobrateam.info>`_
for browser testing with `pytest <http://pytest.org>`_


Fixtures
========
--------

* browser
Get the splinter's Browser. Fixture is underneath session scoped, so browser process is started
Expand Down Expand Up @@ -106,9 +106,17 @@ Fixtures
* splinter_window_size
Size of the browser window on browser initialization. Tuple in form (<width>, <height>). Default is (1366, 768)

* splinter_screenshot_dir
pytest-splinter browser screenshot directory.
Fixture gets the value from the command-line option `splinter-screenshot-dir` (see below)

* splinter_make_screenshot_on_failure
Should pytest-splinter make browser screenshot on test failure.
Fixture gets the value from the command-line option `splinter-make-screenshot-on-failure` (see below)


Command-line options
====================
--------------------

* `--splinter-implicit-wait`
Selenium webdriver implicit wait. Seconds (default: 1).
Expand Down Expand Up @@ -136,11 +144,17 @@ Command-line options
For more details, refer to splinter and selenium documentation.

* `--splinter-session-scoped-browser`
pytest-splinter should use single browser instance per test session. (set by default).
pytest-splinter should use single browser instance per test session. Choise of 'true' or 'false'. (default: 'true').

* `--splinter-make-screenshot-on-failure`
pytest-splinter should make browser screenshot on test failure. Choise of 'true' or 'false'. (default: 'true').

* `--splinter-screenshot-dir`
pytest-splinter browser screenshot directory. By default it's current directory.


Browser fixture
===============
---------------

As mentioned above, browser is a fixture made by creating splinter's Browser object, but with some overrides.

Expand All @@ -156,8 +170,9 @@ As mentioned above, browser is a fixture made by creating splinter's Browser obj
which is in general performance-wise not a good idea. Also normally when you interact with the browser as a user,
you don't need the status code of the page.


Several browsers for your test
==============================
------------------------------

You can have several browsers in one test.

Expand All @@ -175,14 +190,19 @@ You can have several browsers in one test.
assert browser.url == 'http://example.com'
Automatic screenshots of on test failure
----------------------------------------



Python3 support
===============
---------------

Python3 is supported, check if you have recent version of splinter as it was added recently.


Example
=======
-------

test_your_test.py:

Expand Down
2 changes: 1 addition & 1 deletion pytest_splinter/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.1.1'
__version__ = '1.2.0'
165 changes: 121 additions & 44 deletions pytest_splinter/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
Provides easy interface for the browser from your tests providing the `browser` fixture
which is an object of splinter Browser class.
"""
import os.path
import copy # pragma: no cover
import functools # pragma: no cover
import mimetypes # pragma: no cover

import pytest # pragma: no cover
import py # pragma: no cover
import splinter # pragma: no cover
from _pytest import junitxml

from selenium.webdriver.support import wait

Expand Down Expand Up @@ -162,7 +164,19 @@ def splinter_window_size():
@pytest.fixture(scope='session')
def splinter_session_scoped_browser(request):
"""Flag to keep single browser per test session."""
return request.config.option.splinter_session_scoped_browser
return request.config.option.splinter_session_scoped_browser == 'true'


@pytest.fixture(scope='session')
def splinter_make_screenshot_on_failure(request):
"""Flag to make browser screenshot on test failure."""
return request.config.option.splinter_make_screenshot_on_failure == 'true'


@pytest.fixture(scope='session') # pragma: no cover
def splinter_screenshot_dir(request):
"""Browser screenshot directory."""
return os.path.abspath(request.config.option.splinter_screenshot_dir)


@pytest.fixture(scope='session')
Expand Down Expand Up @@ -194,18 +208,21 @@ def browser_patches(splinter_selenium_socket_timeout):
def browser_instance_getter(
request,
browser_patches,
splinter_selenium_socket_timeout,
splinter_selenium_implicit_wait,
splinter_selenium_speed,
splinter_webdriver,
splinter_remote_url,
splinter_browser_load_condition,
splinter_browser_load_timeout,
splinter_file_download_dir,
splinter_download_file_types,
splinter_firefox_profile_preferences,
splinter_driver_kwargs,
splinter_file_download_dir,
splinter_firefox_profile_preferences,
splinter_make_screenshot_on_failure,
splinter_remote_url,
splinter_screenshot_dir,
splinter_selenium_implicit_wait,
splinter_selenium_socket_timeout,
splinter_selenium_speed,
splinter_webdriver,
splinter_window_size,
tmpdir,
browser_pool,
):
"""Splinter browser instance getter. To be used for getting of plugin.Browser's instances.
Expand Down Expand Up @@ -242,62 +259,122 @@ def prepare_browser(parent):
request.addfinalizer(browser.quit)
elif not browser:
browser = browser_pool[browser_key] = get_browser()
else:
try:
browser.driver.implicitly_wait(splinter_selenium_implicit_wait)
browser.driver.set_speed(splinter_selenium_speed)
if splinter_window_size:
browser.driver.set_window_size(*splinter_window_size)
browser.driver.delete_all_cookies()
browser.visit_condition = splinter_browser_load_condition
browser.visit_condition_timeout = splinter_browser_load_timeout
browser.driver.get('about:blank')
except IOError:
# we lost browser, try to restore the justice
try:
browser.driver.implicitly_wait(splinter_selenium_implicit_wait)
browser.driver.set_speed(splinter_selenium_speed)
if splinter_window_size:
browser.driver.set_window_size(*splinter_window_size)
browser.driver.delete_all_cookies()
except IOError:
# we lost browser, try to restore the justice
try:
browser.quit()
except Exception:
pass
browser = browser_pool[browser_key] = get_browser()

browser.visit_condition = splinter_browser_load_condition
browser.visit_condition_timeout = splinter_browser_load_timeout
browser.driver.get('about:blank')
browser.quit()
except Exception:
pass
browser = browser_pool[browser_key] = get_browser()
prepare_browser(parent)

def make_screenshot_on_failure():
if splinter_make_screenshot_on_failure and request.node.splinter_failure:
slaveoutput = getattr(request.config, 'slaveoutput', None)
names = junitxml.mangle_testnames(request.node.nodeid.split("::"))
classname = '.'.join(names[:-1])
screenshot_dir = os.path.join(splinter_screenshot_dir, classname)
screenshot_file_name = '{0}.png'.format(names[-1])
if not slaveoutput:
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
else:
screenshot_dir = tmpdir.mkdir('screenshots').strpath
screenshot_path = os.path.join(screenshot_dir, screenshot_file_name)
browser.driver.save_screenshot(screenshot_path)
with open(screenshot_path) as fd:
if slaveoutput is not None:
slaveoutput.setdefault('screenshots', []).append({
'class_name': classname,
'file_name': screenshot_file_name,
'content': fd.read()
})
request.addfinalizer(make_screenshot_on_failure)

return browser

return prepare_browser


@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call, __multicall__):
"""Assign the report to the item for futher usage."""
rep = __multicall__.execute()
if rep.outcome != 'passed':
item.splinter_failure = rep
else:
item.splinter_failure = None
return rep


@pytest.fixture
def browser(browser_instance_getter):
def browser(request, browser_instance_getter):
"""Browser fixture."""
return browser_instance_getter(browser)


class SplinterPlugin(object):

"""Plugin class to defer pytest-xdist hook handler."""

def pytest_testnodedown(self, node, error):
"""Copy screenshots back from remote nodes to have them on the master."""
config_screenshot_dir = splinter_screenshot_dir(node)
for screenshot in getattr(node, 'slaveoutput', {}).get('screenshots', []):
screenshot_dir = os.path.join(config_screenshot_dir, screenshot['class_name'])
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
with open(os.path.join(screenshot_dir, screenshot['file_name']), 'w') as fd:
fd.write(screenshot['content'])


def pytest_configure(config):
"""Register pytest-splinter's deferred plugin."""
config.pluginmanager.register(SplinterPlugin())


def pytest_addoption(parser): # pragma: no cover
"""Pytest hook to add custom command line option(s)."""
parser.addoption(
group = parser.getgroup("splinter", "splinter integration for browser testing")
group.addoption(
"--splinter-webdriver",
help="pytest-splinter webdriver", type="choice", choices=list(splinter.browser._DRIVERS.keys()),
dest='splinter_webdriver', default='firefox')

parser.addoption(
dest='splinter_webdriver', metavar="DRIVER", default='firefox')
group.addoption(
"--splinter-remote-url",
help="pytest-splinter remote webdriver url ", dest='splinter_remote_url', default=None)

parser.addoption(
help="pytest-splinter remote webdriver url ", metavar="URL", dest='splinter_remote_url', default=None)
group.addoption(
"--splinter-implicit-wait",
help="pytest-splinter selenium implicit wait, seconds", type="int",
dest='splinter_webdriver_implicit_wait', default=1)

parser.addoption(
dest='splinter_webdriver_implicit_wait', metavar="SECONDS", default=1)
group.addoption(
"--splinter-speed",
help="pytest-splinter selenium speed, seconds", type="int",
dest='splinter_webdriver_speed', default=0)

parser.addoption(
dest='splinter_webdriver_speed', metavar="SECONDS", default=0)
group.addoption(
"--splinter-socket-timeout",
help="pytest-splinter socket timeout, seconds", type="int",
dest='splinter_webdriver_socket_timeout', default=120)

parser.addoption(
dest='splinter_webdriver_socket_timeout', metavar="SECONDS", default=120)
group.addoption(
"--splinter-session-scoped-browser",
help="pytest-splinter should use single browser instance per test session", action="store_true",
dest='splinter_session_scoped_browser', default=True)
help="pytest-splinter should use single browser instance per test session. Defaults to true.", action="store",
dest='splinter_session_scoped_browser', metavar="false|true", type="choice", choices=['false', 'true'],
default='true')
group.addoption(
"--splinter-make-screenshot-on-failure",
help="pytest-splinter should make browser screenshot on test failure. Defaults to true.", action="store",
dest='splinter_make_screenshot_on_failure', metavar="false|true", type="choice", choices=['false', 'true'],
default='true')
group.addoption(
"--splinter-screenshot-dir",
help="pytest-splinter browser screenshot directory. By default it's current directory.", action="store",
dest='splinter_screenshot_dir', metavar="DIR", default='.')
10 changes: 6 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ def run_tests(self):

setup(
name='pytest-splinter',
description='Splinter subplugin for Pytest BDD plugin',
description='Splinter plugin for pytest testing framework',
long_description=long_description,
author='Paylogic developers',
license='MIT license',
author_email='developers@paylogic.com',
version=pytest_splinter.__version__,
cmdclass={'test': Tox},
url='https://github.com/paylogic/pytest-splinter',
url='https://github.com/pytest-dev/pytest-splinter',
install_requires=[
'setuptools',
'splinter',
Expand All @@ -65,8 +65,10 @@ def run_tests(self):
'Topic :: Utilities',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
] + [('Programming Language :: Python :: %s' % x) for x in '2.6 2.7 3.0 3.1 3.2 3.3'.split()],
] + [('Programming Language :: Python :: %s' % x) for x in '2.6 2.7 3.0 3.1 3.2 3.3 3.4'.split()],
tests_require=['detox'],
entry_points={'pytest11': ['pytest-splinter=pytest_splinter.plugin']},
entry_points={'pytest11': [
'pytest-splinter=pytest_splinter.plugin',
]},
packages=['pytest_splinter'],
)
Loading

0 comments on commit 300077b

Please sign in to comment.