{"id":3121,"date":"2015-10-01T00:15:21","date_gmt":"2015-10-01T04:15:21","guid":{"rendered":"http:\/\/qxf2.com\/blog\/?p=3121"},"modified":"2018-03-14T10:27:53","modified_gmt":"2018-03-14T14:27:53","slug":"selenium-html5-canvas-verify-what-was-drawn","status":"publish","type":"post","link":"https:\/\/qxf2.com\/blog\/selenium-html5-canvas-verify-what-was-drawn\/","title":{"rendered":"Selenium &#038; HTML5 canvas: Verify what was drawn"},"content":{"rendered":"<p><strong>Problem:<\/strong> How do you verify what was drawn on a canvas element using Selenium?<\/p>\n<hr>\n<h3>Why this post?<\/h3>\n<p>The HTML5 canvas element is opaque to Selenium and does not lend itself well to GUI automation. An application I was testing had an HTML5 canvas element that users could interact with. Users could place pre-defined objects upon the canvas element and then print out the canvas. This area of the software repeatedly had regressions and needed constant attention from testers. I wrote an automated check that, while not ideal, was a big improvement over the existing situation. <\/p>\n<hr>\n<h3>Overview:<\/h3>\n<p>I use an indirect way to verify what is drawn on an HTML5 canvas. The approach relies on having baseline images of how the canvas is expected to look like at each step of the drawing. At a high level we will:<br \/>\n1. Use the canvas element&#8217;s toDataURL() method to get an image of the canvas<br \/>\n2. Use Selenium&#8217;s execute_script() method to execute the toDataURL() JavaScript call<br \/>\n3. Compare the image generated with a baseline <\/p>\n<hr>\n<h3>Step 1. The canvas element&#8217;s toDataURL() method<\/h3>\n<p>Let&#8217;s begin by looking at the builtin toDataURL() method of all HTML5 canvas elements.<\/p>\n<blockquote><p>The HTMLCanvasElement.toDataURL() method returns a data URIs containing a representation of the image in the format specified by the type parameter (defaults to PNG). <strong>Source:<\/strong><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTMLCanvasElement\/toDataURL\">The docs<\/a><\/p><\/blockquote>\n<p>Sure, this is a JavaScript call &#8211; but still useful to know. It returns an image of the canvas. So for now, identify your canvas element and get your JavaScript call ready.<\/p>\n<pre lang=\"javascript\">\r\nreturn document.getElementsByClassName(\"my-canvas\")[0].toDataURL(\"image\/png\");\r\n<\/pre>\n<p>If you cannot figure this out on your own, go ahead and request your developers for help.<\/p>\n<h3>Step 2. Selenium&#8217;s execute_script() method<\/h3>\n<p>Selenium has a backdoor to execute JavaScript. This is true no matter in which language you use to write your Selenium tests. Let&#8217;s use the JavaScript we wrote in Step 1 and execute it as part of our Selenium script. I&#8217;ll show you how to write the script in Python &#8211; but you can try something similar in a language of your choice. <\/p>\n<pre lang=\"python\">\r\n# Get the image\r\npng_url = self.driver.execute_script('return document.getElementsByClassName(\"my-canvas\")[0].toDataURL(\"image\/png\");')\r\n<\/pre>\n<p>Next, you will need to convert the long string that the toDataURL() method returns and save it as a PNG. <\/p>\n<pre lang=\"python\">\r\n#Parse the URI to get only the base64 part\r\nstr_base64 = re.search(r'base64,(.*)',png_url).group(1)\r\n\r\n#Convert it to binary\r\nstr_decoded = str_base64.decode('base64')\r\n \r\n#Write out the image somewhere\r\noutput_img = \"you fill the path here like \/tmp\/checkpoint1.png\"        \r\nfp = open(output_img,'wb')\r\nfp.write(str_decoded)\r\nfp.close()\r\n<\/pre>\n<p>BOOM! You just exported the current canvas as an image.<\/p>\n<h3>Step 3. Image compare<\/h3>\n<p>The next step is to compare the image with a known baseline. I am using Python and its Python Image Library (PIL). You can try something similar in other languages too. Here is a code snippet showing how to compare two images.  <\/p>\n<pre lang=\"python\">\r\nfrom PIL import Image, ImageChops\r\nimport os\r\n\r\ndef is_equal(img_actual,img_expected,result):\r\n    \"Returns true if the images are identical(all pixels in the difference image are zero)\"\r\n    result_flag = False\r\n\r\n    #Check that img_actual exists\r\n    if not os.path.exists(img_actual):\r\n        print 'Could not locate the generated image: %s'%img_actual\r\n   \r\n    #Check that img_expected exists\r\n    if not os.path.exists(img_expected):\r\n        print 'Could not locate the baseline image: %s'%img_expected\r\n\r\n    if os.path.exists(img_actual) and os.path.exists(img_expected):\r\n        actual = Image.open(img_actual)\r\n        expected = Image.open(img_expected)\r\n        result_image = ImageChops.difference(actual,expected)\r\n\r\n        #Where the real magic happens\r\n        if (ImageChops.difference(actual,expected).getbbox() is None):\r\n            result_flag = True\r\n\r\n        #Bonus code to store the overlay\r\n        #Result image will look black in places where the two images match\r\n        color_matrix = ([0] + ([255] * 255))\r\n        result_image = result_image.convert('L')\r\n        result_image = result_image.point(color_matrix)\r\n        result_image.save(result)#Save the result image\r\n\r\n    return result_flag\r\n<\/pre>\n<p>Now you can generate images every time you draw something on the canvas and compare them with pre-defined baselines.<br \/>\n<strong>Caution:<\/strong> The images you produce depend on your screen resolution. So there is some housekeeping involved in making the test repeatable across all resolutions. <\/p>\n<hr>\n<p>Voila! You now have an automated way to check what was drawn on an HTML5 canvas. I will follow up soon with a post on how to use Selenium to handle different screen resolutions, drag &#038; drop, drag &#038; click and identify coordinates on a canvas element. Stay tuned!<\/p>\n<p><strong>PS:<\/strong> Looking for Python-based web automation framework? Try our open-sourced <a href=\"https:\/\/github.com\/qxf2\/qxf2-page-object-model\">GUI automation framework<\/a> based on the page object model. <\/p>\n<hr>\n<script>(function() {\n\twindow.mc4wp = window.mc4wp || {\n\t\tlisteners: [],\n\t\tforms: {\n\t\t\ton: function(evt, cb) {\n\t\t\t\twindow.mc4wp.listeners.push(\n\t\t\t\t\t{\n\t\t\t\t\t\tevent   : evt,\n\t\t\t\t\t\tcallback: cb\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n})();\n<\/script><!-- Mailchimp for WordPress v4.10.1 - https:\/\/wordpress.org\/plugins\/mailchimp-for-wp\/ --><form id=\"mc4wp-form-1\" class=\"mc4wp-form mc4wp-form-6165 mc4wp-form-theme mc4wp-form-theme-blue\" method=\"post\" data-id=\"6165\" data-name=\"Newsletter\" ><div class=\"mc4wp-form-fields\"><div style=\"border:3px; border-style:dashed;border-color:#56d1e1;padding:1.2em;\">\r\n  <h1 style=\"text-align: center; padding-top: 20px; padding-bottom: 20px; color: #592b1b;\">Subscribe to our weekly Newsletter<\/h1>\r\n  <input style=\"margin: auto;\" type=\"email\" name=\"EMAIL\" placeholder=\"Your email address\" required \/>\r\n  <br>\r\n  <p style=\"text-align: center;\">\r\n    <input style=\"background-color: #890c06 !important; border-color: #890c06;\" type=\"submit\" value=\"Sign up\" \/>\r\n    \r\n  <\/p>\r\n  <p style=\"text-align: center;\">\r\n    <a href=\"http:\/\/mailchi.mp\/c9c7b81ddf13\/the-informed-testers-newsletter-20-oct-2017\"><small>View a sample<\/small><\/a>\r\n  <\/p>\r\n  <br>\r\n<\/div><\/div><label style=\"display: none !important;\">Leave this field empty if you're human: <input type=\"text\" name=\"_mc4wp_honeypot\" value=\"\" tabindex=\"-1\" autocomplete=\"off\" \/><\/label><input type=\"hidden\" name=\"_mc4wp_timestamp\" value=\"1776953053\" \/><input type=\"hidden\" name=\"_mc4wp_form_id\" value=\"6165\" \/><input type=\"hidden\" name=\"_mc4wp_form_element_id\" value=\"mc4wp-form-1\" \/><div class=\"mc4wp-response\"><\/div><\/form><!-- \/ Mailchimp for WordPress Plugin -->\n<hr>\n","protected":false},"excerpt":{"rendered":"<p>Problem: How do you verify what was drawn on a canvas element using Selenium? Why this post? The HTML5 canvas element is opaque to Selenium and does not lend itself well to GUI automation. An application I was testing had an HTML5 canvas element that users could interact with. Users could place pre-defined objects upon the canvas element and then [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38,18,30],"tags":[],"class_list":["post-3121","post","type-post","status-publish","format-standard","hentry","category-automation","category-python","category-selenium"],"_links":{"self":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/3121","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/comments?post=3121"}],"version-history":[{"count":33,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/3121\/revisions"}],"predecessor-version":[{"id":8703,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/posts\/3121\/revisions\/8703"}],"wp:attachment":[{"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/media?parent=3121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/categories?post=3121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/qxf2.com\/blog\/wp-json\/wp\/v2\/tags?post=3121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}