Home > .Net, MsBuild, WatiN > Good HTML email from .trx Test Results In Team Build 2008

Good HTML email from .trx Test Results In Team Build 2008

I struggle with mstest.exe and its various intricacies… The test results that Visual Studio 2008 produces are pretty good, but the viewer is not very user-friendly.

I wanted my team to have a more visible notice for our WatiN functional test runs. The default team system email was just too hard to extract the information we desired with just a glance. I put together a couple things to transform the single .trx file our TFS 2008 team build outputs into an email in your inbox. Below is an xslt file that sits along side the build definition:

<?xml version="1.0" encoding="utf-8"?>
<!-- modified; original by Gavin Stevens: http://geekswithblogs.net/gavin/archive/2009/08/25/134311.aspx -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:vs="http://microsoft.com/schemas/VisualStudio/TeamTest/2006"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:user="http://tempuri.org/lebobitz">

  <msxsl:script language="CSharp" implements-prefix="user">
    <![CDATA[  
        // credit Chris Doggett
        // http://stackoverflow.com/questions/840120/timespan-formatting
        public static string Pluralize(int n, string unit)
        {
	        if (string.IsNullOrEmpty(unit)) return string.Empty;

	        n = Math.Abs(n); // -1 should be singular, too

	        return unit + (n == 1 ? string.Empty : "s");
        }

        // credit Chris Doggett
        // http://stackoverflow.com/questions/840120/timespan-formatting
        public static string TimeSpanInWords(TimeSpan aTimeSpan)
        {
	        System.Collections.Generic.List<string> timeStrings = new System.Collections.Generic.List<string>();

	        int[] timeParts = new[] { aTimeSpan.Days, aTimeSpan.Hours, aTimeSpan.Minutes, aTimeSpan.Seconds };
	        string[] timeUnits = new[] { "day", "hour", "minute", "second" };

	        for (int i = 0; i < timeParts.Length; i++)
	        {
		        if (timeParts[i] > 0)
		        {
			        timeStrings.Add(string.Format("{0} {1}", timeParts[i], Pluralize(timeParts[i], timeUnits[i])));
		        }
	        }

	        return timeStrings.Count != 0 ? string.Join(", ", timeStrings.ToArray()) : "0 seconds";
        }

        public string PrettyDateDifference(string start, string end)
        {
	        var startDate = DateTime.Parse(start);
	        var endDate = DateTime.Parse(end);
	        TimeSpan ts = endDate.Subtract(startDate);
	        return TimeSpanInWords(ts);
        }
        
        public string PrettyDate(string date)
        {
          DateTime d = DateTime.Parse(date);
          return string.Format("{0} {1}", d.ToLongDateString(), d.ToLongTimeString());
        }
    ]]>
  </msxsl:script>

  <xsl:template match="/">
    <html>
      <head>
        <style type="text/css">
          
        </style>
      </head>
      <body style="font-family:Verdana; font-size:10pt">
        <h1>Test Results Summary</h1>
        <table style="width:700;border:1px solid black;font-family:Verdana; font-size:10pt;">
          <tr>
            <td width="150">
              <b>Start Date/Time</b>
            </td>
            <td width="550">
              <xsl:value-of select="user:PrettyDate(//vs:Times/@start)"/>
            </td>
          </tr>
          <tr>
            <td width="150">
              <b>End Date/Time</b>
            </td>
            <td width="550">
              <xsl:value-of select="user:PrettyDate(//vs:Times/@finish)"/>
            </td>
          </tr>
          <tr>
            <td width="150">
              <b>Total Time</b>
            </td>
            <td width="550">
              <xsl:value-of select="user:PrettyDateDifference(//vs:Times/@start,//vs:Times/@finish)"/>
            </td>
          </tr>
          <tr>
            <td width="150">
              <b>Results</b>
            </td>
            <td width="550">
              <xsl:value-of select="//vs:TestRun/@name"/>
            </td>
          </tr>
        </table>
        <xsl:call-template name="summary" />
        <xsl:call-template name="details" />
      </body>
    </html>
  </xsl:template>
  <xsl:template name="summary">
    <h3>Test Summary</h3>
    <table style="width:700;border:1px solid black;font-family:Verdana; font-size:10pt">
      <tr>
        <td style="font-weight:bold">Total</td>
        <td style="font-weight:bold">Failed</td>
        <td style="font-weight:bold">Passed</td>
      </tr>
      <tr>
        <td >
          <xsl:value-of select="//vs:ResultSummary/vs:Counters/@total"/>
        </td>
        <td style="background-color:pink;">
          <xsl:value-of select="//vs:ResultSummary/vs:Counters/@failed"/>
        </td>
        <td style="background-color:lightgreen;">
          <xsl:value-of select="//vs:ResultSummary/vs:Counters/@passed"/>
        </td>
      </tr>
    </table>
  </xsl:template>
  <xsl:template name="details">
    <h3>Test Details</h3>
    <table style="width:700;border:1px solid black;font-family:Verdana; font-size:10pt;">
      <tr>
        <td style="font-weight:bold">Test Name</td>
        <td style="font-weight:bold">Result</td>
        <td style="font-weight:bold">Duration</td>
      </tr>
      <xsl:for-each select="//vs:Results/vs:UnitTestResult">
		<xsl:sort select="@outcome" order="ascending" />
        <xsl:variable name="executionId">
          <xsl:value-of select="@executionId"/>
        </xsl:variable>
        <tr>
          <xsl:attribute name="style">
            <xsl:choose>
              <xsl:when test="@outcome = 'Failed'">background-color:pink;</xsl:when>
              <xsl:when test="@outcome = 'Passed'">background-color:lightgreen;</xsl:when>
              <xsl:otherwise>background-color:yellow;</xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
          <td>
            <xsl:value-of select="@testName"/>
          </td>
          <td>
            <xsl:choose>
              <xsl:when test="@outcome = 'Failed'">FAILED</xsl:when>
              <xsl:when test="@outcome = 'Passed'">Passed</xsl:when>
              <xsl:otherwise>Inconclusive</xsl:otherwise>
            </xsl:choose>
          </td>
          <td>
            <xsl:value-of select="@duration"/>
          </td>
        </tr>
        <tr>
          <td colspan="3">
			  <xsl:choose>
              <xsl:when test="@outcome = 'Failed'">
					<div  id="{$executionId}">
					  <table style="width:700;border:1px solid black;font-family:Verdana; font-size:10pt;">
							  <tr>
								<td width="10"></td>
								<td>
								  <pre style="word-wrap: break-word">
									<xsl:value-of select="vs:Output/vs:ErrorInfo/vs:Message"/>
								  </pre>
								</td>
							  </tr>
							  <tr>
								<td width="10"></td>
								<td>
								  <pre style="word-wrap: break-word">
									<xsl:value-of select="vs:Output/vs:ErrorInfo/vs:StackTrace"/>
								  </pre>
								</td>
							  </tr>
					  </table>
					</div>
			  </xsl:when>
              <xsl:otherwise></xsl:otherwise>
            </xsl:choose>
          </td>
        </tr>
      </xsl:for-each>
</table>
  </xsl:template>
</xsl:stylesheet>

And then in the TfsBuild.proj itself, there are some customizations:

...
<UsingTask AssemblyFile="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.dll" TaskName="MSBuild.ExtensionPack.Communication.Email" />
  <UsingTask AssemblyFile="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.dll" TaskName="MSBuild.ExtensionPack.Xml.XmlTask" />
  <UsingTask AssemblyFile="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.dll" TaskName="MSBuild.ExtensionPack.Framework.MsBuildHelper" />
  
...
 <Target Name="AfterTest" DependsOnTargets="ParseTrxAndEmail;">
  </Target>

  <Target Name="ParseTrxAndEmail">
    <CreateItem Include="$(TestResultsRoot)\*.trx">
      <Output TaskParameter="Include" ItemName="TrxFiles" />
    </CreateItem>

    <MSBuild.ExtensionPack.Framework.MsBuildHelper TaskAction="GetItem" InputItems1="@(TrxFiles)" Position="0">
      <Output TaskParameter="OutputItems" ItemName="TheTrx" />
    </MSBuild.ExtensionPack.Framework.MsBuildHelper>

    <MSBuild.ExtensionPack.Xml.XmlTask TaskAction="Transform"
                                       Conformance="Document"
                                       XslTransformFile="TrxTransform.xslt"
                                       XmlFile="%(TheTrx.FullPath)">

      <Output PropertyName="TrxTransformed" TaskParameter="Output"/>
    </MSBuild.ExtensionPack.Xml.XmlTask>
    <MSBuild.ExtensionPack.Communication.Email TaskAction="Send" Subject="$(RequestedFor)'s WatiN tests for $(MainBuildLabelToDeploy)" SmtpServer="mail.tempuri.org" MailFrom="buildservice@tempuri.org" MailTo="lebobitz@tempuri.org" Body="$(TrxTransformed)" />
  </Target>
...
Advertisements
Categories: .Net, MsBuild, WatiN
  1. May 15, 2011 at 11:13 pm

    have you tried http://trx2html.codeplex.com ?

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: