Click here to Skip to main content
15,867,686 members
Articles / General Programming / Tools
Article

MSTest or TRX to HTML with Animated Charts

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
16 Oct 2012CPOL6 min read 90.5K   15   32
MS Test Result Viewer is a free open source library to convert MS Test result (.trx) file into HTML. It is also allowed you to perform MS Test on your test container project (.dll) file. This utility will work with simple command line arguments to generate test report in HTML format with excellent U

Image 1

Animated Test Result ChartsTest Result Error WindowPrint Report
MSTest result in Charts  MSTest Error Window Print Report

Introduction  

This tool will allows you to generate HTML report as well as you can perform MS Test on your test project container. This provides a number of advantages like, It allows for easy state management in your tests. Your setup and tear down code will be run each time a method is called and your instance variables are automatically reset. There’s no need to reset them manually as it is in NUnit. Indeed, many of MSTest’s other strengths rely on this very principle as we shall see.

About MSTest  from Microsoft's site.

"MSTest.exe is the command-line command that is used to run tests. This command has several options you can use to customize your test run. You can use many of these options in conjunction with one another; in fact, you must use certain options in conjunction with others, as  described in the following sections. You can specify these options in any order on the MSTest.exe command line."

for more detail:  http://msdn.microsoft.com/en-us/library/ms182489%28v=vs.80%29.aspx

Using the code 

Image 5
  •  Variable Declaration
  • Defined constants for TableName, Columns, Tags, Attributes and ExecutionSleep as well as some variables are defined for access in more than one methods.
    C#
    #region "Variables"
    const int SLEEP_EXECUTION_TIME = 200;
    const string TAG_UNITTEST = "UnitTest";
    const string TAG_ERRORINFO = "ErrorInfo";
     
    const string TABLE_TESTMETHOD = "TestMethod";
    const string TABLE_TESTRUN = "TestRun";
    const string TABLE_UNITTESTRESULT = "UnitTestResult";
    const string TABLE_TIMES = "Times";
     
    const string COLUMN_CLASSNAME = "className";
    const string COLUMN_NAME = "name";
    const string COLUMN_RUNUSER = "runUser";
    const string COLUMN_MESSAGE = "Message";
    const string COLUMN_STACKTRACE = "StackTrace";
    const string COLUMN_COUNTERS = "Counters";
    const string COLUMN_TOTAL = "total";
    const string COLUMN_PASSED = "passed";
    const string COLUMN_FAILED = "failed";
    const string COLUMN_INCONCLUSIVE = "inconclusive";
    const string COLUMN_TESTNAME = "testName";
    const string COLUMN_OUTCOME = "outcome";
    const string COLUMN_DURATION = "duration";
    const string COLUMN_CREATION = "creation";
    const string COLUMN_CODEBASE = "codebase";
     
    const string ATTRIBUTE_TESTMETHODID = "TestMethodID";
    const string ATTRIBUTE_ID = "id";
    const string ATTRIBUTE_TESTID = "testId";
    const string FILENAME_TRX = "MSTestResult.trx";
     
    private static TestEnvironmentInfo testEnvironmentInfo;
    private static TestResultSummary testResultSummary;
    private static List<TestProjects> testProjects;
     
    static string projectChartDataValue = "";
    static string classChartDataValue = "";
    static string classChartDataText = "";
    static string methoChartDataValue = "";
    static string methoChartDataText = "";
    static string methoChartDataColor = "";
    static string folderPath = "";
    static string trxFilePath = "";
     
    static string MSTestExePathParam = "";
    static string TestContainerFolderPathParam = "";
    static string DestinationFolderParam = "";
    static string LogFileParam = "";
    static string HelpFileParam = "";
    #endregion
  • Properties
  • To get Application Name and Version number for displaying on HTML Report.
    C#
    public static string Title
    {
        get { return GetValue<AssemblyTitleAttribute>(a => a.Title); }
    }
    public static string Version
    {
        get { return "1.0"; }
    }
    static string GetValue<T>(Func<T, string> getValue) where T : Attribute
    {
        T a = (T)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(T));
        return a == null ? "" : getValue(a);
    }
  • Command-line Parameters
  • This is a main or startup function of the application, it will require some parameter to generate HTML report successfully.


    NameFlagSummary 
    MSTest Exe File Path/m
    You have to define physical path of MSTest.exe. this will required only if you don’t have .trx file.
    Test Container Folder Path/tc
    You have to define physical path of your test container file (.dll). this will required only if you don’t have .trx file.
    Trx File Path/tYou have to define physical path of trx file.
    Destination Folder Path/dOutput data (HTML) files will store at this location. If path not exists then it will automatically created.
    Help?This will display all command parameters on screen 
  • Command Line Usage
  • C#
    MSTestResultViewer.Consol.exe /t "d:\test.trx" /d "d:\HTMLReport" 
  • Private Methods 
    • Transform
    • This method will fill dataset using xml file, and then dataset will use for calculating all the statistics and information for Test cases and environmental details.
      C#
      private static void Transform(string fileName)
      {
          XmlDocument xmlDoc = new XmlDocument();
          if (File.Exists(fileName))
          {
              xmlDoc.Load(fileName);
              XmlNodeList list = xmlDoc.GetElementsByTagName(TAG_UNITTEST);
              foreach (XmlNode node in list)
              {
                  XmlAttribute newAttr = xmlDoc.CreateAttribute(ATTRIBUTE_TESTMETHODID);
                  newAttr.Value = node.Attributes[ATTRIBUTE_ID].Value;
                  node.ChildNodes[1].Attributes.Append(newAttr);
              }
       
              list = xmlDoc.GetElementsByTagName(TAG_ERRORINFO);
              foreach (XmlNode node in list)
              {
                  XmlAttribute newAttr = xmlDoc.CreateAttribute(ATTRIBUTE_TESTMETHODID);
                  newAttr.Value = (((node).ParentNode).ParentNode).Attributes[ATTRIBUTE_TESTID].Value;
                  node.Attributes.Append(newAttr);
              }
              //xmlDoc.Save(fileName);
      
              DataSet ds = new DataSet();
              ds.ReadXml(new XmlNodeReader(xmlDoc));
       
              if (ds != null && ds.Tables.Count >= 4)
              {
                  Console.WriteLine(string.Format("Start gathering test environment information...\n"));
                  System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
                  SetTestEnvironmentInfo(ds);
       
                  Console.WriteLine(string.Format("Start gathering test result summary...\n"));
                  System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
                  SetTestResultSummary(ds);
       
                  Console.WriteLine(string.Format("Start gathering test classes methods information...\n"));
                  System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
                  SetTestClassMethods(ds);
       
                  if (testProjects.Count >= 1)
                  {
                      Console.WriteLine(string.Format("Start transforming test result into html...\n"));
                      System.Threading.Thread.Sleep(SLEEP_EXECUTION_TIME);
       
                      CreateTestHierarchy();
       
                      CreateTestResultTable();
       
                      CreateTestResultChart();
       
                      Console.WriteLine(string.Format("TRX file transformation completed successfully. \nFile generated at: \"{0}.htm\"\n", trxFilePath));
                  }
                  else
                  {
                      Console.WriteLine(string.Format("No test cases are available for test\n"));
                      Console.ReadLine();
                  }
              }
              else
              {
                  Console.WriteLine(string.Format("No test cases are available for test\n"));
                  Console.ReadLine();
              }
          }
          else
          {
              Console.WriteLine(string.Format("Test Result File (.trx) not found at \"" + trxFilePath + "\"!\n"));
              Console.ReadLine();
          }
      }
    • SetTestEnvironmentInfo
    • This function will get test machine environment information like Machine Name, Original TRX File Name, User Name, Timestamp etc.
      C#
      private static void SetTestEnvironmentInfo(DataSet ds)
      {
          DataRow dr = ds.Tables[TABLE_TESTRUN].Rows[0];
          string trxfile = dr[COLUMN_NAME].ToString();
          int idxattherate = trxfile.IndexOf("@") + 1;
          int idxspace = trxfile.IndexOf(" ");
          string machine = trxfile.Substring(idxattherate, idxspace - idxattherate);
          DateTime time = (ds.Tables[TABLE_TIMES] != null && ds.Tables[TABLE_TIMES].Rows.Count > 0) ? Convert.ToDateTime(ds.Tables[TABLE_TIMES].Rows[0][COLUMN_CREATION]) : DateTime.Now;
          string codebase = (ds.Tables[TABLE_TESTMETHOD] != null && ds.Tables[TABLE_TESTMETHOD].Rows.Count > 0) ? ds.Tables[TABLE_TESTMETHOD].Rows[0][COLUMN_CODEBASE].ToString() : "";
          testEnvironmentInfo = new TestEnvironmentInfo()
          {
              MachineName = machine,
              OriginalTRXFile = trxfile,
              TestCodebase = codebase,
              UserName = dr[COLUMN_RUNUSER].ToString(),
              Timestamp = time
          };
      }
    • SetTestResultSummary
    • This function show overall statistics of test result like Passed, Failed, Ignored and Duration.
      C#
      private static void SetTestResultSummary(DataSet ds)
      {
          DataRow dr = ds.Tables[COLUMN_COUNTERS].Rows[0];
          testResultSummary = new TestResultSummary()
          {
              Total = Convert.ToInt16(dr[COLUMN_TOTAL].ToString()),
              Passed = Convert.ToInt16(dr[COLUMN_PASSED].ToString()),
              Failed = Convert.ToInt16(dr[COLUMN_FAILED].ToString()),
              Ignored = Convert.ToInt16(dr[COLUMN_INCONCLUSIVE].ToString()),
              Duration = "--",
              TestEnvironment = testEnvironmentInfo
          };
      }
    • SetTestClassMethods
    • This will create hierarchy in order to generate Test Project, Test Class and Test Method with its statistics.
      C#
      private static void SetTestClassMethods(DataSet ds)
      {
          DataView view = new DataView(ds.Tables[TABLE_TESTMETHOD]);
          DataTable distinctValues = view.ToTable(true, COLUMN_CLASSNAME);
          char[] delimiters = new char[] { ',' };
       
          //Getting Distinct Project
          testProjects = new List<TestProjects>();
          foreach (DataRow dr in distinctValues.Rows)
          {
              string _project = dr[COLUMN_CLASSNAME].ToString().Split(delimiters, StringSplitOptions.RemoveEmptyEntries)[1].Trim();
              int cnt = (from t in testProjects where t.Name == _project select t).Count();
              if (cnt == 0) testProjects.Add(new TestProjects() { Name = _project.Trim() });
          }
       
          //Iterate through all the projects for getting its classes
          foreach (TestProjects project in testProjects)
          {
              DataRow[] classes = distinctValues.Select(COLUMN_CLASSNAME + " like '% " + project.Name + ", %'");
              if (classes != null && classes.Count() > 0)
              {
                  project.Classes = new List<TestClasses>();
                  foreach (DataRow dr in classes)
                  {
                      string _class = dr[COLUMN_CLASSNAME].ToString().Split(delimiters, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
                      project.Classes.Add(new TestClasses() { Name = _class });
                  }
              }
          }
       
          //Iterate through all the projects and then classes to get test methods details
          TimeSpan durationProject = TimeSpan.Parse("00:00:00.00");
          foreach (TestProjects _project in testProjects)
          {
              Int32 _totalPassed = 0;
              Int32 _totalFailed = 0;
              Int32 _totalIgnored = 0;
              foreach (TestClasses _class in _project.Classes)
              {
                  TimeSpan durationClass = TimeSpan.Parse("00:00:00.00");
                  DataRow[] methods = ds.Tables[TABLE_TESTMETHOD].Select(COLUMN_CLASSNAME + " like '" + _class.Name + ", " + _project.Name + ", %'");
                  if (methods != null && methods.Count() > 0)
                  {
                      _class.Methods = new List<TestClassMethods>();
                      Int32 _passed = 0;
                      Int32 _failed = 0;
                      Int32 _ignored = 0;
                      foreach (DataRow dr in methods)
                      {
                          TimeSpan durationMethod = TimeSpan.Parse("00:00:00.00");
                          TestClassMethods _method = GetTestMethodDetails(ds, dr[ATTRIBUTE_TESTMETHODID].ToString());
                          switch (_method.Status.ToUpper())
                          {
                              case "PASSED":
                                  _passed++;
                                  break;
                              case "FAILED":
                                  _failed++;
                                  break;
                              default:
                                  _ignored++;
                                  break;
                          }
                          _class.Passed = _passed;
                          _class.Failed = _failed;
                          _class.Ignored = _ignored;
                          _class.Total = (_passed + _failed + _ignored);
                          _class.Methods.Add(_method);
       
                          durationClass += TimeSpan.Parse(_method.Duration);
                      }
                  }
                  _totalPassed += _class.Passed;
                  _totalFailed += _class.Failed;
                  _totalIgnored += _class.Ignored;
       
                  _class.Duration = durationClass.ToString();
                  durationProject += TimeSpan.Parse(_class.Duration);
              }
              _project.Passed = _totalPassed;
              _project.Failed = _totalFailed;
              _project.Ignored = _totalIgnored;
              _project.Total = (_totalPassed + _totalFailed + _totalIgnored);
       
              _project.Duration = durationProject.ToString();
              durationProject += TimeSpan.Parse(_project.Duration);
          }
      }
    • GetTestMethodDetails
    • This method will return Test Case Result it is failed. This will return information like Error Description, Stack Trace, Method Name and Line Number.
      C#
      private static TestClassMethods GetTestMethodDetails(DataSet ds, string testID)
      {
          TestClassMethods _method = null;
          DataRow[] methods = ds.Tables[TABLE_UNITTESTRESULT].Select(ATTRIBUTE_TESTID + "='" + testID + "'");
          if (methods != null && methods.Count() > 0)
          {
              _method = new TestClassMethods();
              foreach (DataRow dr in methods)
              {
                  _method.Name = dr[COLUMN_TESTNAME].ToString();
                  _method.Status = dr[COLUMN_OUTCOME].ToString();//(Enums.TestStatus)Enum.Parse(typeof(Enums.TestStatus), dr[COLUMN_OUTCOME].ToString());
                  _method.Error = GetErrorInfo(ds, testID);
                  _method.Duration = dr[COLUMN_DURATION].ToString();
              }
          }
          return _method;
      }
    • GetErrorInfo
    • This is helper method to get information of Test Result.
      C#
      private static ErrorInfo GetErrorInfo(DataSet ds, string testID)
      {
          ErrorInfo _error = null;
          DataRow[] errorMethod = ds.Tables[TAG_ERRORINFO].Select(ATTRIBUTE_TESTMETHODID + "='" + testID + "'");
          if (errorMethod != null && errorMethod.Count() > 0)
          {
              _error = new ErrorInfo();
              string[] delimiters = new string[] { ":line " };
              foreach (DataRow dr in errorMethod)
              {
                  _error.Message = dr[COLUMN_MESSAGE].ToString();
                  _error.StackTrace = dr[COLUMN_STACKTRACE].ToString();
                  string strLineNo = _error.StackTrace.Split(delimiters, StringSplitOptions.RemoveEmptyEntries)[1];
                  Int32 LineNo = Convert.ToInt32(strLineNo);
                  _error.LineNo = LineNo;
              }
          }
          return _error;
      }
    • CreateTestHierarchy
    • This will create Hierarchy of your Test Project with status of every Test Method in chronological order. Like first it will display Test Project Name and then list of all the Test Classes under the test project and then list of all those Test Methods that are related to it's test class. And create one dynamic javascript (TestHierarchy.js) file to display tree on HTML report.
      C#
      private static void CreateTestHierarchy()
      {
          StringBuilder sb = new StringBuilder();
          sb.Append("var treeData = '");
          foreach (var _project in testProjects)
          {
              sb.Append("<li><span class=\"testProject\">" + _project.Name + "</span>");
              sb.Append("<ul>");
              foreach (var _class in _project.Classes)
              {
                  //Remove project name from name space if exists.
                  string classname = _class.Name;
                  string projectname = _project.Name + ".";
                  string[] tmp = _class.Name.Split(new string[] { projectname }, StringSplitOptions.RemoveEmptyEntries);
                  if (tmp.Length >= 2)
                      classname = (tmp[0] == _project.Name) ? ConvertStringArrayToString(tmp, 1) : tmp[0];
                  else if (tmp.Length == 1)
                      classname = tmp[0];
       
                  sb.Append("<li><span class=\"testClass\">" + classname + "</span>");
                  sb.Append("<ul>");
                  foreach (var _method in _class.Methods)
                  {
                      string imgStatus = "StatusFailed";
                      switch (_method.Status)
                      {
                          case "Passed":
                              imgStatus = "StatusPassed";
                              break;
                          case "Ignored":
                              imgStatus = "StatusIngnored";
                              break;
                          case "Failed":
                              imgStatus = "StatusFailed";
                              break;
                      }
                      sb.Append("<li><span class=\"testMethod\">" + _method.Name + "<img src=\"Images/" + imgStatus + ".png\" height=\"10\" width=\"10\" /></span></li>");
                  }
                  sb.Append("</ul>");
                  sb.Append("</li>");
              }
              sb.Append("</ul>");
              sb.Append("</li>");
          }
          sb.Append("';");
          string htmlTestHierarchy = sb.ToString();
          WriteFile("TestHierarchy.js", htmlTestHierarchy);
      }
    • ConvertStringArrayToString
    • This is helper method to get single string from list of several item in Array. It is a concatenate of array elements into single string.
      C#
      private static string ConvertStringArrayToString(string[] array, int startIndex)
      {
          StringBuilder builder = new StringBuilder();
          for (int i = startIndex; i < array.Length; i++)
          {
              builder.Append(array[i]);
          }
          return builder.ToString();
      }
    • CreateTestResultTable
    • This method will display complete information about your test container with Total Number of Passed, Failed and Ignored methods into tree and grid (Advanced Tabular) format. With additional information like total time taken to execute method, progress indicator for test method status like if it passed or failed.
      While clicking on any test method a Error Window (at Bottom side of page) will appear with complete error information.
      Again this method will create a dynamic javascript file (Table.js) to display data in TreeGrid format.
      C#
      private static void CreateTestResultTable()
      {
          try
          {
              StringBuilder sbenv = new StringBuilder();
              sbenv.Append("var environment = {");
              sbenv.Append("'TestCodebase':'" + testEnvironmentInfo.TestCodebase + "',");
              sbenv.Append("'Timestamp':'" + testEnvironmentInfo.Timestamp + "',");
              sbenv.Append("'MachineName':'" + testEnvironmentInfo.MachineName + "',");
              sbenv.Append("'UserName':'" + testEnvironmentInfo.UserName + "',");
              sbenv.Append("'OriginalTRXFile':'" + testEnvironmentInfo.OriginalTRXFile + "'");
              sbenv.Append("};");
              WriteFile("Environment.js", sbenv.ToString());
       
              StringBuilder sb = new StringBuilder();
              sb.Append("$(function () {");
              sb.Append(" $('#dvTestCodebase').text(environment.TestCodebase);");
              sb.Append(" $('#dvGeneratedDate').text(environment.Timestamp);");
              sb.Append(" $('#dvMachineName').text(environment.MachineName);");
              sb.Append(" $('#dvUserName').text(environment.UserName);");
              sb.Append(" $('#dvTRXFileName').text(environment.OriginalTRXFile);");
              sb.Append("var mydata = [");
              int counter = 0;
       
              foreach (var _project in testProjects)
              {
                  counter++;
                  int total = _project.Passed + _project.Failed + _project.Ignored;
                  double percentPass = (_project.Passed * 100);
                  if (percentPass > 0) percentPass = percentPass / total;
                  double percentFail = (_project.Failed * 100);
                  if (percentFail > 0) percentFail = percentFail / total;
                  double percentIgnore = (_project.Ignored * 100);
                  if (percentIgnore > 0) percentIgnore = percentIgnore / total;
                  string strPercent = string.Format("{0},{1},{2}", percentPass, percentFail, percentIgnore);
                  string strProject = string.Format("{{id: \"{0}\", parent: \"{1}\", level: \"{2}\", Name:  \"{3}\", Passed: \"{4}\", Failed: \"{5}\", Ignored: \"{6}\", Percent: \"{7}\", Progress: \"{8}\", Time: \"{9}\", Message: \"{10}\", StackTrace: \"{11}\", LineNo: \"{12}\", isLeaf: {13}, expanded: {14}, loaded: {15}}},", counter, "", "0", _project.Name, _project.Passed, _project.Failed, _project.Ignored, string.Format("{0:00.00}", percentPass), strPercent, TimeSpan.Parse(_project.Duration).TotalMilliseconds, "", "", "", "false", "true", "true");
                  sb.Append(strProject);
                  int projParent = counter;
       
                  projectChartDataValue = "var projectData = [" + _project.Passed + ", " + _project.Failed + ", " + _project.Ignored + "];";
       
                  foreach (var _class in _project.Classes)
                  {
                      counter++;
                      total = _class.Passed + _class.Failed + _class.Ignored;
                      percentPass = (_class.Passed * 100);
                      if (percentPass > 0) percentPass = percentPass / total;
                      percentFail = (_class.Failed * 100);
                      if (percentFail > 0) percentFail = percentFail / total;
                      percentIgnore = (_class.Ignored * 100);
                      if (percentIgnore > 0) percentIgnore = percentIgnore / total;
                      strPercent = string.Format("{0},{1},{2}", percentPass, percentFail, percentIgnore);
       
                      //Remove project name from name space if exists.
                      string classname = _class.Name;
                      string projectname = _project.Name + ".";
                      string[] tmp = _class.Name.Split(new string[] { projectname }, StringSplitOptions.RemoveEmptyEntries);
                      if (tmp.Length >= 2)
                          classname = (tmp[0] == _project.Name) ? ConvertStringArrayToString(tmp, 1) : tmp[0];
                      else if (tmp.Length == 1)
                          classname = tmp[0];
       
                      string strClass = string.Format("{{id: \"{0}\", parent: \"{1}\", level: \"{2}\", Name:  \"{3}\", Passed: \"{4}\", Failed: \"{5}\", Ignored: \"{6}\", Percent: \"{7}\", Progress: \"{8}\", Time: \"{9}\", Message: \"{10}\", StackTrace: \"{11}\", LineNo: \"{12}\", isLeaf: {13}, expanded: {14}, loaded: {15}}},", counter, projParent, "1", classname, _class.Passed, _class.Failed, _class.Ignored, string.Format("{0:00.00}", percentPass), strPercent, TimeSpan.Parse(_class.Duration).TotalMilliseconds, "", "", "", "false", "true", "true");
                      sb.Append(strClass);
                      int classParent = counter;
       
                      classChartDataValue += "[" + _class.Passed + ", " + _class.Failed + ", " + _class.Ignored + "],";
                      classChartDataText += "'" + classname + "',";
       
                      foreach (var _method in _class.Methods)
                      {
                          counter++;
                          int _passed = 0;
                          int _failed = 0;
                          int _ignored = 0;
                          percentPass = 0.0;
                          strPercent = "";
       
                          methoChartDataValue += TimeSpan.Parse(_method.Duration).TotalMilliseconds + ",";
                          methoChartDataText += "'" + _method.Name + "',";
       
                          switch (_method.Status)
                          {
                              case "Passed":
                                  _passed = 1;
                                  percentPass = 100;
                                  strPercent = "100,0,0";
                                  methoChartDataColor += "testResultColor[0],";
                                  break;
                              case "Failed":
                                  _failed = 1;
                                  strPercent = "0,100,0";
                                  methoChartDataColor += "testResultColor[1],";
                                  break;
                              case "Ignored":
                                  _ignored = 1;
                                  strPercent = "0,0,100";
                                  methoChartDataColor += "testResultColor[2],";
                                  break;
                          }
       
                          string strError = "";
                          string strStack = "";
                          string strLine = "";
                          if (_method.Error != null)
                          {
                              strError = _method.Error.Message.Replace("\r\n", "").Replace("\"", "'");
                              strStack = _method.Error.StackTrace.Replace("\r\n", "").Replace("\"", "'");
                              strLine = _method.Error.LineNo.ToString();
                          }
       
                          string strMethod = string.Format("{{id: \"{0}\", parent: \"{1}\", level: \"{2}\", Name:  \"{3}\", Passed: \"{4}\", Failed: \"{5}\", Ignored: \"{6}\", Percent: \"{7}\", Progress: \"{8}\", Time: \"{9}\", Message: \"{10}\", StackTrace: \"{11}\", LineNo: \"{12}\", isLeaf: {13}, expanded: {14}, loaded: {15}}},", counter, classParent, "2", _method.Name, _passed, _failed, _ignored, string.Format("{0:00.00}", percentPass), strPercent, TimeSpan.Parse(_method.Duration).TotalMilliseconds, strError, strStack, strLine, "true", "false", "true");
                          sb.Append(strMethod);
                      }
                  }
       
                  classChartDataValue = "var classDataValue = [" + classChartDataValue + "];";
                  classChartDataText = "var classDataText = [" + classChartDataText + "];";
       
                  methoChartDataValue = "var methoDataValue = [" + methoChartDataValue + "];";
                  methoChartDataText = "var methoDataText = [" + methoChartDataText + "];";
                  methoChartDataColor = "var methoDataColor = [" + methoChartDataColor + "];";
              }
              sb.Append("],");
              sb.Append("getColumnIndexByName = function (grid, columnName) {");
              sb.Append("var cm = grid.jqGrid('getGridParam', 'colModel');");
              sb.Append("for (var i = 0; i < cm.length; i += 1) {");
              sb.Append("if (cm[i].name === columnName) {");
              sb.Append("return i;");
              sb.Append("}");
              sb.Append("}");
              sb.Append("return -1;");
              sb.Append("},");
              sb.Append("grid = $('#treegrid');");
              sb.Append("grid.jqGrid({");
              sb.Append("datatype: 'jsonstring',");
              sb.Append("datastr: mydata,");
              sb.Append("colNames: ['Id', 'Name', 'Passed', 'Failed', 'Ignored', '%', '', 'Time', 'Message','StackTrace','LineNo'],");
              sb.Append("colModel: [");
              sb.Append("{ name: 'id', index: 'id', width: 1, hidden: true, key: true },");
              sb.Append("{ name: 'Name', index: 'Name', width: 380 },");
              sb.Append("{ name: 'Passed', index: 'Passed', width: 70, align: 'right', formatter: testCounterFormat },");
              sb.Append("{ name: 'Failed', index: 'Failed', width: 70, align: 'right', formatter: testCounterFormat },");
              sb.Append("{ name: 'Ignored', index: 'Ignored', width: 70, align: 'right', formatter: testCounterFormat },");
              sb.Append("{ name: 'Percent', index: 'Percent', width: 50, align: 'right' },");
              sb.Append("{ name: 'Progress', index: 'Progress', width: 200, align: 'right', formatter: progressFormat },");
              sb.Append("{ name: 'Time', index: 'Time', width: 75, align: 'right'},");
              sb.Append("{ name: 'Message', index: 'Message', hidden: true, width: 100, align: 'right'},");
              sb.Append("{ name: 'StackTrace', index: 'StackTrace', hidden: true, width: 100, align: 'right'},");
              sb.Append("{ name: 'LineNo', index: 'LineNo', width: 100, hidden: true, align: 'right'}],");
              sb.Append("height: 'auto',");
              sb.Append("gridview: true,");
              sb.Append("rowNum: 10000,");
              sb.Append("sortname: 'id',");
              sb.Append("treeGrid: true,");
              sb.Append("treeGridModel: 'adjacency',");
              sb.Append("treedatatype: 'local',");
              sb.Append("ExpandColumn: 'Name',");
       
              sb.Append("ondblClickRow: function(id) {");
              sb.Append("parent.innerLayout.open('south');");
              sb.Append("setErrorInfo(id);");
              sb.Append("},");
       
              sb.Append("onSelectRow: function(id){");
              sb.Append("setErrorInfo(id);");
              sb.Append("},");
       
              sb.Append("jsonReader: {");
              sb.Append("repeatitems: false,");
              sb.Append("root: function (obj) { return obj; },");
              sb.Append("page: function (obj) { return 1; },");
              sb.Append("total: function (obj) { return 1; },");
              sb.Append("records: function (obj) { return obj.length; }");
              sb.Append("}");
              sb.Append("});");
       
              sb.Append("function setErrorInfo(id) {");
              sb.Append("var doc = $('#tblError', top.document);");
              sb.Append("doc.find('#dvErrorMessage').text($('#treegrid').getRowData(id)['Message']);");
              sb.Append("doc.find('#dvLineNumber').text($('#treegrid').getRowData(id)['LineNo']);");
              sb.Append("doc.find('#dvStackTrace').text($('#treegrid').getRowData(id)['StackTrace']);");
              sb.Append("}");
       
              sb.Append("function progressFormat(cellvalue, options, rowObject) {");
              sb.Append("var progress = cellvalue.split(',');");
              sb.Append("var pass = Math.round(progress[0]) * 2;");
              sb.Append("var fail = Math.round(progress[1]) * 2;");
              sb.Append("var ignore = Math.round(progress[2]) * 2;");
              sb.Append("var strProgress = \"<div class='ProgressWrapper'>");
              sb.Append("<div class='ProgressPass' title='\"+ Number(progress[0]).toFixed(2) +\"% Passed' style='width: \" + pass + \"px'></div>");
              sb.Append("<div class='ProgressFail' title='\"+ Number(progress[1]).toFixed(2) +\"% Failed' style='width: \" + fail + \"px'></div>");
              sb.Append("<div class='ProgressIgnore' title='\"+ Number(progress[2]).toFixed(2) +\"% Ignored' style='width: \" + ignore + \"px'></div>");
              sb.Append("</div>\";");
              sb.Append("return strProgress;");
              sb.Append("}");
       
              sb.Append("function testCounterFormat(cellvalue, options, rowObject) {");
              sb.Append("return cellvalue;");
              sb.Append("}");
              sb.Append("grid.jqGrid('setLabel', 'Passed', '', { 'text-align': 'right' });");
              sb.Append("grid.jqGrid('setLabel', 'Failed', '', { 'text-align': 'right' });");
              sb.Append("grid.jqGrid('setLabel', 'Ignored', '', { 'text-align': 'right' });");
              sb.Append("grid.jqGrid('setLabel', 'Percent', '', { 'text-align': 'right' });");
              sb.Append("});");
              string xmlTestResultTable = sb.ToString().Replace("},],", "}],");
              WriteFile("Table.js", xmlTestResultTable);
          }
          catch (Exception e)
          {
              Console.WriteLine("Exception: " + e.Message);
          }
      }
    • CreateTestResultChart
    • this method will create dynamic javascript (Chart.js) file to display Animated Chart (Pie and Bar).
      C#
      private static void CreateTestResultChart()
      {
          StringBuilder sb = new StringBuilder();
          sb.Append("var testResultStatus = ['Passed', 'Failed', 'Ignored'];");
          sb.Append("var testResultColor = ['#ABD874', '#E18D87', '#F4AD7C'];");
          sb.Append(projectChartDataValue);
          sb.Append(classChartDataValue);
          sb.Append(classChartDataText);
          sb.Append(methoChartDataValue);
          sb.Append(methoChartDataText);
          sb.Append(methoChartDataColor);
       
          string xmlTestResultTable = sb.ToString().Replace(",]", "]");
          WriteFile("Chart.js", xmlTestResultTable);
      }
    • WriteFile
    • This is helper method, It will write content in file and saved to destination folder.
      C#
      private static void WriteFile(string FileName, string FileContent)
      {
          using (System.IO.StreamWriter file = new System.IO.StreamWriter(Path.Combine(folderPath, FileName)))
          {
              file.WriteLine(FileContent);
          }
      }
    • GenerateTRXFile
    • This is very important method of this application, If you don't have TRX file then it will allows you to provide option for generate trx file using test container project (.dll).
      This method will create dynamic batch (mstestrunner.bat) file with three parameters. Using these parameters it will call from this application to perform MS Test on test project container. After successfully completion of batch command then it will create new file (.trx) for getting statistics of your test project.
      C#
      private static void GenerateTRXFile(string MsTestExePath, string TestContainerFilePath)
      {
          trxFilePath = Path.Combine(folderPath, FILENAME_TRX);
          string commandText = "\"" + MsTestExePath + "\" /testcontainer:\"" + TestContainerFilePath + "\" /resultsfile:\"" + trxFilePath + "\"";
          WriteFile("mstestrunner.bat", commandText);
          ExecuteBatchFile();
      }
    • ExecuteBatchFile
    • This is helper file, will responsible for executing any batch file using
      C#
      new Process()
      command.
      C#
      private static void ExecuteBatchFile()
      {
          Process process = new Process();
          process.StartInfo.UseShellExecute = false;
          process.StartInfo.RedirectStandardOutput = true;
          process.StartInfo.FileName = Path.Combine(folderPath, "mstestrunner.bat");
          process.Start();
          string output = process.StandardOutput.ReadToEnd();
          process.WaitForExit();
      }
    • RecognizeParameters
    • This method will only responsible for validating parameter passed by the users
      C#
      private static bool RecognizeParameters(string[] args)
      {
          if (args.Length >= 4 && args.Length <= 10)
          {
              int i = 0;
              while (i < args.Length)
              {
                  switch (args[i].ToLower())
                  {
                      case "/m":
                      case "/mstestexepath":
                      case "-m":
                      case "-mstestexepath":
                          if (args.Length > i)
                          {
                              MSTestExePathParam = args[i + 1];
                              i += 2;
                          }
                          else
                              return false;
                          break;
                      case "/tc":
                      case "/testcontainerfolderpath":
                      case "-tc":
                      case "-testcontainerfolderpath":
                          if (args.Length > i)
                          {
                              TestContainerFolderPathParam = args[i + 1];
                              i += 2;
                          }
                          else
                              return false;
                          break;
                      case "/t":
                      case "/trxfilepath":
                      case "-t":
                      case "-trxfilepath":
                          if (args.Length > i)
                          {
                              trxFilePath = args[i + 1];
                              i += 2;
                          }
                          else
                              return false;
                          break;
                      case "/d":
                      case "/destinationfolderpath":
                      case "-d":
                      case "-destinationfolderpath":
                          if (args.Length > i)
                          {
                              DestinationFolderParam = args[i + 1];
                              i += 2;
                          }
                          else
                              return false;
                          break;
                      case "/?":
                      case "/help":
                      case "-?":
                      case "-help":
                          return false;
                      default:
                          Console.WriteLine("Error: Unrecognized parameter\n");
                          return false;
                  }
              }
              return true;
          }
          return false;
      }
    • DisplayCommandLineHelp
    • This will show you help on scree, for each parameters. Using following command you will get comelete help for this application.
      C#
      MSTestResultViewer.Consol.exe /?h
      Code:

      C#
      private static void DisplayCommandLineHelp()
      {
          StringBuilder sb = new StringBuilder();
          sb.Append(string.Format("{0}.exe\n", Title));
          sb.Append(string.Format("[</M MSTestExePath>\n"));
          sb.Append(string.Format(" </TC TestContainerFolderPath>]    :optional if you set /T TRXFilePath\n"));
          sb.Append(string.Format("[</T TRXFilePath>]\n"));
          sb.Append(string.Format("</D DestinationFolder>             :not available\n"));
          sb.Append(string.Format("</L LogFile>                       :not available\n"));
          sb.Append(string.Format("</H HelpFile>                      :not available\n"));
          sb.Append(string.Format("</? Help>\n"));
          Console.Write(sb.ToString());
          Console.ReadKey();
      }
    • CopyFilesWithSubFolders
    • This is helper method, will take care to copy any files and folders from source to target. If the source folder doesn't exists then it will automatically created.
      cs"
      private static bool CopyFilesWithSubFolders(string SourcePath, string DestinationPath, bool overwriteexisting, string Pattern = "*.*")
      {
          bool ret = true;
          try
          {
              SourcePath = SourcePath.EndsWith(@"\") ? SourcePath : SourcePath + @"\";
              DestinationPath = DestinationPath.EndsWith(@"\") ? DestinationPath : DestinationPath + @"\";
       
              if (Directory.Exists(SourcePath))
              {
                  if (Directory.Exists(DestinationPath) == false) Directory.CreateDirectory(DestinationPath);
                  foreach (string fls in Directory.GetFiles(SourcePath, Pattern))
                  {
                      FileInfo flinfo = new FileInfo(fls);
                      flinfo.CopyTo(DestinationPath + flinfo.Name, overwriteexisting);
                  }
                  foreach (string drs in Directory.GetDirectories(SourcePath))
                  {
                      DirectoryInfo drinfo = new DirectoryInfo(drs);
                      if (CopyFilesWithSubFolders(drs, DestinationPath + drinfo.Name, overwriteexisting, Pattern) == false) ret = false;
                  }
              }
              else
              {
                  ret = false;
              }
          }
          catch (Exception ex)
          {
              ret = false;
          }
          return ret;
      }
  • Comments and Feedback
  • Creating and maintaining MsTestResultViewer has required – and still does – a considerable amount of work and effort.

    MsTestResultViewer a is free, and I hope that you will find it is useful. If you’d like to support future development and new product features, please make a your valuable comments, feedback, suggestions or appreciation via Comments, mail, messages, likes or share.

    These efforts are used to cover and improve product efficiency within timeframe.

  • Third-Party Notice and License Information
    • RGraph (http://www.rgraph.net
    • RGraph is free to use on non-commercial websites such as personal blogs, educational or charity sites - you don't need to buy a license - just link to the RGraph website on your own site. The full source is included in the download and you can edit it as you need.
    • jQuery UI Layout Plug-in (http://layout.jquery-dev.net
    • plug-in was inspired by the extJS border-layout, and recreates that functionality as a jQuery plug-in. The UI Layout plug-in can create any UI look you want - from simple headers or sidebars, to a complex application with toolbars, menus, help-panels, status bars, sub-forms, etc.
    • jQuery Grid Plugin – jqGrid (http://www.trirand.net/licensing.aspx)
    • is and will always be licensed under the most permissive and free MIT license. However, many customers and organizations require commercial grade licenses, support and features.

  • License 
  • Open-source licenses are also commonly free, allowing for modification, redistribution, and commercial use without having to pay to the original author. This is open source software and from here Web App Development Company Blog you will get updated setup, source code and complete usage guideline document for MsTestResultViewer.

     

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Infostretch Ahmedabad-Gujarat
India India
Aspiring for a challenging carrier wherein I can learn, grow, expand and share my existing knowledge in meaningful and coherent way.

sunaSaRa Imdadhusen


AWARDS:

  1. 2nd Best Mobile Article of January 2015
  2. 3rd Best Web Dev Article of May 2014
  3. 2nd Best Asp.Net article of MAY 2011
  4. 1st Best Asp.Net article of SEP 2010


Read More Articles...

Comments and Discussions

 
QuestionHow do you run this application exactly? Pin
sorianog1-Mar-17 4:21
sorianog1-Mar-17 4:21 
QuestionGot an error when i used MSTESTRESULTVIEWER.CONSOLE.exe without any error or description Pin
pradyumna kumar biswal5-Apr-16 22:46
pradyumna kumar biswal5-Apr-16 22:46 
QuestionMSTest TRX to HTML Viewer canbe found Pin
Vijaykumar Vadnal24-Sep-15 21:14
Vijaykumar Vadnal24-Sep-15 21:14 
Questionhtml file is not getting generated.. Pin
Member 32030624-Sep-15 2:57
Member 32030624-Sep-15 2:57 
Hi

i see all the 4 javascript files created but no html file is created.. which part of the code is responsible for html file generation?
QuestionPlease kindly to release a new version for VS2013 Pin
Member 1191689317-Aug-15 23:27
Member 1191689317-Aug-15 23:27 
QuestionNot able to generate the html file. Pin
er.debu8-Jun-15 19:14
er.debu8-Jun-15 19:14 
QuestionNot able to generate Report Pin
Member 442996223-Mar-15 20:20
Member 442996223-Mar-15 20:20 
Bugerror while processing trx Pin
pipll29-Dec-14 0:54
pipll29-Dec-14 0:54 
QuestionDocumentation? Pin
tollgen23-Oct-14 6:08
tollgen23-Oct-14 6:08 
QuestionUpdate for VS2012 Pin
Member 1077395624-Apr-14 20:26
Member 1077395624-Apr-14 20:26 
AnswerRe: Update for VS2012 Pin
tollgen23-Oct-14 5:15
tollgen23-Oct-14 5:15 
QuestionI am also getting an same error. Any update please? Pin
Member 101965617-Aug-13 18:09
Member 101965617-Aug-13 18:09 
AnswerRe: I am also getting an same error. Any update please? Pin
Sunasara Imdadhusen21-Aug-13 23:50
professionalSunasara Imdadhusen21-Aug-13 23:50 
AnswerRe: I am also getting an same error. Any update please? Pin
Sunasara Imdadhusen22-Apr-14 2:54
professionalSunasara Imdadhusen22-Apr-14 2:54 
GeneralRe: I am also getting an same error. Any update please? Pin
Member 1077395624-Apr-14 20:29
Member 1077395624-Apr-14 20:29 
QuestionGot an error without any hint's description when used MSTESTRESULTVIEWER.CONSOLE.exe Pin
fHirad20037-Aug-13 4:05
fHirad20037-Aug-13 4:05 
AnswerRe: Got an error without any hint's description when used MSTESTRESULTVIEWER.CONSOLE.exe Pin
Sunasara Imdadhusen21-Aug-13 23:49
professionalSunasara Imdadhusen21-Aug-13 23:49 
QuestionFix for VS2012 Pin
Member 810159226-Jun-13 4:59
Member 810159226-Jun-13 4:59 
AnswerRe: Fix for VS2012 Pin
Sunasara Imdadhusen22-Apr-14 2:57
professionalSunasara Imdadhusen22-Apr-14 2:57 
Question{"Column 'TestMethodID' does not belong to table TestMethod."} Pin
kfirav9-Jun-13 23:55
kfirav9-Jun-13 23:55 
AnswerRe: {"Column 'TestMethodID' does not belong to table TestMethod."} Pin
Sunasara Imdadhusen22-Apr-14 2:57
professionalSunasara Imdadhusen22-Apr-14 2:57 
BugError parsing trx file (I think) Pin
Atrejoe14-May-13 5:29
Atrejoe14-May-13 5:29 
GeneralRe: Error parsing trx file (I think) Pin
Atrejoe21-May-13 22:55
Atrejoe21-May-13 22:55 
GeneralRe: Error parsing trx file (I think) Pin
Sunasara Imdadhusen21-May-13 23:05
professionalSunasara Imdadhusen21-May-13 23:05 
GeneralGreat idea... but! Pin
wboatin26-Feb-13 6:55
wboatin26-Feb-13 6:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.