Skip to content

Commit

Permalink
Merge pull request #12 from jlane9/property_refactor
Browse files Browse the repository at this point in the history
Property refactor
  • Loading branch information
jlane9 committed Feb 2, 2018
2 parents 9f81acd + 3b3ac01 commit b6180cb
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 55 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
language: python
sudo: required
python:
- "2.7"
- "3.6"
# command to install dependencies
install:
- pip install -e .
- pip install -r requirements.txt
- sudo apt-get install imagemagick perceptualdiff
before_script:
- pytest --driver BrowserStack --capability os "OS X" --capability os_version "El Capitan" --needle-save-baseline test/
# command to run tests
Expand Down
214 changes: 166 additions & 48 deletions pytest_needle/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,32 +57,9 @@ def __init__(self, driver, **kwargs):
self.options = kwargs
self.driver = driver

self.save_baseline = kwargs.get('save_baseline', False)
self.cleanup_on_success = kwargs.get('cleanup_on_success', False)

self.baseline_dir = kwargs.get('baseline_dir', DEFAULT_BASELINE_DIR)
self.output_dir = kwargs.get('output_dir', DEFAULT_OUTPUT_DIR)

# Create the output and baseline directories if they do not yet exist.
for directory in (self.baseline_dir, self.output_dir):
self._create_dir(directory)

dimensions = kwargs.get('viewport_size', DEFAULT_VIEWPORT_SIZE)
viewport_size = re.match(r'(?P<width>\d+)\s?[xX]\s?(?P<height>\d+)', dimensions)

# Set viewport position, size
self.driver.set_window_position(0, 0)
viewport_dimensions = (int(viewport_size.group('width')), int(viewport_size.group('height'))) if viewport_size \
else (int(DEFAULT_VIEWPORT_SIZE.split('x')[0]), int(DEFAULT_VIEWPORT_SIZE.split('x')[1]))

self.driver.set_window_size(*viewport_dimensions)

# Instantiate the diff engine
engine_config = kwargs.get('needle_engine', 'pil').lower()
self.engine_class = self.ENGINES.get(engine_config, DEFAULT_ENGINE)

klass = import_from_string(self.engine_class)
self.engine = klass()
self.set_viewport()

@staticmethod
def _create_dir(directory):
Expand Down Expand Up @@ -173,6 +150,77 @@ def _get_window_size(self):
window_size = self.driver.get_window_size()
return window_size['width'], window_size['height']

@property
def baseline_dir(self):
"""Return baseline image path
:return:
:rtype: str
"""

return self.options.get('baseline_dir', DEFAULT_BASELINE_DIR)

@baseline_dir.setter
def baseline_dir(self, value):
"""Set baseline image directory
:param str value: File path
:return:
"""

assert isinstance(value, basestring)
self.options['baseline_dir'] = value

@property
def cleanup_on_success(self):
"""Returns True, if cleanup on success flag is set
:return:
:rtype: bool
"""

return self.options.get('cleanup_on_success', False)

@cleanup_on_success.setter
def cleanup_on_success(self, value):
"""Set cleanup on success flag
:param bool value: Cleanup on success flag
:return:
"""

self.options['cleanup_on_success'] = bool(value)

@property
def engine(self):
"""Return image processing engine
:return:
"""

return import_from_string(self.engine_class)()

@property
def engine_class(self):
"""Return image processing engine name
:return:
:rtype: str
"""

return self.ENGINES.get(self.options.get('needle_engine', 'pil').lower(), DEFAULT_ENGINE)

@engine_class.setter
def engine_class(self, value):
"""Set image processing engine name
:param str value: Image processing engine name (pil, imagemagick, perceptualdiff)
:return:
"""

assert value.lower() in self.ENGINES
self.options['needle_engine'] = value.lower()

def get_screenshot(self, element=None):
"""Returns screenshot image
Expand Down Expand Up @@ -248,41 +296,35 @@ def assert_screenshot(self, file_path, element_or_selector=None, threshold=0, ex

element = self._find_element(element_or_selector)

# Get baseline screenshot
self._create_dir(self.baseline_dir)
baseline_image = os.path.join(self.baseline_dir, '%s.png' % file_path) \
if isinstance(file_path, basestring) else Image.open(file_path).convert('RGB')

# Take screenshot and exit if in baseline saving mode
if self.save_baseline:
return self.get_screenshot_as_image(element, exclude=exclude).save(baseline_image)

# Get fresh screenshot
self._create_dir(self.output_dir)
fresh_image = self.get_screenshot_as_image(element, exclude=exclude)
fresh_image_file = os.path.join(self.output_dir, '%s.png' % file_path)
fresh_image.save(fresh_image_file)

# Get baseline image
if isinstance(file_path, basestring):
baseline_image = os.path.join(self.baseline_dir, '%s.png' % file_path)
if self.save_baseline:

# Take screenshot and exit
return self.get_screenshot_as_image(element, exclude=exclude).save(baseline_image)

else:

if not os.path.exists(baseline_image):
raise IOError('The baseline screenshot %s does not exist. '
'You might want to re-run this test in baseline-saving mode.'
% baseline_image)
else:
# Comparing in-memory files instead of on-disk files
baseline_image = Image.open(file_path).convert('RGB')
# Error if there is not a baseline image to compare
if not self.save_baseline and not isinstance(file_path, basestring) and not os.path.exists(baseline_image):
raise IOError('The baseline screenshot %s does not exist. You might want to '
're-run this test in baseline-saving mode.' % baseline_image)

# Compare images
if isinstance(baseline_image, basestring):
try:
self.engine.assertSameFiles(fresh_image_file, baseline_image, threshold)

except AssertionError as err:
msg = err.message \
if hasattr(err, "message") \
else err.args[0] if err.args else ""
msg = err.message if hasattr(err, "message") else err.args[0] if err.args else ""
args = err.args[1:] if len(err.args) > 1 else []
raise ImageMismatchException(
msg, baseline_image, fresh_image_file, args)
raise ImageMismatchException(msg, baseline_image, fresh_image_file, args)

finally:
if self.cleanup_on_success:
Expand All @@ -294,5 +336,81 @@ def assert_screenshot(self, file_path, element_or_selector=None, threshold=0, ex
distance = abs(diff.get_distance())

if distance > threshold:
pytest.fail('Fail: New screenshot did not match the '
'baseline (by a distance of %.2f)' % distance)
pytest.fail('Fail: New screenshot did not match the baseline (by a distance of %.2f)' % distance)

@property
def output_dir(self):
"""Return output image path
:return:
:rtype: str
"""

return self.options.get('output_dir', DEFAULT_OUTPUT_DIR)

@output_dir.setter
def output_dir(self, value):
"""Set output image directory
:param str value: File path
:return:
"""

assert isinstance(value, basestring)
self.options['output_dir'] = value

@property
def save_baseline(self):
"""Returns True, if save baseline flag is set
:return:
:rtype: bool
"""

return self.options.get('save_baseline', False)

@save_baseline.setter
def save_baseline(self, value):
"""Set save baseline flag
:param bool value: Save baseline flag
:return:
"""

self.options['save_baseline'] = bool(value)

def set_viewport(self):
"""Set viewport width, height based off viewport size
:return:
"""

viewport_size = re.match(r'(?P<width>\d+)\s?[xX]\s?(?P<height>\d+)', self.viewport_size)

viewport_dimensions = (viewport_size.group('width'), viewport_size.group('height')) if viewport_size \
else DEFAULT_VIEWPORT_SIZE.split('x')

self.driver.set_window_size(*[int(dimension) for dimension in viewport_dimensions])

@property
def viewport_size(self):
"""Return setting for browser window size
:return:
:rtype: str
"""

return self.options.get('viewport_size', DEFAULT_VIEWPORT_SIZE)

@viewport_size.setter
def viewport_size(self, value):
"""Set setting for browser window size
:param value: Browser window size, as string or (x,y)
:return:
"""

assert isinstance(value, (basestring, list, tuple))
assert len(value) == 2 and all([isinstance(i, int) for i in value]) \
if isinstance(value, (list, tuple)) else True
self.options['viewport_size'] = value if isinstance(value, basestring) else '{}x{}'.format(*value)
105 changes: 105 additions & 0 deletions test/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

import os
import pytest
from selenium.webdriver.common.by import By

Expand Down Expand Up @@ -63,3 +64,107 @@ def test_example_element(needle):

# Take an element screen diff
needle.assert_screenshot('search_field', (By.ID, 'tsf'), threshold=80)


@pytest.mark.cleanup
def test_cleanup_on_success(needle):
"""Verify that the --needle-cleanup-on-success removes the newly generated file
:param NeedleDriver needle: NeedleDriver instance
:return:
"""

screenshot_path = os.path.join(needle.output_dir, "cleanup_test.png")

# Set cleanup on success to true
needle.cleanup_on_success = True

# Navigate to web page
needle.driver.get('https://www.example.com')

# Take a entire page screen diff
needle.assert_screenshot('cleanup_test', threshold=80)

assert not os.path.exists(screenshot_path)


@pytest.mark.output_dir
def test_output_dir(needle):
"""Verify that the --needle-output-dir saves the fresh image in the specified directory
:param NeedleDriver needle: NeedleDriver instance
:return:
"""

# Reassign output_dir
needle.output_dir = os.path.join(needle.output_dir, 'extra')
needle._create_dir(needle.output_dir)

screenshot_path = os.path.join(needle.output_dir, "output_dir_test.png")

# Navigate to web page
needle.driver.get('https://www.example.com')

# Take a entire page screen diff
needle.assert_screenshot('output_dir_test', threshold=80)

if not needle.save_baseline:
assert os.path.exists(screenshot_path)


@pytest.mark.baseline_dir
def test_baseline_dir(needle):
"""Verify that the --needle-baseline-dir saves the fresh image in the specified directory
:param NeedleDriver needle: NeedleDriver instance
:return:
"""

# Reassign output_dir
needle.baseline_dir = os.path.join(needle.baseline_dir, 'default')
needle._create_dir(needle.baseline_dir)

screenshot_path = os.path.join(needle.baseline_dir, "baseline_dir_test.png")

# Navigate to web page
needle.driver.get('https://www.example.com')

# Take a entire page screen diff
needle.assert_screenshot('baseline_dir_test', threshold=80)

assert os.path.exists(screenshot_path)


@pytest.mark.viewport
def test_viewport_size(needle):
"""Verify that viewport size can be
:param NeedleDriver needle: NeedleDriver instance
:return:
"""

original_size = needle.driver.get_window_size()

needle.viewport_size = "900x600"
needle.set_viewport()

assert needle.driver.get_window_size() != original_size


@pytest.mark.engine
@pytest.mark.parametrize('engine', ('pil', 'perceptualdiff', 'imagemagick'))
def test_image_engine(needle, engine):
"""Verify all image engines can be set
:param NeedleDriver needle: NeedleDriver instance
:return:
"""

needle.engine_class = engine
assert needle.engine_class == needle.ENGINES[engine]

# Navigate to web page
needle.driver.get('https://www.example.com')

# Take a entire page screen diff
needle.assert_screenshot('test_' + engine, threshold=80)

0 comments on commit b6180cb

Please sign in to comment.