Code coverage HTML reports are missing styles in VSTS

23 Sep 2017

Due to a security issue identified using external style-sheets, meta or script files, VSTS strip all the meta tags, link and script tags from the code coverage HTML file. The iframe is tagged as a sandboxed mode, so the scripts loading and executions are blocked. You can check out the discussion on github regarding this.

You can possibly live without the scripts required to provide extras like sorting, filtering etc., But looking at a code coverage report, without styles is really painful. So as a work-around, you can inline all external styles in the generated HTML reports. It is really as messy as it sounds.

When I initially searched for a workaround, I landed up in following beautiful article written around this issue 

1) Including CSS in VSTS Code Coverage Results - using gulp  

This option is based on gulp task runner, it uses inline-source node package to embeds the script file, css files and images inside the HTML files. Since the base tool is generic it considers every HTML is linked with different CSS. So it makes no assumption and the css files read are not cached at all, so it is re-read every time causing little bit of overhead.


This method is directly based on inline-css, a package primarily used for in-lining the CSS files using style attributes on each and every HTML node. This package is well fit when you construct HTML/email newsletters. But this method will result in increase of the HTML file size, and also the time taken to inline CSS. When you have lot of code files/components ie., the number of HTML reports generated by code coverage are too many. The resultant iniling process to increase proportionately (no. of files * X).  This affects overall build timings in vsts as well as the entire inlining process in unwanted one.  

So I tried to check for node based solution, that can convert external resources into internal resource using <style> tag in an HTML. I came across this good solution, but it didn't work for physical files. Maybe something wrong with my setup.


Finally, I decided to implement a simple solution in node that is specific for this fix i.e., just convert external CSS files into an internal CSS with following 2 assumptions for speed optimization

  1. The external CSS files linked to report HTML will be same, so we can simply read once and cache it for further use. (Speed optimization)
  2. For a basic report there is no need of scripts and images, so I left those out of inlining.

With above two assumptions, the conversion process has reached its at most speed. In order to make this a reusable package for multiple projects, I published it as a NPM package. Here is the link to the vsts-coverage-styles package.


 * Vsts test coverage view doesn't load external CSS due to security reasons.
 * So we are converting all external css files to internal <style> tags using vsts-coverage-styles (node module).
 * Fix all UI issues due to VSTS stripping :after & :before selectors, images & charsets

const vstsCoverageStyles = require('vsts-coverage-styles').VstsCoverageStyles;
const overrideCss = '.status-line { clear:both;} ' +
    '.coverage .line-count, .coverage .line-coverage, ' +
    '.coverage .text .prettyprint {font-size:12px !important; ' +
    'line-height:1.2 !important;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace !important;}' +
    '.coverage .line-count{max-width:40px;padding-right:25px !important;} ' +
    '.coverage .line-coverage{max-width:45px;}' +
    '.coverage .line-coverage .cline-any{padding-right:25px !important;}' +

// Default Options
    coverageDir: './coverage',
    pattern: '/**/*.html',
    fileEncoding: 'utf8',
    minifyOptions: {

    extraCss: overrideCss,
    preProcessFn: function (html) {
        return html.replace(new RegExp('×', 'g'), 'x');
    postProcessFn: function(html) {
        return html;

You can then add this as part of package.json,  as follows to automatically run this inline process after a successful coverage generation

// Package.json

// ...
  scripts: {
     "test:ci": "npm run test -- --browsers PhantomJS_custom",
     "posttest": "node ./tools/process-coverage-report.js"