我们使用 C# selenium 和 Xunit(2.7.0) 进行自动化。我们有一个 Visual Studio 测试任务,可以通过测试选择触发“测试运行”。目前我们正在按照这个附件-创建测试结果附件来上传测试日志文件。我们没有遇到任何有关上传者逻辑的问题,但我们看不到任何附件。这是我们的逻辑 - 这就是我们如何称呼上传者
try
{
// Run asynchronous upload operations asynchronously
Task.Run(async () =>
{
try
{
// Upload log file
string pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT");
string runId = devOpsUploader.GetLatestRunId(pat);
var testResults = await devOpsUploader.FetchTestResults(runId);
string testCaseResultId = testResults.FirstOrDefault()?.Id.ToString() ?? "";
string comment = "Log file attachment";
string basePath = @"D:\a\1\s\TestProject1\bin\Debug\netcoreapp3.1\Logs";
string fileName = $"CreateTnMProjectTest_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
string fullPath = Path.Combine(basePath, fileName);
await devOpsUploader.UploadLogFile(runId, testCaseResultId, fullPath, comment);
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred while uploading log file: {ex.Message}");
}
}).Wait();
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred while running asynchronous operations: {ex.Message}");
}
这是我们的上传方法
public async Task UploadLogFile(string runId, string testCaseResultId, string logFilePath, string comment)
{
byte[] fileBytes = File.ReadAllBytes(logFilePath);
string fileContentBase64 = Convert.ToBase64String(fileBytes);
var streamContent = new ByteArrayContent(fileBytes);
streamContent.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");
var attachment = new
{
stream = fileContentBase64,
fileName = Path.GetFileName(logFilePath),
comment,
attachmentType = "GeneralAttachment"
};
string attachmentJson = JsonConvert.SerializeObject(attachment);
var content = new StringContent(attachmentJson, Encoding.UTF8, "application/json");
string apiUrl = $"_apis/test/Runs/{runId}/Results/{testCaseResultId}/attachments?api-version=6.0-preview.1";
string fullUrl = client.BaseAddress + apiUrl;
var response = await client.PostAsync(fullUrl, content);
if (!response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var responseHeaders = response.Headers.ToString();
Console.WriteLine($"Response Headers: {responseHeaders}");
Console.WriteLine($"Response Content: {responseContent}");
string errorMessage = await response.Content.ReadAsStringAsync();
throw new Exception($"Failed to upload log file: {errorMessage}");
}
else
{
Console.WriteLine("File uploaded successfully.");
Console.WriteLine($"Status Code: {(int)response.StatusCode} {response.ReasonPhrase}");
}
}
public string GetLatestRunId(string pat)
{
string organization = "*******";
string project = "Training";
// Construct the URL for fetching test runs
string apiUrl = $"https://dev.azure.com/{organization}/{project}/_apis/test/runs";
// Initialize HttpClient
using (HttpClient httpClient = new HttpClient())
{
// Set up HttpClient headers (authentication may be required)
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($":{pat}")));
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/octet-stream");
// Perform HTTP GET request to fetch test runs
HttpResponseMessage response = httpClient.GetAsync(apiUrl).Result;
// Check if the response is successful
response.EnsureSuccessStatusCode();
var testRunData = response.Content.ReadAsStringAsync().Result;
// Deserialize the response content into a collection of Run objects
var runs = JsonConvert.DeserializeObject<TestRunCollection>(testRunData);
// Ensure there are runs available
if (runs != null && runs.Count > 0)
{
// Sort the runs by StartTime in descending order to get the latest run first
var latestRun = runs.OrderByDescending(r => r.StartTime).First();
// Return the ID of the latest run
return latestRun.Id.ToString();
}
else
{
throw new InvalidOperationException("No test runs found.");
}
}
}
public async Task<List<TestResult>> FetchTestResults(string runId)
{
string organization = "********";
string project = "Training";
string apiUrl = $"https://dev.azure.com/{organization}/{project}/_apis/test/Runs/{runId}/Results?api-version=6.0-preview.1";
using (HttpClient httpClient = new HttpClient())
{
string pat = Environment.GetEnvironmentVariable("AZURE_DEVOPS_PAT");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($":{pat}")));
HttpResponseMessage response = await httpClient.GetAsync(apiUrl);
response.EnsureSuccessStatusCode();
var testResultData = await response.Content.ReadAsStringAsync();
var testResults = JsonConvert.DeserializeObject<List<dynamic>>(testResultData);
List<TestResult> resultList = new List<TestResult>();
foreach (var result in testResults)
{
// Create a new TestCase object
var testCase = new TestCase { Name = result.testCase.name };
// Create a new TestResult object and add it to the list
resultList.Add(new TestResult(testCase)
{
Id = result.id,
Outcome = result.outcome,
RunId = runId
}) ;
}
return resultList.Select(result => new TestResult(
new TestCase { Name = result.TestCase.Name }
)
{
Id = result.Id,
Outcome = result.Outcome,
RunId = runId // Add this line
}).ToList();
};
}
在本地 PowerShell 中使用此 API 进行测试期间,它可以通过管道为
VSTest
的测试结果创建附件文件。看来您还没有找到正确的测试结果或测试运行。
为此,建议使用此API通过BuildId获取测试结果(包括测试运行id),并使用另一个API添加测试结果附件。在下面的示例中,我在
VSTest
步骤之后在PowerShell脚本中调用了API请求,因为在VSTest
步骤完成并发布测试结果之前我们无法找到测试运行。
pool:
vmImage: windows-latest
variables:
orgName: ${{ split(variables['System.CollectionUri'], '/')[3] }} # Split orgName from URL like https://dev.azure.com/OrgName/
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'restore'
projects: '**/*.sln'
feedsToUse: 'select'
displayName: DotNet Restore
- task: VSBuild@1
inputs:
solution: '**\*.sln'
displayName: VS Build
- task: VSTest@3
inputs:
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\*test*.dll
!**\*TestAdapter.dll
!**\obj\**
searchFolder: '$(System.DefaultWorkingDirectory)'
displayName: VS Test
continueOnError: true
- powershell: |
Write-Host "orgName is $(orgName)"
# Generate attachment file for test
New-Item -Path "TestAttachmentFile-$(Build.BuildId).txt" -ItemType File -Value "TestAttachmentFile-$(Build.BuildId)"
cat TestAttachmentFile-$(Build.BuildId).txt
# Call Rest APIs authenticating against System.AccessToken
$headers = @{
'Authorization' = 'Bearer ' + "$(System.AccessToken)"
'Content-Type' = 'application/json'
}
Write-Host "================1. Get test results by Build.BuildId:================"
# Resultsbybuild - List GET https://vstmr.dev.azure.com/{organization}/{project}/_apis/testresults/resultsbybuild?buildId={buildId}&api-version=7.1-preview.1
$testResultsByBuildURL= "https://vstmr.dev.azure.com/$(orgName)/$(System.TeamProject)/_apis/testresults/resultsbybuild?buildId=$(Build.BuildId)&api-version=7.1-preview.1"
$testResultsByBuild = Invoke-RestMethod -Method Get -Uri $testResultsByBuildURL -Headers $headers
# Filter test results by test case title and extract test result ID
$testCaseTitle = "SucceededTest"
$targetTestResult = $testResultsByBuild.value | Where-Object { $_.testCaseTitle -eq $testCaseTitle }
if ($targetTestResult) {
$testResultId = $targetTestResult.id
$testRunId = $targetTestResult.runId
Write-Host "Test Result ID for '$testCaseTitle' of Test Run(id:$testRunId): $testResultId"
} else {
Write-Host "Test Result '$testResultName' not found."
}
Write-Host "================2. Create attachment for test result:================"
# Attachments - Create Test Result Attachment POST https://vstmr.dev.azure.com/{organization}/{project}/_apis/testresults/runs/{runId}/results/{testCaseResultId}/attachments?api-version=7.1-preview.1
$addAttachmentURL = "https://vstmr.dev.azure.com/$(orgName)/$(System.TeamProject)/_apis/testresults/runs/$testRunId/results/$testResultId/attachments?api-version=7.1-preview.1"
$FilePath = "$(System.DefaultWorkingDirectory)\TestAttachmentFile-$(Build.BuildId).txt"
$FileContent = Get-Content $FilePath -Raw
$fileContentInBytes = [System.Text.Encoding]::UTF8.GetBytes($FileContent)
$fileContentEncoded = [System.Convert]::ToBase64String($fileContentInBytes)
$body = @{
'stream' = $fileContentEncoded
'fileName' = 'TestAttachmentFile-$(Build.BuildId).txt'
'comment' = 'Create attachment for test result from $(Build.BuildId)'
'attachmentType' = 'GeneralAttachment'
} | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri $addAttachmentURL -Headers $headers -Body $body
displayName: Use APIs to locate and create attachment to test result