Python Visual Regression Testing
Needle, Selenium and Nose in Python
Introduction
In this post, we will have a look at using Needle which allows you to automatically check that your visuals render correctly by taking screenshots of portions of a website and comparing them against known good screenshots. We can then use PerceptualDiff as an image comparison utility to show the difference between the screenshots. We will write a simple Selenium test using Needle and PerceptualDiff to automate the checking of the header bar for the Sydney Morning Herald home page.
Installation
Python
Install Python 2.7.10. Please ensure that you allow the installer to update your PATH. As part of your installation, please also ensure that you install pip, which is a tool that allows easy management of any Python packages that you wish to use. Installers for versions prior to Python 2.7.9 will not have pip bundled, so if you do choose to use an earlier version, please ensure you manually install pip.
Ensure that you have successfully installed Python:
bash-3.2$ python --version Python 2.7.10
Ensure that you have successfully installed pip:
bash-3.2$ pip --version pip 6.1.1 from /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages (python 2.7)
You can now use the following commands to install the Selenium, Needle and Nose packages:
bash-3.2$ pip install selenium bash-3.2$ pip install needle bash-3.2$ pip install nose
PerceptualDiff
Download the latest version of PerceptualDiff. Include the PerceptualDiff folder in your PATH
environment variable.
Initial Setup
Create a file such as sydney-morning-herald-network-strip-test.py
and write a simple Selenium test that invokes the setUp()
and tearDown()
methods for initialisation and cleanup of the fixture:
import unittest from selenium import webdriver class SydneyMorningHeraldNetworkStripTest(unittest.TestCase): def setUp(self): self.driver = webdriver.Firefox() def test_check_network_strip_of_sydney_morning_herald_home_page(self): self.driver.set_page_load_timeout(20) self.driver.implicitly_wait(20) self.driver.maximize_window() self.driver.get('http://www.smh.com.au/') def tearDown(self): self.driver.quit()
Run the test using nosetests sydney-morning-herald-network-strip-test.py
:
bash-3.2$ nosetests sydney-morning-herald-network-strip-test.py . ---------------------------------------------------------------------- Ran 1 test in 15.795s OK
Using Needle and PerceptualDiff
To make use of Needle and PerceptualDiff we will have to make a few small changes in sydney-morning-herald-network-strip-test.py
. In this file make the following changes to import and use NeedleTestCase, which also removes the need to use setup()
and tearDown()
:
from needle.cases import NeedleTestCase from selenium import webdriver class SydneyMorningHeraldNetworkStripTest(NeedleTestCase): def test_check_network_strip_of_sydney_morning_herald_home_page(self): self.driver.set_page_load_timeout(20) self.driver.implicitly_wait(20) self.driver.maximize_window() self.driver.get('http://www.smh.com.au/')
Now, also make the following changes to enable the PerceptualDiff engine, which will allow for a visual difference between captures to be generated (the default engine does not provide this):
from needle.cases import NeedleTestCase from selenium import webdriver class SydneyMorningHeraldNetworkStripTest(NeedleTestCase): engine_class = 'needle.engines.perceptualdiff_engine.Engine'
Also make the following changes to set the browser’s viewport (this will allow for consistency between capture sizes):
from needle.cases import NeedleTestCase from selenium import webdriver class SydneyMorningHeraldNetworkStripTest(NeedleTestCase): engine_class = 'needle.engines.perceptualdiff_engine.Engine' viewport_width = 1024 viewport_height = 768
Add the assertScreenshot()
which requires two arguments, a CSS selector for the element that you are capturing and a filename for the captured image.
from needle.cases import NeedleTestCase from selenium import webdriver class SydneyMorningHeraldNetworkStripTest(NeedleTestCase): engine_class = 'needle.engines.perceptualdiff_engine.Engine' viewport_width = 1024 viewport_height = 768 def test_check_network_strip_of_sydney_morning_herald_home_page(self): self.driver.set_page_load_timeout(20) self.driver.implicitly_wait(20) self.driver.maximize_window() self.driver.get('http://www.smh.com.au/') self.assertScreenshot('#network-strip', 'network-strip-capture')
Execution
On first execution of the test, you need to capture a baseline image by using the --with-save-baseline
paramter:
bash-3.2$ nosetests sydney-morning-herald-network-strip-test.py --with-save-baseline . ---------------------------------------------------------------------- Ran 1 test in 24.711s OK
This should now create screenshots/baseline/network-strip-capture.png
. Open the file and you should see a timestamp. Upon a second execution of the test you can remove the --with-save-baseline
parameter, and you should see something similar to this extract:
bash-3.2$ nosetests sydney-morning-herald-network-strip-test.py .. FAIL: Images are visibly different 87 pixels are different .. Ran 1 test in 23.810s FAILED (failures=1)
This should now create screenshots/network-strip-capture.png
. Open the file and you should see a different timestamp. Also open screenshots/network-strip-capture.diff.png
and you should see the area where the difference was found.
Full Example
https://github.com/the-creative-tester/needle-visual-regression-testing-example