{"id":3114,"date":"2017-10-25T14:26:37","date_gmt":"2017-10-25T12:26:37","guid":{"rendered":"https:\/\/humanoids.be\/log\/?p=3114"},"modified":"2018-11-10T15:58:35","modified_gmt":"2018-11-10T13:58:35","slug":"code-coverage-reports-for-webextensions","status":"publish","type":"post","link":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/","title":{"rendered":"Code coverage reports for WebExtensions"},"content":{"rendered":"<p>It&#8217;s been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then Firefox has gotten a completely new extension system. I&#8217;ve been really busy porting my extensions and not writing blog posts.<\/p>\n<p>The add-on SDK conveniently came with a test runner built into the JPM command line tool. <a href=\"https:\/\/developer.mozilla.org\/en-US\/Add-ons\/WebExtensions\" rel=\"noopener nofollow\">WebExtensions<\/a> don&#8217;t, which forces the choice of test runner on the developer.<\/p>\n<p>If you use a traditional test runner, you&#8217;re probably not really struggling with code coverage. You may have had to do a little bit of research to properly mock the environment of the test runner, so your extension code can run in it, most likely ending up with something that exports the DOM globals from <a href=\"https:\/\/www.npmjs.com\/package\/jsdom\" rel=\"noopener nofollow\">JSDOM<\/a> and some stub of the WebExtensions API (my choice is <a href=\"https:\/\/www.npmjs.com\/package\/sinon-chrome\" rel=\"noopener nofollow\">sinon-chrome<\/a>). And then you&#8217;re done.<br \/>\n<!--more--><\/p>\n<pre><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst browserEnv = require(&amp;amp;amp;amp;amp;amp;quot;browser-env&amp;amp;amp;amp;amp;amp;quot;);\nconst browser = require(&amp;amp;amp;amp;amp;amp;quot;sinon-chrome\/webextensions&amp;amp;amp;amp;amp;amp;quot;);\n\nbrowserEnv();\nglobal.browser = browser;\n<\/pre>\n<p>Trouble is, I like the fancy modern test runners, which run tests in parallel. Of course we could use the global injection approach there. However we would sacrifice running tests in parallel inside a single test file. While we may still be able to run multiple test files at once depending on the test runner (my choice is <a href=\"https:\/\/www.npmjs.com\/package\/ava\" rel=\"noopener nofollow\">ava<\/a>).<\/p>\n<p>So how can we run them in parallel then? The extension files all need a window global and a <code>browser<\/code>\/<code>chrome<\/code> global. Sure, but luckily JSDOM by default doesn&#8217;t export the window global to the node global, instead it creates a sandbox. And into that sandbox we also inject the WebExtension API stubs and whatever DOM APIs we need that JSDOM doesn&#8217;t come with. Finally we execute the file to test in that sandbox (using <code>eval<\/code>) and pass in the window as context to the test.<\/p>\n<pre><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst fs = require(&amp;amp;amp;amp;amp;amp;quot;mz\/fs&amp;amp;amp;amp;amp;amp;quot;);\nconst { JSDOM } = require(&amp;amp;amp;amp;amp;amp;quot;jsdom&amp;amp;amp;amp;amp;amp;quot;);\n\n\/**\n * Creates sandbox with DOM and WebExtensions APIs and loads instrumented files in it.\n *\n * @param {&#x5B;string]} files - Paths of files to load in the DOM sandbox.\n * @param {string} &#x5B;html=&amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;lt;html&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;quot;] - Document to create the DOM sandbox around.\n * @returns {object} JSDOM global the files are loaded in.\n *\/\nconst getEnv = async (files, html = &amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;lt;html&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;quot;) =&amp;amp;amp;amp;amp;amp;gt; {\n const dom = new JSDOM(html, {\n   runScripts: 'outside-only',\n   virtualConsole\n });\n dom.window.browser = require(&amp;amp;amp;amp;amp;amp;quot;sinon-chrome\/webextensions&amp;amp;amp;amp;amp;amp;quot;);\n \/\/ Purge that instance of the browser stubs, so tests have their own env.\n delete require.cache&#x5B;path.join(__dirname, '..\/node_modules\/sinon-chrome\/webextensions\/index.js')];\n for(const file of files) {\n   dom.window.eval(await fs.readFile(path.join(__dirname, file), 'utf8'));\n }\n return dom;\n};\n<\/pre>\n<p>Sounds simple enough, and results in us getting a sandbox per test. A lot of overhead, just to run tests in parallel. We haven&#8217;t even started with overhead though.<\/p>\n<p>Like in the <a href=\"https:\/\/humanoids.be\/log\/2015\/11\/checking-code-coverage-of-add-on-sdk-extensions\/\" rel=\"noopener nofollow\">article for the add-on SDK<\/a>, to get coverage, files have to be instrumented and coverage has be read from them. This setup isn&#8217;t doing that yet. Instead it currently loads the tested files using node&#8217;s <code>fs<\/code> module (actually, using a promisified version) and then evaluates them inside the sandbox. At no point could my coverage tool of choice, <a href=\"https:\/\/www.npmjs.com\/package\/nyc\" rel=\"noopener nofollow\">nyc<\/a>, instrument them or read coverage.<\/p>\n<p>To sum up, we have to manually instrument the files when loading them using nyc and then manually export the coverage results so nyc can generate a report. Now, the instrumenting bit is actually an API nyc offers. Trouble is the reading the coverage bit.<\/p>\n<p>Let&#8217;s start with the simple task, instrumenting. The following code snippet replaces the <code>readFile<\/code> call in the <code>getEnv<\/code> function from before. It only instruments files using nyc when nyc is being used to run the tests, else it just reads the file.<\/p>\n<pre><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst fs = require(&amp;amp;amp;amp;amp;amp;quot;fs\/mz&amp;amp;amp;amp;amp;amp;quot;);\nconst cp = require(&amp;amp;amp;amp;amp;amp;quot;child_process&amp;amp;amp;amp;amp;amp;quot;);\nconst util = require(&amp;amp;amp;amp;amp;amp;quot;util&amp;amp;amp;amp;amp;amp;quot;);\nconst ef = util.promisify(cp.execFile);\n\n\/**\n * Returns the source of the specified file, instrumented when running nyc.\n *\n * @param {string} sourcePath - Path to the file that should be loaded.\n * @returns {string} Potentially instrumented source code of the given file.\n *\/\nconst instrument = async (sourcePath) =&amp;amp;amp;amp;amp;amp;gt; {\n if(!instrumentCache.has(sourcePath)) {\n   if(!process.env.NYC_CONFIG) {\n     const source = await fs.readFile(sourcePath, 'utf8');\n     instrumentCache.set(sourcePath, source);\n   }\n   else {\n     const instrumented = await ef(process.execPath, &#x5B;\n       '.\/node_modules\/.bin\/nyc',\n       'instrument',\n       sourcePath\n     ], {\n       cwd: process.cwd(),\n       env: process.env\n     });\n     instrumentCache.set(sourcePath, instrumented.stdout.toString('utf-8'));\n   }\n }\n return instrumentCache.get(sourcePath);\n};\n<\/pre>\n<p>Sadly we can&#8217;t just share the coverage global with the JSDOM scopes. Instead we have to manually collect the coverage and hand it over to nyc.<\/p>\n<p>Luckily nyc reads all &#8220;.json&#8221; files in the &#8220;.nyc_output&#8221; directory as coverage results. Thus we can just write the coverage results to a uniquely named JSON file in that directory once tests are done (oh, and we close the window to clean up all garbage):<\/p>\n<pre><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst fs = require(&amp;amp;amp;amp;amp;amp;quot;mz\/fs&amp;amp;amp;amp;amp;amp;quot;);\nconst mkdirp = require(&amp;amp;amp;amp;amp;amp;quot;mkdirp&amp;amp;amp;amp;amp;amp;quot;);\nconst util = require(&amp;amp;amp;amp;amp;amp;quot;util&amp;amp;amp;amp;amp;amp;quot;);\nconst mk = util.promisify(mkdirp);\n\n\/**\n * Saves coverage to disk for nyc to collect and cleans up the JSDOM sandbox.\n *\n * @param {JSDOMWindow} window - Window global of the sandbox. Window property\n *                               on the global getEnv returns.\n * @returns {undefined}\n *\/\nconst cleanUp = async (window) =&amp;amp;amp;amp;amp;amp;gt; {\n if(process.env.NYC_CONFIG) {\n   const nycConfig = JSON.parse(process.env.NYC_CONFIG);\n   await mk(nycConfig.tempDirectory);\n   await fs.writeFile(path.join(nycConfig.tempDirectory, `${Date.now()}_${process.pid}_${++id}.json`), JSON.stringify(window.__coverage__), 'utf8');\n }\n window.close();\n};\n<\/pre>\n<p><strong>Update (2018-11-10):<\/strong> in nyc 13.1.0 tempDirectory was changed to tempDir.<\/p>\n<p>Code in this article are excerpts from the test suites I use for two of my extensions. <a href=\"https:\/\/github.com\/freaktechnik\/advanced-github-notifier\" rel=\"noopener nofollow\">Advanced GitHub Notifier<\/a> has the fancy one we built throughout this article, which essentially creates a sandbox per test and <a href=\"https:\/\/github.com\/freaktechnik\/justintv-stream-notifications\" rel=\"noopener nofollow\">Live Stream Notifier<\/a> uses a global scope-polluting setup. Both setups have their drawbacks. Polluting the global environment means that tests depending on the state of the API stubs or similar have to run in series. However it is much faster than initializing a whole DOM sandbox for every test and requires a lot more setup.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then Firefox has gotten a completely new extension system. I&#8217;ve been really busy porting my extensions and not writing blog posts. The add-on SDK conveniently came with a test runner &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Code coverage reports for WebExtensions&#8221;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":3069,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"activitypub_content_warning":"","activitypub_content_visibility":"","activitypub_max_image_attachments":4,"activitypub_interaction_policy_quote":"anyone","activitypub_status":"","footnotes":""},"categories":[615,12],"tags":[714,707,441,713,669,708,670,712],"class_list":["post-3114","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-extensions","category-firefox","tag-ava","tag-coverage","tag-javascript","tag-nyc","tag-test","tag-unit-test","tag-unit-tests","tag-webextensions"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Code coverage reports for WebExtensions - Humanoids beLog<\/title>\n<meta name=\"description\" content=\"It&#039;s been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Code coverage reports for WebExtensions - Humanoids beLog\" \/>\n<meta property=\"og:description\" content=\"It&#039;s been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then\" \/>\n<meta property=\"og:url\" content=\"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/\" \/>\n<meta property=\"og:site_name\" content=\"Humanoids beLog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/humanoidsbelog\" \/>\n<meta property=\"article:published_time\" content=\"2017-10-25T12:26:37+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2018-11-10T13:58:35+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1007\" \/>\n\t<meta property=\"og:image:height\" content=\"573\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Martin Giger\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@freaktechnik\" \/>\n<meta name=\"twitter:site\" content=\"@freaktechnik\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Martin Giger\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/\"},\"author\":{\"name\":\"Martin Giger\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/#\\\/schema\\\/person\\\/a58850edf3908fc1b0987aedfb9a080d\"},\"headline\":\"Code coverage reports for WebExtensions\",\"datePublished\":\"2017-10-25T12:26:37+00:00\",\"dateModified\":\"2018-11-10T13:58:35+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/\"},\"wordCount\":658,\"commentCount\":0,\"image\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/humanoids.be\\\/log\\\/wp-content\\\/uploads\\\/2015\\\/11\\\/coverage.png\",\"keywords\":[\"ava\",\"coverage\",\"javascript\",\"nyc\",\"test\",\"unit test\",\"unit tests\",\"webextensions\"],\"articleSection\":[\"Extensions\",\"Firefox\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/\",\"url\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/\",\"name\":\"Code coverage reports for WebExtensions - Humanoids beLog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/humanoids.be\\\/log\\\/wp-content\\\/uploads\\\/2015\\\/11\\\/coverage.png\",\"datePublished\":\"2017-10-25T12:26:37+00:00\",\"dateModified\":\"2018-11-10T13:58:35+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/#\\\/schema\\\/person\\\/a58850edf3908fc1b0987aedfb9a080d\"},\"description\":\"It's been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#primaryimage\",\"url\":\"https:\\\/\\\/humanoids.be\\\/log\\\/wp-content\\\/uploads\\\/2015\\\/11\\\/coverage.png\",\"contentUrl\":\"https:\\\/\\\/humanoids.be\\\/log\\\/wp-content\\\/uploads\\\/2015\\\/11\\\/coverage.png\",\"width\":1007,\"height\":573},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/2017\\\/10\\\/code-coverage-reports-for-webextensions\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/humanoids.be\\\/log\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Code coverage reports for WebExtensions\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/#website\",\"url\":\"https:\\\/\\\/humanoids.be\\\/log\\\/\",\"name\":\"Humanoids beLog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/humanoids.be\\\/log\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/humanoids.be\\\/log\\\/#\\\/schema\\\/person\\\/a58850edf3908fc1b0987aedfb9a080d\",\"name\":\"Martin Giger\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8766da02c6809c8ca3142c0c75bbfd454a6d1120dc01fede05d0beffedb6dd40?s=96&d=mm&r=pg\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8766da02c6809c8ca3142c0c75bbfd454a6d1120dc01fede05d0beffedb6dd40?s=96&d=mm&r=pg\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8766da02c6809c8ca3142c0c75bbfd454a6d1120dc01fede05d0beffedb6dd40?s=96&d=mm&r=pg\",\"caption\":\"Martin Giger\"},\"description\":\"openpgp4fpr:89346D522A2C190EEB959F52AE530058EFE7FD60\",\"sameAs\":[\"http:\\\/\\\/humanoids.be\\\/\"],\"url\":\"https:\\\/\\\/humanoids.be\\\/log\\\/author\\\/humanoid\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Code coverage reports for WebExtensions - Humanoids beLog","description":"It's been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/","og_locale":"en_US","og_type":"article","og_title":"Code coverage reports for WebExtensions - Humanoids beLog","og_description":"It's been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then","og_url":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/","og_site_name":"Humanoids beLog","article_publisher":"https:\/\/www.facebook.com\/humanoidsbelog","article_published_time":"2017-10-25T12:26:37+00:00","article_modified_time":"2018-11-10T13:58:35+00:00","og_image":[{"width":1007,"height":573,"url":"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png","type":"image\/png"}],"author":"Martin Giger","twitter_card":"summary_large_image","twitter_creator":"@freaktechnik","twitter_site":"@freaktechnik","twitter_misc":{"Written by":"Martin Giger","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#article","isPartOf":{"@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/"},"author":{"name":"Martin Giger","@id":"https:\/\/humanoids.be\/log\/#\/schema\/person\/a58850edf3908fc1b0987aedfb9a080d"},"headline":"Code coverage reports for WebExtensions","datePublished":"2017-10-25T12:26:37+00:00","dateModified":"2018-11-10T13:58:35+00:00","mainEntityOfPage":{"@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/"},"wordCount":658,"commentCount":0,"image":{"@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#primaryimage"},"thumbnailUrl":"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png","keywords":["ava","coverage","javascript","nyc","test","unit test","unit tests","webextensions"],"articleSection":["Extensions","Firefox"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/","url":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/","name":"Code coverage reports for WebExtensions - Humanoids beLog","isPartOf":{"@id":"https:\/\/humanoids.be\/log\/#website"},"primaryImageOfPage":{"@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#primaryimage"},"image":{"@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#primaryimage"},"thumbnailUrl":"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png","datePublished":"2017-10-25T12:26:37+00:00","dateModified":"2018-11-10T13:58:35+00:00","author":{"@id":"https:\/\/humanoids.be\/log\/#\/schema\/person\/a58850edf3908fc1b0987aedfb9a080d"},"description":"It's been quite a while since I last posted on here, and interestingly about the same topic: code coverage analysis in Firefox extensions. And since then","breadcrumb":{"@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#primaryimage","url":"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png","contentUrl":"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png","width":1007,"height":573},{"@type":"BreadcrumbList","@id":"https:\/\/humanoids.be\/log\/2017\/10\/code-coverage-reports-for-webextensions\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/humanoids.be\/log\/"},{"@type":"ListItem","position":2,"name":"Code coverage reports for WebExtensions"}]},{"@type":"WebSite","@id":"https:\/\/humanoids.be\/log\/#website","url":"https:\/\/humanoids.be\/log\/","name":"Humanoids beLog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/humanoids.be\/log\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/humanoids.be\/log\/#\/schema\/person\/a58850edf3908fc1b0987aedfb9a080d","name":"Martin Giger","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/8766da02c6809c8ca3142c0c75bbfd454a6d1120dc01fede05d0beffedb6dd40?s=96&d=mm&r=pg","url":"https:\/\/secure.gravatar.com\/avatar\/8766da02c6809c8ca3142c0c75bbfd454a6d1120dc01fede05d0beffedb6dd40?s=96&d=mm&r=pg","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8766da02c6809c8ca3142c0c75bbfd454a6d1120dc01fede05d0beffedb6dd40?s=96&d=mm&r=pg","caption":"Martin Giger"},"description":"openpgp4fpr:89346D522A2C190EEB959F52AE530058EFE7FD60","sameAs":["http:\/\/humanoids.be\/"],"url":"https:\/\/humanoids.be\/log\/author\/humanoid\/"}]}},"jetpack_featured_media_url":"https:\/\/humanoids.be\/log\/wp-content\/uploads\/2015\/11\/coverage.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/posts\/3114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/comments?post=3114"}],"version-history":[{"count":21,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/posts\/3114\/revisions"}],"predecessor-version":[{"id":3253,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/posts\/3114\/revisions\/3253"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/media\/3069"}],"wp:attachment":[{"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/media?parent=3114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/categories?post=3114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/humanoids.be\/log\/wp-json\/wp\/v2\/tags?post=3114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}