[
  {
    "path": ".gitignore",
    "content": "*.pyc\n*.egg-info\n.DS_Store\n*~\n.*.swo\n.*.swp\n.*.swn\n.*.swm\n/.tox/\n/build/\n/dist/\nselenium-server-standalone-*.jar\n/docs/_build\ntests/screenshots/*.png\ngeckodriver.log\nghostdriver.log\n__pycache__\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\nsudo: required\ndist: trusty\npython:\n  - \"2.7\"\n  - \"3.2\"\n  - \"3.3\"\n  - \"3.4\"\n  - \"3.5\"\n  - \"3.6\"\n# Firefox 48+ only works with geckodriver, first supported properly in selenium 3.0.0.b3\n# See https://github.com/SeleniumHQ/selenium/issues/2739#issuecomment-249482533\nenv:\n  - SELENIUM_VERSION='<3' NEEDLE_BROWSER=chrome\n  - SELENIUM_VERSION='<4' NEEDLE_BROWSER=chrome\n  - SELENIUM_VERSION='<4' NEEDLE_BROWSER=firefox\n  - SELENIUM_VERSION='<3' NEEDLE_BROWSER=phantomjs\n  - SELENIUM_VERSION='<4' NEEDLE_BROWSER=phantomjs\nbefore_install:\n  - if [[ \"$NEEDLE_BROWSER\" != \"phantomjs\" ]]; then export DISPLAY=:99.0; fi\n  - if [[ \"$NEEDLE_BROWSER\" != \"phantomjs\" ]]; then sh -e /etc/init.d/xvfb start; fi\n  - if [[ \"$NEEDLE_BROWSER\" != \"phantomjs\" ]]; then sleep 3; fi # give xvfb some time to start\n  - if [[ \"$NEEDLE_BROWSER\" == \"firefox\" ]]; then wget https://github.com/mozilla/geckodriver/releases/download/v0.15.0/geckodriver-v0.15.0-linux64.tar.gz; fi\n  - if [[ \"$NEEDLE_BROWSER\" == \"firefox\" ]]; then mkdir geckodriver; fi\n  - if [[ \"$NEEDLE_BROWSER\" == \"firefox\" ]]; then tar -xzf geckodriver-v0.15.0-linux64.tar.gz -C geckodriver; fi\n  - if [[ \"$NEEDLE_BROWSER\" == \"firefox\" ]]; then export PATH=$PATH:$PWD/geckodriver; fi\n  - sudo apt-get update -qq\n  - sudo apt-get install -y perceptualdiff imagemagick\n  - if [[ \"$NEEDLE_BROWSER\" == \"chrome\" ]]; then sudo apt-get install -y chromium-chromedriver; fi\n  - if [[ \"$NEEDLE_BROWSER\" == \"chrome\" ]]; then export PATH=$PATH:/usr/lib/chromium-browser/; fi\naddons:\n  firefox: '52.0'\ninstall:\n  - pip install \"selenium ${SELENIUM_VERSION}\"\n  - pip install -e .\nscript:\n  - nosetests -v\n"
  },
  {
    "path": "CHANGES.md",
    "content": "Change log\n==========\n\nUpcoming 0.5.0\n--------------\n\n- Dropped Python 2.6 support.\n- Verified support for Python 3.2-3.6.\n- Firefox improvements: use FirefoxWebElement and geckodriver by default.\n\n0.4.1 (2017-01-04)\n------------------\n\n- Fixed a race condition in creating baseline and output directories.\n\n0.4.0 (2016-12-21)\n------------------\n\n- Added support for Selenium 3.\n- Added ImageMagick engine.\n- Fixed an issue with false positives yielded by the Perceptual engine.\n\n0.3.0 (2015-02-22)\n------------------\n\n- Added the cleanup_on_success option for deleting screenshots after successful test runs.\n\n0.2.4 (2015-02-08)\n------------------\n\n- Use selenium native element location instead of javascript call in order to assure crossplatform compatability.\n\n0.2.3 (2015-01-17)\n------------------\n\n- Fixed a unicode error (issue #23).\n\n0.2.2 (2014-08-16)\n------------------\n\n- Fixed an issue with saving screenshots on Windows.\n\n0.2.1 (2014-04-10)\n------------------\n\n - Fixed a regression in the PIL engine.\n\n0.2.0 (2014-04-07)\n------------------\n\n - Added support for Python 3.\n - Changed default threshold from 0.1 to 0.\n - Added configurable way of plugging external diff engines like PerceptualDiff.\n - Removed the necessity to run the Selenium server by using a Firefox web\n   driver instance by default. This is slightly backwards-incompatible if you\n   relied on the now-removed `driver_command_executor`,\n   `driver_desired_capabilities` and `driver_browser_profile` attributes.\n   To control the logic for selecting the proper web driver, you may simply\n   override the `get_web_driver()` method.\n - The `--with-needle-capture` and `NeedleTestCase.capture` options were\n   deprecated and will be removed in version 0.4.0. Instead, you should now\n   respectively use the new, more explicit `--with-save-baseline` and\n   `NeedleTestCase.save_baseline` options. Note that those new options will\n   systematically cause the baseline image files to be saved on disk,\n   overwriting potentially existing baseline files.\n - Removed the `NeedleWebElement.get_computed_property()` method. Instead, you\n   may use Selenium's built-in `value_of_css_property()` method.\n - Upgraded vendored jQuery to version 11.0.\n\n0.1.0 (2014-02-20)\n------------------\n\n - Add `set_viewport_size()` method to `NeedleTestCase`\n - Calculate the dimensions of elements more accurately with jQuery\n - Only load jQuery if it hasn't already been loaded\n\nThanks @jphalip!\n\n0.0.2 (2013-10-24)\n------------------\n\n - Allow needle to be used with custom web driver\n - Replace PIL with pillow\n\nThanks @treyhunner!\n\n0.0.1 (2013-05-07)\n------------------\n\nInitial release.\n\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2011, Ben Firshman\nAll rights reserved.\n \nRedistribution and use in source and binary forms, with or without \nmodification, are permitted provided that the following conditions are met:\n \n * Redistributions of source code must retain the above copyright notice, this \n   list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice, \n   this list of conditions and the following disclaimer in the documentation \n   and/or other materials provided with the distribution.\n * The names of its contributors may not be used to endorse or promote products \n   derived from this software without specific prior written permission.\n \nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE \nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR \nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; \nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON \nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT \n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS \nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\ninclude *.md\nrecursive-include needle/js *\n"
  },
  {
    "path": "README.md",
    "content": "Needle\n======\n\n[![Build Status](https://travis-ci.org/python-needle/needle.png?branch=master)](https://travis-ci.org/python-needle/needle)\n\nNeedle is a tool for testing visuals with [Selenium](http://seleniumhq.org/) \nand [nose](https://nose.readthedocs.io/).\n\nIt checks that visuals (CSS/fonts/images/SVG/etc.) render correctly by taking\nscreenshots of portions of a website and comparing them against known good\nscreenshots. It also provides tools for testing calculated CSS values and the\nposition of HTML elements.\n\nExample\n-------\n\nThis is what a Needle test case looks like:\n\n```python\nfrom needle.cases import NeedleTestCase\n\nclass BBCNewsTest(NeedleTestCase):\n    def test_masthead(self):\n        self.driver.get('http://www.bbc.co.uk/news/')\n        self.assertScreenshot('#blq-mast', 'bbc-masthead')\n```\n\nThis example checks for regressions in the appearance of the BBC's masthead.\n\nDocumentation\n-------------\n\nFull documentation available on [Read the Docs](https://needle.readthedocs.io/).\n\nIf you'd like to build the documentation yourself, first install ``sphinx``:\n\n    pip install sphinx\n    \nThen run:\n\n    cd docs\n    make html\n    \nThe documentation will then be available browsable from\n``docs/_build/index.html``.\n\nRunning Needle's test suite\n---------------------------\n\nFirst install tox (usually via ``pip install tox``).  Then:\n\n    $ tox\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/Needle.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/Needle.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/Needle\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Needle\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\tmake -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Needle documentation build configuration file, created by\n# sphinx-quickstart on Tue Apr  5 19:53:10 2011.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys, os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.txt'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'Needle'\ncopyright = u'2011, Ben Firshman'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '0.1a1'\n# The full version, including alpha/beta/rc tags.\nrelease = '0.1a1'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'nature'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'Needledoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\n# The paper size ('letter' or 'a4').\n#latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n#latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'Needle.tex', u'Needle Documentation',\n   u'Ben Firshman', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Additional stuff for the LaTeX preamble.\n#latex_preamble = ''\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'needle', u'Needle Documentation',\n     [u'Ben Firshman'], 1)\n]\n"
  },
  {
    "path": "docs/index.txt",
    "content": "Needle: Automated tests for your visuals\n========================================\n\nNeedle is a tool for testing your CSS and visuals with `Selenium <http://seleniumhq.org/>`_\nand `nose <https://nose.readthedocs.io/>`_.\n\nIt checks that visuals (CSS/fonts/images/SVG/etc.) render correctly by taking\nscreenshots of portions of a website and comparing them against known good\nscreenshots. It also provides tools for testing calculated CSS values and the\nposition of HTML elements.\n\nInstallation\n------------\n\nIf you haven't got `pip <http://www.pip-installer.org/>`_ installed::\n\n    $ sudo easy_install pip\n\nAs root, or in a `virtualenv <http://www.virtualenv.org/>`_::\n\n    $ pip install selenium\n    $ pip install needle\n\n\nGetting started\n---------------\n\nCreate ``test_bbc.py`` in an empty directory::\n\n    from needle.cases import NeedleTestCase\n\n    class BBCNewsTest(NeedleTestCase):\n        def test_masthead(self):\n            self.driver.get('http://www.bbc.co.uk/news/')\n            self.assertScreenshot('#blq-mast', 'bbc-masthead')\n\nThis is a test case which tells the Selenium web driver (by default Firefox)\nto open BBC News and check the bar across the top of the page looks correct.\n:py:meth:`~needle.cases.NeedleTestCase.assertScreenshot` take two arguments: a\nCSS selector for the element we are capturing and a filename for the image.\n\nTo create an initial screenshot of the logo, we need to run Needle in\n'baseline saving' mode::\n\n    $ nosetests test_bbc.py --with-save-baseline\n\nThis will create ``screenshots/baseline/bbc-masthead.png``. Open it up and\ncheck it looks okay.\n\nNow if we run our tests, it will take the same screenshot and check it against\nthe screenshot on disk::\n\n    $ nosetests test_bbc.py\n\nIf a regression in your CSS causes them to become significantly different, the\ntest will fail.\n\n\nSelecting a WebDriver\n---------------------\n\nYou may control which browser is used by Needle by overriding the\n``get_web_driver()`` method::\n\n    from needle.cases import NeedleTestCase\n    from needle.driver import NeedlePhantomJS\n\n    class MyTests(NeedleTestCase):\n\n        @classmethod\n        def get_web_driver(cls):\n            return NeedlePhantomJS()\n\n        def test_something(self):\n            ...\n\nBy default Needle uses ``NeedleFirefox``, which is a wrapper of Selenium's\nbuilt-in ``selenium.webdriver.firefox.webdriver.WebDriver`` class. You may use\nany of the following WebDrivers: ``NeedleRemote``, ``NeedlePhantomJS``,\n``NeedleFirefox``, ``NeedleChrome``, ``NeedleIe``, ``NeedleOpera`` and\n``NeedleSafari``. Refer to Selenium's documentation to learn how to install and\nconfigure any of those WebDrivers.\n\n\nSetting the viewport's size\n---------------------------\n\nYou may set the size of the browser's viewport using the\n``set_viewport_size()`` method::\n\n    from needle.cases import NeedleTestCase\n\n    class MyTests(NeedleTestCase):\n\n        def test_something(self):\n            self.set_viewport_size(width=1024, height=768)\n            ...\n\nThis is particularly useful to predict the size of the resulting screenshots\nwhen taking fullscreen captures, or to test responsive sites.\n\nYou may also set the default viewport size for all your tests with the\n``viewport_width`` and ``viewport_height`` class attributes::\n\n    from needle.cases import NeedleTestCase\n\n    class MyTests(NeedleTestCase):\n        viewport_width = 1024\n        viewport_height = 768\n\n        ...\n\nEngines\n-------\n\nBy default Needle uses the PIL engine (``needle.engines.pil_engine.Engine``)\nto take screenshots. Instead of PIL, you may also use PerceptualDiff or\nImageMagick.\n\nExample with PerceptualDiff::\n\n    from needle.cases import NeedleTestCase\n\n    class MyTests(NeedleTestCase):\n        engine_class = 'needle.engines.perceptualdiff_engine.Engine'\n\n        def test_something(self):\n            ...\n\nExample with ImageMagick::\n\n    from needle.cases import NeedleTestCase\n\n    class MyTests(NeedleTestCase):\n        engine_class = 'needle.engines.imagemagick_engine.Engine'\n\n        def test_something(self):\n            ...\n\nBesides being much faster than PIL, PerceptualDiff and ImageMagick also\ngenerate a diff PNG file when a test fails, highlighting the differences\nbetween the baseline image and the new screenshot.\n\nNote that to use the PerceptualDiff engine you will first need to\n`download <http://pdiff.sourceforge.net/>`_ the ``perceptualdiff`` binary and\nplace it in your ``PATH``.\n\nTo use the ImageMagick engine you will need to install a package on your\nmachine (e.g. ``sudo apt-get install imagemagick`` on Ubuntu or\n``brew install imagemagick`` on OSX).\n\nFile cleanup\n------------\n\nEach time you run tests, Needle will create new screenshot images on disk, for\ncomparison with the baseline screenshots. It's then up to you whether you want\nto delete them or archive them.\n\nSet the ``cleanup_on_success`` class attribute to ``True`` to delete these\nfiles for all successful tests. Any screenshots that differ from the baseline\nwill remain on disk for your inspection. Example::\n\n    from needle.cases import NeedleTestCase\n\n    class MyTests(NeedleTestCase):\n        cleanup_on_success = True\n\n        def test_something(self):\n            ...\n\nBy default, ``cleanup_on_success`` is ``False``.\n\nYou may also activate the file cleanup from the command line by using the\n``--with-needle-cleanup-on-success`` nose plugin:\n\n    $ nosetests --with-needle-cleanup-on-success\n\nIndices and tables\n------------------\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\Needle.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\Needle.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "# Used by ReadTheDocs for documentation builds\nSphinx==1.5.3\n"
  },
  {
    "path": "needle/__init__.py",
    "content": "__version__ = '0.5.0'\n__author__ = 'Ben Firshman'\n__contact__ = 'ben@firshman.co.uk'\n__homepage__ = 'https://github.com/python-needle/needle'\n\n"
  },
  {
    "path": "needle/cases.py",
    "content": "# encoding: utf-8\nfrom __future__ import absolute_import\nfrom __future__ import print_function\n\nfrom warnings import warn\nfrom contextlib import contextmanager\nfrom errno import EEXIST\nimport os\nimport sys\nimport time\n\nif sys.version_info > (2, 7):\n    from unittest import TestCase\nelse:\n    from unittest2 import TestCase\n\nif sys.version_info >= (3, 0):\n    basestring = str\n\n\nfrom PIL import Image\n\nfrom selenium.common.exceptions import WebDriverException\n\nfrom needle.engines.pil_engine import ImageDiff\nfrom needle.driver import (NeedleFirefox, NeedleChrome, NeedleIe, NeedleOpera,\n                           NeedleSafari, NeedlePhantomJS, NeedleWebElementMixin)\n\nDRIVER_ACQUISITION_TIMEOUT = 5  # seconds\n\n\ndef _object_filename(obj):\n    return os.path.abspath(sys.modules[type(obj).__module__].__file__)\n\n\ndef import_from_string(path):\n    \"\"\"\n    Utility function to dynamically load a class specified by a string,\n    e.g. 'path.to.my.Class'.\n    \"\"\"\n    module_name, klass = path.rsplit('.', 1)\n    module = __import__(module_name, fromlist=[klass])\n    return getattr(module, klass)\n\n\nclass NeedleTestCase(TestCase):\n    \"\"\"\n    A `unittest2 <http://www.voidspace.org.uk/python/articles/unittest2.shtml>`_\n    test case which provides tools for testing CSS with Selenium.\n    \"\"\"\n\n    driver = None\n\n    capture = False  # Deprecated\n    save_baseline = False\n    cleanup_on_success = False\n\n    viewport_width = 1024\n    viewport_height = 768\n\n    output_directory = None\n    baseline_directory = None\n\n    engine_class = 'needle.engines.pil_engine.Engine'\n\n    @classmethod\n    def setUpClass(cls):\n        if os.environ.get('NEEDLE_CAPTURE'):\n            cls.capture = True\n        if os.environ.get('NEEDLE_SAVE_BASELINE'):\n            cls.save_baseline = True\n        if os.environ.get('NEEDLE_CLEANUP_ON_SUCCESS'):\n            cls.cleanup_on_success = True\n\n        # Instantiate the diff engine\n        klass = import_from_string(cls.engine_class)\n        cls.engine = klass()\n\n        cls.driver = cls.get_web_driver()\n        cls.driver.set_window_position(0, 0)\n        cls.set_viewport_size(cls.viewport_width, cls.viewport_height)\n        super(NeedleTestCase, cls).setUpClass()\n\n    @classmethod\n    def tearDownClass(cls):\n        if isinstance(cls.driver, NeedlePhantomJS):\n            # Workaround for https://github.com/SeleniumHQ/selenium/issues/767\n            cls.driver.service.send_remote_shutdown_command()\n            cls.driver.service._cookie_temp_file = None\n        cls.driver.quit()\n        super(NeedleTestCase, cls).tearDownClass()\n\n    @classmethod\n    def get_web_driver(cls):\n        \"\"\"\n        Returns the WebDriver instance to be used. Defaults to `NeedleFirefox()`.\n        Override this method if you'd like to control the logic for choosing\n        the proper WebDriver instance.\n        \"\"\"\n        browser_name = os.environ.get('NEEDLE_BROWSER')\n        browser_map = {\n            'firefox': NeedleFirefox,\n            'chrome': NeedleChrome,\n            'ie': NeedleIe,\n            'opera': NeedleOpera,\n            'safari': NeedleSafari,\n            'phantomjs': NeedlePhantomJS,\n        }\n        browser_class = browser_map.get(browser_name, NeedleFirefox)\n        # Allow a few retries to get the driver, in case it isn't quite ready yet\n        start_time = time.time()\n        while True:\n            try:\n                browser = browser_class()\n                break\n            except Exception as e:\n                if (not isinstance(e, WebDriverException)) and e.__class__.__name__ != 'WebDriverException':\n                    # nose likes to change selenium's WebDriverException to \"nose.proxy.WebDriverException\"\n                    raise\n                if time.time() - start_time >= DRIVER_ACQUISITION_TIMEOUT:\n                    raise\n                time.sleep(1)\n        return browser\n\n    def __init__(self, *args, **kwargs):\n        super(NeedleTestCase, self).__init__(*args, **kwargs)\n        # TODO: should output directory be timestamped?\n        if self.output_directory is None:\n            self.output_directory = os.environ.get('NEEDLE_OUTPUT_DIR', os.path.realpath(os.path.join(os.getcwd(), 'screenshots')))\n        # TODO: Should baseline be a top-level peer to output_directory?\n        if self.baseline_directory is None:\n            self.baseline_directory = os.environ.get('NEEDLE_BASELINE_DIR', os.path.realpath(os.path.join(os.getcwd(), 'screenshots', 'baseline')))\n\n        # Create the output and baseline directories if they do not yet exist.\n        for dirname in (self.baseline_directory, self.output_directory):\n            # Recursively create the directory, handling its\n            # prior existence as a valid exception.\n            # This will guard against race conditions.\n            # E.g. when running tests in multithreaded mode\n            # they likely have the same directories specified\n            # and might encounter this block at the same time.\n            try:\n                os.makedirs(dirname)\n            except OSError as err:\n                if err.errno == EEXIST and os.path.isdir(dirname):\n                    pass\n                else:\n                    raise\n\n    @classmethod\n    def set_viewport_size(cls, width, height):\n        cls.driver.set_window_size(width, height)\n\n        # Measure the difference between the actual document width and the\n        # desired viewport width so we can account for scrollbars:\n        measured = cls.driver.execute_script(\"return {width: document.body.clientWidth, height: document.body.clientHeight};\")\n        delta = width - measured['width']\n\n        cls.driver.set_window_size(width + delta, height)\n\n    def assertScreenshot(self, element_or_selector, file, threshold=0):\n        \"\"\"assert-style variant of compareScreenshot context manager\n\n        compareScreenshot() can be considerably more efficient for recording baselines by avoiding the need\n        to load pages before checking whether we're actually going to save them. This function allows you\n        to continue using normal unittest-style assertions if you don't need the efficiency benefits\n        \"\"\"\n\n        with self.compareScreenshot(element_or_selector, file, threshold=threshold):\n            pass\n\n    @contextmanager\n    def compareScreenshot(self, element_or_selector, file, threshold=0):\n        \"\"\"\n        Assert that a screenshot of an element is the same as a screenshot on disk,\n        within a given threshold.\n\n        :param element_or_selector:\n            Either a CSS selector as a string or a\n            :py:class:`~needle.driver.NeedleWebElementMixin` object that represents\n            the element to capture.\n        :param file:\n            If a string, then assumed to be the filename for the screenshot,\n            which will be appended with ``.png``. Otherwise assumed to be\n            a file object for the baseline image.\n        :param threshold:\n            The threshold for triggering a test failure.\n        \"\"\"\n\n        yield  # To allow using this method as a context manager\n\n        if not isinstance(element_or_selector, NeedleWebElementMixin):\n            element = self.driver.find_element_by_css_selector(element_or_selector)\n        else:\n            element = element_or_selector\n\n        if not isinstance(file, basestring):\n            # Comparing in-memory files instead of on-disk files\n            baseline_image = Image.open(file).convert('RGB')\n            fresh_screenshot = element.get_screenshot()\n            diff = ImageDiff(fresh_screenshot, baseline_image)\n            distance = abs(diff.get_distance())\n            if distance > threshold:\n                raise AssertionError(\"The new screenshot did not match \"\n                                     \"the baseline (by a distance of %.2f)\"\n                                     % distance)\n        else:\n            baseline_file = os.path.join(self.baseline_directory, '%s.png' % file)\n            output_file = os.path.join(self.output_directory, '%s.png' % file)\n\n            # Determine whether we should save the baseline image\n            save_baseline = False\n            if self.save_baseline:\n                save_baseline = True\n            elif self.capture:\n                warn(\"The 'NeedleTestCase.capture' attribute and '--with-save-baseline' nose option \"\n                     \"are deprecated since version 0.2.0. Use 'save_baseline' and '--with-save-baseline' \"\n                     \"instead. See the changelog for more information.\",\n                     PendingDeprecationWarning)\n                if os.path.exists(baseline_file):\n                    self.skipTest('Not capturing %s, its baseline image already exists. If you '\n                                  'want to capture this element again, delete %s'\n                                  % (file, baseline_file))\n                else:\n                    save_baseline = True\n\n            if save_baseline:\n                # Save the baseline screenshot and bail out\n                element.get_screenshot().save(baseline_file)\n                return\n            else:\n                if not os.path.exists(baseline_file):\n                    raise IOError('The baseline screenshot %s does not exist. '\n                                  'You might want to re-run this test in baseline-saving mode.'\n                                  % baseline_file)\n\n                # Save the new screenshot\n                element.get_screenshot().save(output_file)\n\n                try:\n                    self.engine.assertSameFiles(output_file, baseline_file, threshold)\n                except:\n                    raise\n                else:\n                    if self.cleanup_on_success:\n                        os.remove(output_file)\n"
  },
  {
    "path": "needle/driver.py",
    "content": "# encoding: utf-8\nfrom __future__ import absolute_import\n\nimport base64\nimport os\nimport sys\n\nif sys.version_info >= (3, 0):\n    from urllib.parse import quote\n    from io import BytesIO as IOClass\nelse:\n    from urllib import quote\n    try:\n        from cStringIO import StringIO as IOClass\n    except ImportError:\n        from StringIO import StringIO as IOClass\n\n\nfrom PIL import Image\n\nfrom selenium.common.exceptions import WebDriverException\nfrom selenium.webdriver.remote.webelement import WebElement\nfrom selenium.webdriver.firefox.webdriver import WebDriver as Firefox\nfrom selenium.webdriver.chrome.webdriver import WebDriver as Chrome\nfrom selenium.webdriver.ie.webdriver import WebDriver as Ie\nfrom selenium.webdriver.opera.webdriver import WebDriver as Opera\nfrom selenium.webdriver.safari.webdriver import WebDriver as Safari\nfrom selenium.webdriver.phantomjs.webdriver import WebDriver as PhantomJS\nfrom selenium.webdriver.remote.webdriver import WebDriver as Remote\n\ntry:\n    # Added in selenium 3.0.0.b3\n    from selenium.webdriver.firefox.webelement import FirefoxWebElement\nexcept ImportError:\n    from selenium.webdriver.remote.webelement import WebElement as FirefoxWebElement\n\n\nclass NeedleWebElementMixin(object):\n    \"\"\"\n    An element on a page that Selenium has opened.\n\n    It is a Selenium :py:class:`~selenium.webdriver.remote.webelement.WebElement`\n    object with some extra methods for testing CSS.\n    \"\"\"\n    def get_dimensions(self):\n        \"\"\"\n        Returns a dictionary containing, in pixels, the element's ``width`` and\n        ``height``, and it's ``left`` and ``top`` position relative to the document.\n        \"\"\"\n        location = self.location\n        size = self.size\n        return {\n            \"top\": location['y'],\n            \"left\": location['x'],\n            \"width\": size['width'],\n            \"height\": size['height']\n        }\n\n    def get_screenshot(self):\n        \"\"\"\n        Returns a screenshot of this element as a PIL image.\n        \"\"\"\n        d = self.get_dimensions()\n        \n        # Cast values to int in order for _ImageCrop not to break\n        d['left'] = int(d['left'])\n        d['top'] = int(d['top'])\n        d['width'] = int(d['width'])\n        d['height'] = int(d['height'])\n\n        try:\n            # For selenium >= 2.46.1, W3C WebDriver spec drivers (like geckodriver)\n            fh = IOClass(self.screenshot_as_png)\n            image = Image.open(fh).convert('RGB')\n            # Make sure it isn't actually a full-page screenshot (PhantomJS)\n            if image.size == (d['width'], d['height']):\n                return image\n        except (AttributeError, WebDriverException):\n            # Fall back to cropping a full page screenshot\n            image = self._parent.get_screenshot_as_image()\n\n        return image.crop((\n            d['left'],\n            d['top'],\n            d['left'] + d['width'],\n            d['top'] + d['height'],\n        ))\n\n\nclass NeedleWebDriverMixin(object):\n    \"\"\"\n    Selenium WebDriver mixin with some extra methods for testing CSS.\n    \"\"\"\n    def load_html(self, html):\n        \"\"\"\n        Similar to :py:meth:`get`, but instead of passing a URL to load in the\n        browser, the HTML for the page is provided.\n        \"\"\"\n        self.get('data:text/html,' + quote(html))\n\n    def get_screenshot_as_image(self):\n        \"\"\"\n        Returns a screenshot of the current page as an RGB\n        `PIL image <http://www.pythonware.com/library/pil/handbook/image.htm>`_.\n        \"\"\"\n        fh = IOClass(base64.b64decode(self.get_screenshot_as_base64().encode('ascii')))\n        return Image.open(fh).convert('RGB')\n\n    def load_jquery(self):\n        \"\"\"\n        Loads jQuery onto the current page so calls to\n        :py:meth:`execute_script` have access to it.\n        \"\"\"\n        if (self.execute_script('return typeof(jQuery)') == 'undefined'):\n            self.execute_script(open(\n                os.path.join(self._get_js_path(), 'jquery-1.11.0.min.js')\n            ).read() + '\\nreturn \"\";')\n\n    def _get_js_path(self):\n        return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'js')\n\n    def create_web_element(self, element_id, *args, **kwargs):\n        if isinstance(self, NeedleFirefox):\n            return NeedleFirefoxWebElement(self, element_id, w3c=self.w3c, *args, **kwargs)\n        else:\n            return NeedleWebElement(self, element_id, w3c=self.w3c, *args, **kwargs)\n\n\nclass NeedleRemote(NeedleWebDriverMixin, Remote):\n    \"\"\"\n    The same as Selenium's remote WebDriver, but with NeedleWebDriverMixin's\n    functionality.\n    \"\"\"\n\nclass NeedlePhantomJS(NeedleWebDriverMixin, PhantomJS):\n    \"\"\"\n    The same as Selenium's PhantomJS WebDriver, but with NeedleWebDriverMixin's\n    functionality.\n    \"\"\"\n\nclass NeedleFirefox(NeedleWebDriverMixin, Firefox):\n    \"\"\"\n    The same as Selenium's Firefox WebDriver, but with NeedleWebDriverMixin's\n    functionality.\n    \"\"\"\n\nclass NeedleChrome(NeedleWebDriverMixin, Chrome):\n    \"\"\"\n    The same as Selenium's Chrome WebDriver, but with NeedleWebDriverMixin's\n    functionality.\n    \"\"\"\n\nclass NeedleIe(NeedleWebDriverMixin, Ie):\n    \"\"\"\n    The same as Selenium's Internet Explorer WebDriver, but with\n    NeedleWebDriverMixin's functionality.\n    \"\"\"\n\nclass NeedleOpera(NeedleWebDriverMixin, Opera):\n    \"\"\"\n    The same as Selenium's Opera WebDriver, but with NeedleWebDriverMixin's\n    functionality.\n    \"\"\"\n\nclass NeedleSafari(NeedleWebDriverMixin, Safari):\n    \"\"\"\n    The same as Selenium's Safari WebDriver, but with NeedleWebDriverMixin's\n    functionality.\n    \"\"\"\n\nclass NeedleWebElement(NeedleWebElementMixin, WebElement):\n    \"\"\"\n    The same as Selenium's WebElement, but with NeedleWebElementMixin's\n    functionality.\n    \"\"\"\n\nclass NeedleFirefoxWebElement(NeedleWebElementMixin, FirefoxWebElement):\n    \"\"\"\n    The same as Selenium's FirefoxWebElement, but with NeedleWebElementMixin's\n    functionality.\n    \"\"\"\n"
  },
  {
    "path": "needle/engines/__init__.py",
    "content": ""
  },
  {
    "path": "needle/engines/base.py",
    "content": "class EngineBase(object):\n    \"\"\"\n    Base class for diff engines.\n    \"\"\"\n\n    def assertSameFiles(self, output_file, baseline_file, threshold):\n        raise NotImplementedError"
  },
  {
    "path": "needle/engines/imagemagick_engine.py",
    "content": "import os\nimport subprocess\n\nfrom needle.engines.base import EngineBase\n\n\nclass Engine(EngineBase):\n    compare_path = \"compare\"\n    compare_command = (\"{compare} -metric RMSE -subimage-search -dissimilarity-threshold 1.0 {baseline} \"\n                       \"{new} {diff}\")\n\n    def assertSameFiles(self, output_file, baseline_file, threshold=0):\n        diff_file = output_file.replace('.png', '.diff.png')\n\n        compare_cmd = self.compare_command.format(\n            compare=self.compare_path,\n            baseline=baseline_file,\n            new=output_file,\n            diff=diff_file)\n        process = subprocess.Popen(compare_cmd, shell=True,\n                                   stdout=subprocess.PIPE,\n                                   stderr=subprocess.PIPE)\n        compare_stdout, compare_stderr = process.communicate()\n\n        difference = float(compare_stderr.split()[1][1:-1])\n        if difference <= threshold:\n            os.remove(diff_file)\n            return\n\n        raise AssertionError(\"The new screenshot '{new}' did not match \"\n                             \"the baseline '{baseline}' (See {diff}):\\n\"\n                             \"{stdout}{stderr}\"\n                             .format(new=output_file,\n                                     baseline=baseline_file,\n                                     diff=diff_file,\n                                     stdout=compare_stdout,\n                                     stderr=compare_stderr))"
  },
  {
    "path": "needle/engines/perceptualdiff_engine.py",
    "content": "import subprocess\nimport os\n\nfrom PIL import Image\n\nfrom needle.engines.base import EngineBase\n\n\nclass Engine(EngineBase):\n\n    perceptualdiff_path = 'perceptualdiff'\n    perceptualdiff_output_png = True\n\n    def assertSameFiles(self, output_file, baseline_file, threshold):\n        # Calculate threshold value as a pixel number instead of percentage.\n        width, height = Image.open(output_file).size\n        threshold = int(width * height * threshold)\n\n        diff_ppm = output_file.replace(\".png\", \".diff.ppm\")\n        cmd = \"%s -threshold %d -output %s %s %s\" % (\n            self.perceptualdiff_path, threshold, diff_ppm, baseline_file, output_file)\n        process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n        perceptualdiff_stdout, _ = process.communicate()\n\n        # Sometimes perceptualdiff returns a false positive with this exact message:\n        # 'FAIL: Images are visibly different\\n0 pixels are different\\n\\n'\n        # We catch that here.\n        if process.returncode == 0 or b'\\n0 pixels are different' in perceptualdiff_stdout:\n            # No differences found, but make sure to clean up the .ppm in case it was created.\n            if os.path.exists(diff_ppm):\n                os.remove(diff_ppm)\n            return\n        else:\n            if os.path.exists(diff_ppm):\n                if self.perceptualdiff_output_png:\n                    # Convert the .ppm output to .png\n                    diff_png = diff_ppm.replace(\"diff.ppm\", \"diff.png\")\n                    Image.open(diff_ppm).save(diff_png)\n                    os.remove(diff_ppm)\n                    diff_file_msg = ' (See %s)' % diff_png\n                else:\n                    diff_file_msg = ' (See %s)' % diff_ppm\n            else:\n                diff_file_msg = ''\n            raise AssertionError(\"The new screenshot '%s' did not match \"\n                                 \"the baseline '%s'%s:\\n%s\"\n                                 % (output_file, baseline_file, diff_file_msg, perceptualdiff_stdout))\n"
  },
  {
    "path": "needle/engines/pil_engine.py",
    "content": "import sys\nfrom itertools import chain\nimport math\n\nif sys.version_info >= (3, 0):\n    izip = zip\nelse:\n    from itertools import izip\n\nfrom PIL import Image\n\nfrom needle.engines.base import EngineBase\n\n\nclass Engine(EngineBase):\n\n    def assertSameFiles(self, output_file, baseline_file, threshold):\n        output_image = Image.open(output_file).convert('RGB')\n        baseline_image = Image.open(baseline_file).convert('RGB')\n        diff = ImageDiff(output_image, baseline_image)\n        distance = abs(diff.get_distance())\n        if distance > threshold:\n            raise AssertionError(\"The new screenshot '%s' did not match \"\n                                 \"the baseline '%s' (by a distance of %.2f)\"\n                                 % (output_file, baseline_file, distance))\n\n\nclass ImageDiff(object):\n    \"\"\"\n    Utility class for performing image comparisons using PIL.\n    \"\"\"\n\n    def __init__(self, image_a, image_b):\n        assert image_a.size == image_b.size\n        assert image_a.getbands() == image_b.getbands()\n\n        self.image_a = image_a\n        self.image_b = image_b\n\n    def get_nrmsd(self):\n        \"\"\"\n        Returns the normalised root mean squared deviation of the two images.\n        \"\"\"\n        a_values = chain(*self.image_a.getdata())\n        b_values = chain(*self.image_b.getdata())\n        rmsd = 0\n        for a, b in izip(a_values, b_values):\n            rmsd += (a - b) ** 2\n        rmsd = math.sqrt(float(rmsd) / (\n            self.image_a.size[0] * self.image_a.size[1] * len(self.image_a.getbands())\n        ))\n        return rmsd / 255\n\n    def get_distance(self):\n        \"\"\"\n        Returns the distance between the two images in pixels.\n        \"\"\"\n        a_values = chain(*self.image_a.getdata())\n        b_values = chain(*self.image_b.getdata())\n        band_len = len(self.image_a.getbands())\n        distance = 0\n        for a, b in izip(a_values, b_values):\n            distance += abs(float(a) / band_len - float(b) / band_len) / 255\n        return distance"
  },
  {
    "path": "needle/js/__init__.py",
    "content": ""
  },
  {
    "path": "needle/plugin.py",
    "content": "from nose.plugins import Plugin\n\n\nclass NeedleCapturePlugin(Plugin):\n    \"\"\"\n    A nose plugin which causes all calls to\n    ``NeedleTestCase.assertScreenshot`` to save a baseline screenshot to disk,\n    unless the baseline file already exists.\n    \"\"\"\n    name = 'needle-capture'\n\n    def wantClass(self, cls):\n        # Only gather classes which are a needle test case\n        return hasattr(cls, 'assertScreenshot')\n\n    def wantFunction(self, f):\n        return False\n\n    def beforeTest(self, test):\n        if hasattr(test, 'test'):\n            test.test.capture = True\n\n\nclass SaveBaselinePlugin(Plugin):\n    \"\"\"\n    A nose plugin which causes all calls to ``NeedleTestCase.assertScreenshot``\n    to save the baseline screenshot to disk.\n    \"\"\"\n    name = 'save-baseline'\n\n    def add_options(self, parser, env=None):\n        super(SaveBaselinePlugin, self).add_options(parser, env)\n\n    def wantClass(self, cls):\n        # Only gather classes which are a needle test case\n        return hasattr(cls, 'assertScreenshot')\n\n    def wantFunction(self, f):\n        return False\n\n    def beforeTest(self, test):\n        if hasattr(test, 'test'):\n            test.test.save_baseline = True\n\n\nclass CleanUpOnSuccessPlugin(Plugin):\n    \"\"\"\n    A nose plugin that causes all successful ``NeedleTestCase.assertScreenshot``\n    calls to delete the non-baseline file from disk.\n    \"\"\"\n    name = 'needle-cleanup-on-success'\n\n    def add_options(self, parser, env=None):\n        super(CleanUpOnSuccessPlugin, self).add_options(parser, env)\n\n    def wantClass(self, cls):\n        # Only gather classes which are a needle test case\n        return hasattr(cls, 'assertScreenshot')\n\n    def wantFunction(self, f):\n        return False\n\n    def beforeTest(self, test):\n        if hasattr(test, 'test'):\n            test.test.cleanup_on_success = True\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom setuptools import setup, find_packages\nimport sys\nimport os\nimport codecs\n\nfrom needle import __version__\n\n# Borrowed from\n# https://github.com/jezdez/django_compressor/blob/develop/setup.py\ndef read(*parts):\n    return codecs.open(os.path.join(os.path.dirname(__file__), *parts)).read()\n\n\ninstall_requires = [\n    'nose>=1.0.0',\n    'selenium>=2,<4',\n    'pillow',\n]\n\nif sys.version_info < (2, 7, 0):\n    # Install backport of unittest2 only if needed.\n    install_requires.append('unittest2>=0.5.1')\n\n\nsetup(\n    name='needle',\n    version=__version__,\n    description='Automated testing for your CSS.',\n    author='Ben Firshman',\n    author_email='ben@firshman.co.uk',\n    url='https://github.com/python-needle/needle',\n    packages=find_packages(exclude=['scripts', 'tests']),\n    package_data={'needle': ['js/*']},\n    test_suite='nose.collector',\n    entry_points = {\n        'nose.plugins.0.10': [\n            'needle-capture = needle.plugin:NeedleCapturePlugin',\n            'save-baseline = needle.plugin:SaveBaselinePlugin',\n            'needle-cleanup-on-success = needle.plugin:CleanUpOnSuccessPlugin',\n        ]\n    },\n    install_requires=install_requires,\n    classifiers=[\n        'Development Status :: 5 - Production/Stable',\n        'Environment :: Web Environment',\n        'Intended Audience :: Developers',\n        'License :: OSI Approved :: BSD License',\n        'Operating System :: OS Independent',\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 2',\n        'Programming Language :: Python :: 2.7',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3.2',\n        'Programming Language :: Python :: 3.3',\n        'Programming Language :: Python :: 3.4',\n        'Programming Language :: Python :: 3.5',\n        'Programming Language :: Python :: 3.6',\n        'Topic :: Internet :: WWW/HTTP',\n        'Topic :: Internet :: WWW/HTTP :: Browsers',\n        'Topic :: Multimedia :: Graphics :: Capture :: Screen Capture',\n        'Topic :: Software Development :: Libraries :: Python Modules',\n        'Topic :: Software Development :: Testing',\n    ],\n)\n\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "import sys\n\nfrom PIL import Image, ImageDraw\n\nif sys.version_info >= (3, 0):\n    from io import BytesIO as IOClass\nelse:\n    try:\n        from cStringIO import StringIO as IOClass\n    except ImportError:\n        from StringIO import StringIO as IOClass\n\n\nclass ImageTestCaseMixin(object):\n\n    def get_image(self, colour):\n        return Image.new('RGB', (100, 100), colour)\n\n    def get_black_image(self):\n        return self.get_image((0, 0, 0))\n\n    def get_white_image(self):\n        return self.get_image((255, 255, 255))\n\n    def get_half_filled_image(self):\n        im = self.get_black_image()\n        draw = ImageDraw.Draw(im)\n        draw.rectangle(\n            ((0, 0), (49, 100)),\n            fill=(255, 255, 255)\n        )\n        return im\n\n    def load_black_div(self, text=''):\n        self.driver.load_html('''\n            <style type=\"text/css\">\n                #black-box {\n                    position: absolute;\n                    left: 50px;\n                    top: 100px;\n                    width: 100px;\n                    height: 100px;\n                    background-color: black;\n                    color: white;\n                }\n            </style>\n            <div id=\"black-box\">%s</div>\n        ''' % text)\n\n    def save_image_to_fh(self, im):\n        fh = IOClass()\n        im.save(fh, 'PNG')\n        fh.seek(0)\n        return fh"
  },
  {
    "path": "tests/plugin_test_cases/red_box.py",
    "content": "from needle.cases import NeedleTestCase\nfrom tests import ImageTestCaseMixin\n\nclass RedBoxTestCase(ImageTestCaseMixin, NeedleTestCase):\n    def test_red_box(self):\n        self.driver.load_html(\"\"\"\n            <style type=\"text/css\">\n                #test {\n                    position: absolute;\n                    left: 50px;\n                    top: 100px;\n                    width: 100px;\n                    height: 100px;\n                    background-color: red;\n                }\n            </style>\n            <div id=\"test\"></div>\n        \"\"\")\n        self.assertScreenshot(self.driver.find_element_by_id('test'), 'red_box')"
  },
  {
    "path": "tests/test_diff.py",
    "content": "import math\nimport sys\n\nif sys.version_info > (2, 7):\n    from unittest import TestCase\nelse:\n    from unittest2 import TestCase\n\nfrom needle.engines.pil_engine import ImageDiff\nfrom . import ImageTestCaseMixin\n\n\nclass TestImageDiff(ImageTestCaseMixin, TestCase):\n\n    def test_nrmsd_all_channels(self):\n        diff = ImageDiff(self.get_white_image(), self.get_black_image())\n        self.assertEqual(diff.get_nrmsd(), 1)\n\n    def test_nrmsd_one_channel(self):\n        diff = ImageDiff(self.get_image((255, 0, 0)), self.get_black_image())\n        self.assertEqual(diff.get_nrmsd(), math.sqrt(1.0 / 3))\n\n    def test_nrmsd_half_filled(self):\n        diff = ImageDiff(self.get_black_image(), self.get_half_filled_image())\n        self.assertEqual(diff.get_nrmsd(), math.sqrt(0.5))\n\n    def test_distance_all_channels(self):\n        diff = ImageDiff(self.get_white_image(), self.get_black_image())\n        self.assertAlmostEqual(diff.get_distance(), 100 * 100, delta=0.001)\n\n    def test_distance_one_channel(self):\n        diff = ImageDiff(self.get_image((255, 0, 0)), self.get_black_image())\n        self.assertAlmostEqual(diff.get_distance(), 10000.0 / 3, delta=0.001)\n\n    def test_distance_half_filled(self):\n        diff = ImageDiff(self.get_black_image(), self.get_half_filled_image())\n        self.assertAlmostEqual(diff.get_distance(), 10000.0 / 2, delta=0.001)\n\n\n\n"
  },
  {
    "path": "tests/test_driver.py",
    "content": "from needle.cases import NeedleTestCase\n\nclass TestWebDriver(NeedleTestCase):\n\n    def test_load_html(self):\n        self.driver.load_html('<div id=\"test\">foo</div>')\n        e = self.driver.find_element_by_id('test')\n        self.assertEqual(e.text, 'foo')\n\n    def test_load_html_works_with_large_pages(self):\n        div = '<div>' + 'a' * 1000 + '</div>'\n        html = ''.join(div for _ in range(500)) + '<div id=\"test\">hello</div>'\n        self.driver.load_html(html)\n        self.assertEqual(\n            self.driver.execute_script(\n                'return document.getElementsByTagName(\"div\").length'\n            ),\n            501\n        )\n        e = self.driver.find_element_by_id('test')\n        self.assertEqual(e.text, 'hello')\n\n    def test_load_jquery(self):\n        self.driver.load_html('<div></div>')\n        self.driver.load_jquery()\n        self.assertTrue(self.driver.execute_script(\"\"\"\n            return jQuery !== undefined;\n        \"\"\"))\n\n\nclass TestWebElement(NeedleTestCase):\n\n    def test_get_dimensions(self):\n        self.driver.load_html(\"\"\"\n            <style type=\"text/css\">\n                #test {\n                    position: absolute;\n                    left: 50px;\n                    top: 100px;\n                    width: 150px;\n                    height: 200px;\n                }\n            </style>\n            <div id=\"test\">Test</div>\n        \"\"\")\n        e = self.driver.find_element_by_id('test')\n        self.assertEqual(e.get_dimensions(), {\n            'left': 50,\n            'top': 100,\n            'width': 150,\n            'height': 200,\n        })\n\n    def test_get_screenshot(self):\n        self.driver.load_html(\"\"\"\n            <style type=\"text/css\">\n                #test {\n                    position: absolute;\n                    left: 50px;\n                    top: 100px;\n                    width: 150px;\n                    height: 200px;\n                    background-color: #FF0000;\n                }\n            </style>\n            <div id=\"test\"></div>\n        \"\"\")\n        e = self.driver.find_element_by_id('test')\n        im = e.get_screenshot()\n        self.assertEqual(im.size, (150, 200))\n        for pixel in im.getdata():\n            self.assertEqual(pixel, (255, 0, 0))\n"
  },
  {
    "path": "tests/test_imagemagick_engine.py",
    "content": "from __future__ import absolute_import\nimport subprocess\nimport unittest\nfrom os import devnull\n\nfrom tests.test_perceptualdiff_engine import PerceptualdiffEngineTests\n\n\nclass ImageMagickEngineTests(PerceptualdiffEngineTests):\n    engine_class = 'needle.engines.imagemagick_engine.Engine'\n\n    @classmethod\n    def setUpClass(cls):\n        try:\n            subprocess.call(['compare', '--version'], stdout=open(devnull), stderr=open(devnull))\n        except OSError:\n            raise unittest.SkipTest('ImageMagick is not installed')\n        super(ImageMagickEngineTests, cls).setUpClass()\n\n\n# Delete the imported module so it doesn't get executed here.\ndel PerceptualdiffEngineTests"
  },
  {
    "path": "tests/test_in_memory.py",
    "content": "from __future__ import with_statement\nfrom needle.cases import NeedleTestCase\n\nfrom . import ImageTestCaseMixin\n\n\nclass InMemoryTests(ImageTestCaseMixin, NeedleTestCase):\n\n    def create_div(self):\n        self.driver.load_html(\"\"\"\n            <style type=\"text/css\">\n                #test {\n                    position: absolute;\n                    left: 50px;\n                    top: 100px;\n                    width: 100px;\n                    height: 100px;\n                    background-color: black;\n                }\n            </style>\n            <div id=\"test\"></div>\n        \"\"\")\n\n    def test_assertScreenshot(self):\n        self.create_div()\n        self.assertScreenshot(\n            self.driver.find_element_by_id('test'),\n            self.save_image_to_fh(self.get_black_image())\n        )\n\n    def test_assertScreenshot_with_css_selector(self):\n        self.create_div()\n        self.assertScreenshot(\n            '#test',\n            self.save_image_to_fh(self.get_black_image())\n        )\n\n    def test_assertScreenshot_fails(self):\n        self.create_div()\n        im = self.get_black_image()\n        # Create one red pixel\n        im.putpixel((0, 0), (255, 0, 0))\n        with self.assertRaises(AssertionError):\n            # Default threshold for error is 0\n            self.assertScreenshot(\n                self.driver.find_element_by_id('test'),\n                self.save_image_to_fh(im)\n            )\n\n    def test_assertScreenshot_does_not_fail_with_threshold(self):\n        self.create_div()\n        im = self.get_black_image()\n        # Create one red pixel\n        im.putpixel((0, 0), (255, 0, 0))\n        self.assertScreenshot(\n            self.driver.find_element_by_id('test'),\n            self.save_image_to_fh(im),\n            threshold=1\n        )\n\n    def test_assertScreenshot_fails_with_threshold(self):\n        self.create_div()\n        im = self.get_black_image()\n        # Create two white pixels\n        im.putpixel((0, 0), (255, 255, 255))\n        im.putpixel((1, 0), (255, 255, 255))\n        with self.assertRaises(AssertionError):\n            self.assertScreenshot(\n                self.driver.find_element_by_id('test'),\n                self.save_image_to_fh(im),\n                threshold=1\n            )"
  },
  {
    "path": "tests/test_perceptualdiff_engine.py",
    "content": "from __future__ import absolute_import\nimport subprocess\nimport unittest\nfrom os import path, devnull\n\nfrom tests.test_pil_engine import PILEngineTests\n\n\nclass PerceptualdiffEngineTests(PILEngineTests):\n    engine_class = 'needle.engines.perceptualdiff_engine.Engine'\n\n    @classmethod\n    def setUpClass(cls):\n        try:\n            subprocess.call(['perceptualdiff', '--version'], stdout=open(devnull), stderr=open(devnull))\n        except OSError:\n            raise unittest.SkipTest('perceptualdiff is not installed')\n        super(PerceptualdiffEngineTests, cls).setUpClass()\n\n    def test_assertScreenshot_failure(self):\n        super(PerceptualdiffEngineTests, self).test_assertScreenshot_failure()\n        # Check that the diff file was created\n        diff_file = path.join(path.dirname(__file__), 'screenshots', 'black-box.diff.png')\n        self.assertTrue(path.isfile(diff_file))\n\n\n# Delete the imported module so it doesn't get executed here.\ndel PILEngineTests"
  },
  {
    "path": "tests/test_pil_engine.py",
    "content": "from __future__ import absolute_import\nimport os\nfrom os import path\n\nfrom needle.cases import NeedleTestCase\nfrom tests import ImageTestCaseMixin\n\n\nclass PILEngineTests(ImageTestCaseMixin, NeedleTestCase):\n    engine_class = 'needle.engines.pil_engine.Engine'\n    output_directory = 'tests/screenshots/'\n    baseline_directory = 'tests/screenshots/baseline/'\n\n    def setUp(self):\n        # Remove all screeshots from previous test runs\n        screenshots_path = path.join(path.dirname(__file__), 'screenshots')\n        screenshots = [ f for f in os.listdir(screenshots_path) if f.endswith(\".png\") ]\n        for screenshot in screenshots:\n            os.remove(path.join(screenshots_path, screenshot))\n        super(PILEngineTests, self).setUp()\n\n    def test_assertScreenshot_success(self):\n        self.load_black_div()\n        self.assertScreenshot(\n            self.driver.find_element_by_id('black-box'),\n            'black-box'\n        )\n\n    def test_assertScreenshot_failure(self):\n        with self.assertRaises(AssertionError):\n            self.load_black_div('hello')\n            self.assertScreenshot(\n                self.driver.find_element_by_id('black-box'),\n                'black-box'\n            )"
  },
  {
    "path": "tests/test_plugin.py",
    "content": "import logging\nimport sys\nimport os\nfrom errno import EEXIST\n\nfrom needle.plugin import NeedleCapturePlugin, SaveBaselinePlugin, CleanUpOnSuccessPlugin\nfrom nose.plugins import PluginTester\n\nif sys.version_info > (2, 7):\n    from unittest import TestCase\nelse:\n    from unittest2 import TestCase\n\n\nbaseline_filename = 'screenshots/baseline/red_box.png'\nscreenshot_filename = 'screenshots/red_box.png'\ndummy_baseline_content = b'abcd'\nlog = logging.getLogger(__name__)\n\ndef create_baseline_dir():\n    \"\"\"\n    Create the baseline images directory if it doesn't already exist.\n    \"\"\"\n    dir_path = 'screenshots/baseline'\n    try:\n        os.makedirs(dir_path)\n    except OSError as err:\n        if err.errno == EEXIST and os.path.isdir(dir_path):\n            pass\n        else:\n            raise\n\n\nclass NeedlePluginTester(PluginTester):\n    \"\"\"\n    Base class for tests of needle's nose plugins.\n    \"\"\"\n    suitepath = 'tests/plugin_test_cases/red_box.py'\n\n    def setUp(self):\n        \"\"\"\n        Run the wrapped test suite and log its output for use in debugging\n        failures.\n        \"\"\"\n        super(NeedlePluginTester, self).setUp()\n        log.debug(self.output)\n\n    def tearDown(self):\n        \"\"\"\n        Remove the baseline image created by the test.\n        \"\"\"\n        os.remove(baseline_filename)\n\n\nclass NeedleCaptureTest(NeedlePluginTester, TestCase):\n    \"\"\"\n    Check that the baseline file gets saved when using the\n    --with-needle-capture option.\n    \"\"\"\n    activate = '--with-needle-capture'\n    plugins = [NeedleCapturePlugin()]\n\n    def setUp(self):\n        self.assertFalse(os.path.exists(baseline_filename))\n        super(NeedleCaptureTest, self).setUp()\n\n    def test_baseline_is_saved(self):\n        self.assertTrue(os.path.exists(baseline_filename))\n        self.assertTrue(self.nose.success)\n\n\nclass NeedleCaptureOverwriteTest(NeedlePluginTester, TestCase):\n    \"\"\"\n    Check that an existing baseline file does NOT get overwritten, when using\n    the --with-needle-capture option.\n    \"\"\"\n\n    activate = '--with-needle-capture'\n    plugins = [NeedleCapturePlugin()]\n\n    def setUp(self):\n        self.assertFalse(os.path.exists(baseline_filename))\n\n        # Create dummy baseline file\n        create_baseline_dir()\n        baseline = open(baseline_filename, 'wb')\n        baseline.write(dummy_baseline_content)\n        baseline.close()\n\n        super(NeedleCaptureOverwriteTest, self).setUp()\n\n    def test_existing_baseline_not_overwritten(self):\n        baseline = open(baseline_filename, 'rb')\n        self.assertEqual(baseline.read(), dummy_baseline_content)\n        self.assertTrue(self.nose.success)\n\n\n\nclass SaveBaselineTest(NeedlePluginTester, TestCase):\n    \"\"\"\n    Check that the baseline file gets saved when using the\n    --with-save-baseline option.\n    \"\"\"\n    activate = '--with-save-baseline'\n    plugins = [SaveBaselinePlugin()]\n\n    def setUp(self):\n        self.assertFalse(os.path.exists(baseline_filename))\n        super(SaveBaselineTest, self).setUp()\n\n    def test_baseline_is_saved(self):\n        self.assertTrue(os.path.exists(baseline_filename))\n        self.assertTrue(self.nose.success)\n\n\nclass SaveBaselineOverwriteTest(NeedlePluginTester, TestCase):\n    \"\"\"\n    Check that an existing baseline file DOES get overwritten, when using\n    the --with-save-baseline option.\n    \"\"\"\n\n    activate = '--with-save-baseline'\n    plugins = [SaveBaselinePlugin()]\n\n    def setUp(self):\n        self.assertFalse(os.path.exists(baseline_filename))\n\n        # Create dummy baseline file\n        create_baseline_dir()\n        baseline = open(baseline_filename, 'wb')\n        baseline.write(dummy_baseline_content)\n        baseline.close()\n\n        super(SaveBaselineOverwriteTest, self).setUp()\n\n    def test_existing_baseline_is_overwritten(self):\n        baseline = open(baseline_filename, 'rb')\n        self.assertNotEqual(baseline.read(), dummy_baseline_content)\n        self.assertTrue(self.nose.success)\n\n\nclass NeedleCleanupOnSuccessTest(NeedlePluginTester, TestCase):\n    \"\"\"\n    Check that the screenshot gets removed when using the\n    needle-cleanup-on-success option.\n    \"\"\"\n    activate = '--with-needle-cleanup-on-success'\n    plugins = [CleanUpOnSuccessPlugin()]\n\n    def setUp(self):\n        # Create the baseline\n        create_baseline_dir()\n        baseline = open(baseline_filename, 'wb')\n        baseline.write(open('tests/test_red_box.png', 'rb').read())\n        baseline.close()\n\n        # Make sure the screenshot doesn't exist yet\n        self.assertFalse(os.path.exists(screenshot_filename))\n        super(NeedleCleanupOnSuccessTest, self).setUp()\n\n    def test_screenshot_is_cleanedup(self):\n        # Make sure the screenshot has been cleaned up\n        self.assertFalse(os.path.exists(screenshot_filename))\n        self.assertTrue(self.nose.success)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py{27,34,35,36}-{chrome,firefox,phantomjs}\n\n[testenv]\nsetenv =\n    chrome: NEEDLE_BROWSER=chrome\n    firefox: NEEDLE_BROWSER=firefox\n    phantomjs: NEEDLE_BROWSER=phantomjs\ncommands = nosetests {posargs} -v\n\n[testenv:docs]\ndeps = Sphinx==1.5.3\ncommands =\n    sphinx-build -W -b {posargs:html} docs docs/_build\n"
  }
]