{"id":521,"date":"2014-06-25T20:53:35","date_gmt":"2014-06-26T02:53:35","guid":{"rendered":"http:\/\/www.crccheck.com\/blog\/?p=521"},"modified":"2015-10-11T22:22:16","modified_gmt":"2015-10-12T04:22:16","slug":"how-to-ignore-the-needle-docs","status":"publish","type":"post","link":"https:\/\/www.crccheck.com\/blog\/how-to-ignore-the-needle-docs\/","title":{"rendered":"How to Ignore the Needle Docs"},"content":{"rendered":"<p>At PyCon 2014, I learned about a package called &#8220;needle&#8221; from Julien Phalip&#8217;s talk, <a href=\"http:\/\/www.pyvideo.org\/video\/2660\/advanced-techniques-for-web-functional-testing\" target=\"_blank\">Advanced techniques for Web functional testing<\/a>. When I tried using it with a Django project, I immediately ran into problems:<\/p>\n<ol>\n<li>The <a href=\"http:\/\/needle.readthedocs.org\/en\/latest\/\" target=\"_blank\">needle docs<\/a> aren&#8217;t written for Django, so they don&#8217;t explain how to use <code>NeedleTestCase<\/code> with <code>LiveServerTestCase<\/code>.<\/li>\n<li>I wasn&#8217;t using nose as my test runner, and didn&#8217;t want to start using it just to run Needle.<\/li>\n<\/ol>\n<p>The first problem turned out to be easy; use both:<\/p>\n<pre><code>class SiteTest(NeedleTestCase, LiveServerTestCase):\r\n    pass<\/code><\/pre>\n<p>The second problem wasn&#8217;t that bad either. If you examine the <a href=\"https:\/\/github.com\/bfirsh\/needle\/blob\/master\/needle\/plugin.py\" target=\"_blank\">Nose plugin<\/a> Needle adds, it just adds a\u00c2\u00a0 <code>save_baseline<\/code> attribute to the test case.<\/p>\n<p>There were a lot of random hacks and tweaks I threw together. I think the best way to show them all is with an annotated example:<\/p>\n<pre><code>import os\r\nimport unittest\r\n\r\nfrom django.test import LiveServerTestCase\r\nfrom needle.cases import NeedleTestCase\r\n\r\n\r\n# This is a configuration variable for whether to save the baseline screenshot\r\n# or not. You can flip it by manually changing it, with an environment variable\r\n# check, or monkey patching.\r\nSAVE_BASELINE = False\r\n\r\n\r\n# You should be taking screenshots at multiple widths to capture all your\r\n# responsive breakpoints. Only the width really matter,s but I include the\r\n# height for completeness.\r\nSIZES = (\r\n    (1024, 800),  # desktop\r\n    (800, 600),  # tablet\r\n    (320, 540),  # mobile\r\n)\r\n\r\n\r\n# To keep the test runner from running slow needle tests every time, decorate\r\n# it. In this example, 'RUN_NEEDLE_TESTS' has to exist in your environment for\r\n# these tests to run. So you would run needle tests like:\r\n#\r\n#     RUN_NEEDLE_TESTS=1 python manage.py test python.import.path.to.test_needle\r\n@unittest.skipUnless('RUN_NEEDLE_TESTS' in os.environ, 'expensive tests')\r\nclass ScreenshotTest(NeedleTestCase, LiveServerTestCase):\r\n    # You're going to want to make sure your pages look consistent every time.\r\n    fixtures = ['needle.json']\r\n\r\n    @classmethod\r\n    def setUpClass(cls):\r\n        \"\"\"\r\n        Sets `save_baseline`.\r\n\r\n        I don't remember why I did it here. Maybe the timing didn't work when\r\n        I put it as an attribute on the test class.\r\n        \"\"\"\r\n        cls.save_baseline = SAVE_BASELINE\r\n        super(ScreenshotTest, cls).setUpClass()\r\n\r\n    def assertResponsive(self, scope, name):\r\n        \"\"\"Takes a screenshot for every responsive size you set.\"\"\"\r\n        for width, height in SIZES:\r\n            self.set_viewport_size(width=width, height=height)\r\n            try:\r\n                self.assertScreenshot(\r\n                    scope,\r\n                    # include the name and browser in the filename\r\n                    '{}_{}_firefox'.format(name, width)\r\n                )\r\n            except AssertionError as e:\r\n                print(e)\r\n                # suppress the error so needle keeps making screenshots. Needle\r\n                # is very fickle and we'll have to judge the screenshots by eye\r\n                # anyways instead of relying on needle's pixel perfect\r\n                # judgements.\r\n                pass\r\n\r\n    def test_homepage(self):\r\n        urls_to_test = (\r\n            ('\/', 'homepage'),\r\n            ('\/login\/', 'login'),\r\n            ('\/hamburger\/', 'meat'),\r\n            ('\/fries\/', 'potatoes'),\r\n            ('\/admin\/', 'admin'),\r\n        )\r\n        for url, name in urls_to_test:\r\n            self.driver.get(self.live_server_url + url)\r\n            self.assertResponsive(\r\n                # for now, I always want the full page, so I use 'html' as the\r\n                # scope for my screenshots. But as I document more things,\r\n                # that's likely to change.\r\n                'html',\r\n                # passing in a human readable name helps it generate\r\n                # screenshots file names that make more sense to me.\r\n                name,\r\n            )\r\n<\/code><\/pre>\n<p>Well I hope that that made sense.<\/p>\n<p>When you run the tests, they&#8217;re saved to the .\/screenshots\/ directory, which I keep out of source control because storing so many binary files is a heavy burden on git. We experimented with\u00c2\u00a0 <code>git-annex<\/code><code><\/code> but it turned out to be more trouble than it was worth.<\/p>\n<p>My typical workflow goes like this:<\/p>\n<ol>\n<li>Make sure my reference baseline screenshots are up to date: <code>git checkout master &amp;&amp; grunt &amp;&amp; invoke needle --make<\/code><\/li>\n<li>Generate screenshots for my feature branch: <code>git checkout fat-buttons &amp;&amp; grunt &amp;&amp; invoke needle<\/code><\/li>\n<li>Open the screenshots directory and compare screenshots.<\/li>\n<\/ol>\n<p>In that workflow,\u00c2\u00a0 <code>grunt<\/code> is used to generate css,\u00c2\u00a0 <code>invoke<\/code> is used as my test runner, and\u00c2\u00a0 <code>--make<\/code> is a flag I built into the needle invoke task to make baseline screenshots.<\/p>\n<p>Now I can quickly see if a change has the desired effect for multiple browser widths faster than it takes to actually resize a browser window. Bonus: I can see if a change has <strong>undesired<\/strong> effects on pages that I would have been too lazy to test manually.<\/p>\n<p>Next steps: I still haven&#8217;t figured out how to run the same test in multiple browsers.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At PyCon 2014, I learned about a package called &#8220;needle&#8221; from Julien Phalip&#8217;s talk, Advanced techniques for Web functional testing. When I tried using it with a Django project, I immediately ran into problems. The answer turned out to be &#8220;ignore the documentation&#8221;.<\/p>\n <a href=\"https:\/\/www.crccheck.com\/blog\/how-to-ignore-the-needle-docs\/\" title=\"How to Ignore the Needle Docs\" class=\"entry-more-link\"><span>Read More<\/span> <span class=\"screen-reader-text\">How to Ignore the Needle Docs<\/span><\/a>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"Layout":"","footnotes":""},"categories":[21,4],"tags":[51,68],"class_list":["entry","author-showmewhatyougot","has-excerpt","post-521","post","type-post","status-publish","format-standard","category-case-study","category-technical","tag-django","tag-testing"],"_links":{"self":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts\/521","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/comments?post=521"}],"version-history":[{"count":8,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts\/521\/revisions"}],"predecessor-version":[{"id":536,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/posts\/521\/revisions\/536"}],"wp:attachment":[{"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/media?parent=521"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/categories?post=521"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.crccheck.com\/blog\/wp-json\/wp\/v2\/tags?post=521"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}