Specifying results filename for vstest.console.exe

May be a silly question, but does anybody know how to specify the output filename of a VSTEST.Console.exe run? My command line is as follows:

 vstest.console.exe [assembly] /logger:trx

At the end of the run, the following comes up in the console:

 ResultsFile: somepath\TestResults\{username}_{workstation} {timestamp}.trx

I tried using the .runsettings file to specify the output location, but that only seems to control the output directory, but not the output file. Have not found anything else that would seem to control it.

I want to parse the TRX file and generate a report out of it (this already works, but if I can't specify the output path of the TRX file, I won't know where to pick it up from in the scripts!)

I have to be missing something here...


EDIT: See @AnaFranco's answer - apparently since VS2017 the file name can be configured like so:

vstest.console.exe [assembly] /logger:trx;LogFileName=[filename].trx

I'll leave the old answer for posterity and pre-2017 versions.


Nope, you're not missing anything. The TRX logger doesn't support any parameters (unlike the TFS publisher logger).

The logger assembly is located in "C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.dll". If you check it out in your favorite .NET decompiler, you'll see the method TrxLogger.GetTrxFileName. It uses some basic knowledge about the current test run to produce the mangled name of form {username}_{workstation} {timestamp}.trx and is in no appreciable way configurable.

As far as I can tell, the TRX file is created in the TestResults\ folder under the current working directory unless otherwise configured. What you can do is:

  • Create a new temporary folder
  • Change the current directory to it
  • Run the test runner
  • Scan the folder for the result .trx file using your favorite recursive file search method and you're done

At least that is what I do in our build (MSBuild, sob):

<ItemGroup>
  <TestResult Include="**\*.trx"/>
</ItemGroup>

I.e, gather all .trx files under the current directory and stuff them into the @(TestResult) item group for further processing.


This has worked for me for testing .net core I haven't tried it with .net framework:

vstest.console.exe [assembly] /logger:trx;LogFileName=[filename].trx

Maybe its a new thing

Update: This works for .net framework projects too using the latest Test platform and vstest.console.exe


Apparently, you can specify a directory where to put the *.trx file (not of the file itself though). This is, however, done via .runsettings file rather than via command line.

Excerpt from Bhuvaneshwari's blog:

If the default location of results need to be overriden user need to pass this value using a runsettings file.

Example:

Mstest.exe /testcontainer:abc.dll /results:C:\Results.trx 

Vstest.console.exe abc.dll /settings:output.runsettings 

where the context of the .runsettings file would be something like below :

<?xml version="1.0" encoding="UTF-8"?> 
<RunSettings> 
  <RunConfiguration>
   <ResultsDirectory>c:\</ResultsDirectory>
  </RunConfiguration>
 </RunSettings>

I had this issue as well. I decided to write a MSBuild target that executes vstest.console via the EXEC task, handling all its outputs, including the coverage results.

Basically, I captured the vstest output and used a regex to capture the *.trx and *.coverage portion of the output, which turned out to be really easy. Plus, it removes the TestResults directory to keep the workspace nice and clean.

In the end, you will get the *.trx file and the *.coverage file (optionally).

The script may look a bit complex, but it was necessary to fit our needs. I tried to clean it up a bit. Hope this helps.

<Target Name="Test" DependsOnTargets="Build">
    <!-- Declare the defaults and arrange parameters -->
    <PropertyGroup>
      <ArtifactsPath Condition=" '$(ArtifactsPath)' == '' ">Artifacts</ArtifactsPath>
      <VSTestSessionName Condition=" '$(VSTestSessionName)' == ''">TestResults</VSTestSessionName>
      <VSTestExe Condition=" '$(VSTestExe)' == '' ">C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe</VSTestExe>
      <VSTestFailBuildOnTestFail Condition=" '$(VSTestFailBuildOnTestFail)' == '' ">false</VSTestFailBuildOnTestFail>
      <VSTestInIsolation Condition=" '$(VSTestInIsolation)' == '' ">true</VSTestInIsolation>
      <VSTestUseVsixExtensions Condition=" '$(VSTestUseVsixExtensions)' == '' ">true</VSTestUseVsixExtensions>
      <VSTestFramework Condition=" '$(VSTestFramework)' == '' ">framework45</VSTestFramework>
      <VSTestLogger Condition=" '$(VSTestLogger)' == '' ">trx</VSTestLogger>
      <ErrorCode>0</ErrorCode>
    </PropertyGroup>
    <ItemGroup>
      <VSTestResultsPath Include="$(VSTestResultsPath)" />
      <VSTestParams Include="@(VSTestFiles ->'&quot;%(FullPath)&quot;', ' ')" />
      <VSTestParams Condition="$(VSTestEnableCodeCoverage)" Include="/EnableCodeCoverage" />
      <VSTestParams Condition="$(VSTestInIsolation)" Include="/InIsolation" />
      <VSTestParams Include="/UseVsixExtensions:$(VSTestUseVsixExtensions)" />
      <VSTestParams Include="/Framework:$(VSTestFramework)" />
      <VSTestParams Include="/Logger:$(VSTestLogger)" />
      <VSTestParams Condition="$(VSTestCaseFilter) != ''" Include="/TestCaseFilter:&quot;$(VSTestCaseFilter)&quot;" />
      <VSTestParams Condition="$(VSTestRunSettings) != ''" Include="/Settings:&quot;$(VSTestRunSettings)&quot;" />
    </ItemGroup>

    <Message Text="TestAssembly: %(VSTestFiles.Identity)" Importance="high"/>

    <Exec ContinueOnError="!$(VSTestFailBuildOnTestFail)" ConsoleToMSBuild="true" WorkingDirectory="$(WorkingDirectory)" Condition=" '@(VSTestFiles)' != ''" 
          Command="&quot;$(VSTestExe)&quot; @(VSTestParams, ' ')">
      <Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
      <Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
    </Exec>
    <Message Importance="high" Text="VSTest exitcode: $(ErrorCode)"/>

    <!-- Use the VSTest output to discover the Results & Coverage files respectively -->
    <PropertyGroup>
      <!-- Unencoded Regex: (?<=(Results file: )).*?(?=\;)|(?<=(Attachments:;)).*?(?=\;) -->
      <ResultsFileRegexPattern>(?&lt;=(Results File: )).*.trx</ResultsFileRegexPattern>
      <CoverageFileRegexPattern>(?&lt;=(Attachments:;)).*.coverage</CoverageFileRegexPattern>
      <SourceResultsFile>$([System.Text.RegularExpressions.Regex]::Match($(OutputOfExec), $(ResultsFileRegexPattern)))</SourceResultsFile>
      <SourceCoverageFile Condition="$(VSTestEnableCodeCoverage)">$([System.Text.RegularExpressions.Regex]::Match($(OutputOfExec), $(CoverageFileRegexPattern)))</SourceCoverageFile>
    </PropertyGroup>

    <ItemGroup>
      <TestArtifact Include="$(SourceResultsFile)" />
      <TestArtifact Include="$(SourceCoverageFile)" />
    </ItemGroup>

    <Warning Condition=" '$(SourceResultsFile)' == '' " Text=".trx file not found" />
    <Warning Condition=" $(VSTestEnableCodeCoverage) and '$(SourceCoverageFile)' == '' " Text=".coverage file not found" />

    <!-- Copy files to the artifact directory -->
    <Copy SourceFiles="@(TestArtifact)" DestinationFiles="@(TestArtifact->'$(ArtifactsPath)\$(VSTestSessionName)%(Extension)')" />

    <!-- Clear the test results temporary directory -->
    <RemoveDir Directories="@(TestResults)" />

    <ItemGroup>
      <TestFile Include="$(ArtifactsPath)\**\$(VSTestSessionName).trx" />
      <CoverageFile Include="$(ArtifactsPath)\**\$(VSTestSessionName).coverage" />
    </ItemGroup>

    <Message Text="TestReport: @(TestFile)" />
    <Message Text="CoverageReport: @(CoverageFile)" />
</Target>