如何将Selenium Java测试结果同步至Azure DevOps测试计划?
解决Java Selenium测试结果同步到Azure DevOps测试计划的方案
针对你用Java Selenium执行测试、需要同步结果(状态+截图)到Azure DevOps手动测试用例的需求,直接通过Azure DevOps REST API实现的步骤如下:
1. 准备必要信息与权限
- 个人访问令牌(PAT):在Azure DevOps个人设置中创建,权限需勾选
Test Management的Read & Write。 - 关键ID:
- 测试计划ID:从测试计划页面URL提取(如
.../TestPlans/Edit?planId=123中的123) - 测试套件ID:测试套件页面URL中的
suiteId参数值 - 手动测试用例ID:每个目标用例在Azure DevOps详情页显示的ID
- 测试计划ID:从测试计划页面URL提取(如
2. Java中API请求的基础配置
用JDK11+自带的HttpClient发送请求,请求头需包含认证信息:
import java.util.Base64; // PAT前加冒号再做Base64编码,生成认证头 String pat = "你的PAT令牌"; String authHeader = "Basic " + Base64.getEncoder().encodeToString((":".concat(pat)).getBytes());
3. 创建测试运行
先创建关联目标测试计划和套件的测试运行,后续结果都归属到该运行下:
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; public class AzureDevOpsSync { private static final String ORG = "你的组织名"; private static final String PROJ = "你的项目名"; private static final String API_VERSION = "7.1-preview.3"; private static final HttpClient client = HttpClient.newHttpClient(); public static String createTestRun(String authHeader, int planId, int suiteId) throws Exception { String url = String.format("https://dev.azure.com/%s/%s/_apis/test/runs?api-version=%s", ORG, PROJ, API_VERSION); String requestBody = String.format(""" { "name": "Selenium自动化测试运行-%s", "plan": {"id": %d}, "suite": {"id": %d}, "isAutomated": false }""", System.currentTimeMillis(), planId, suiteId); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Authorization", authHeader) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); // 简化解析测试运行ID,实际建议用Jackson/Gson等JSON库 return response.body().split("\"id\":")[1].split(",")[0].trim(); } }
4. 获取测试点ID(关联手动用例必需)
每个手动用例在测试套件中对应一个测试点,需获取其ID来绑定结果:
public static String getTestPointId(String authHeader, int planId, int suiteId, int testCaseId) throws Exception { String url = String.format("https://dev.azure.com/%s/%s/_apis/test/plans/%d/suites/%d/testpoints?api-version=7.1-preview.2&testCaseId=%d", ORG, PROJ, planId, suiteId, testCaseId); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Authorization", authHeader) .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return response.body().split("\"id\":")[1].split(",")[0].trim(); }
5. 更新测试结果并上传截图
创建测试结果标记成功/失败状态,再上传截图作为附件:
// 创建测试结果 public static String createTestResult(String authHeader, String runId, int testCaseId, String testPointId, boolean isPassed) throws Exception { String url = String.format("https://dev.azure.com/%s/%s/_apis/test/runs/%s/results?api-version=7.1-preview.6", ORG, PROJ, runId); String outcome = isPassed ? "Passed" : "Failed"; String requestBody = String.format(""" [ { "testCaseId": %d, "testPoint": {"id": %s}, "outcome": "%s", "comment": "Selenium自动化测试执行结果" } ]""", testCaseId, testPointId, outcome); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Authorization", authHeader) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); return response.body().split("\"id\":")[1].split(",")[0].trim(); } // 上传截图附件 public static void uploadScreenshot(String authHeader, String runId, String resultId, String screenshotPath) throws Exception { String url = String.format("https://dev.azure.com/%s/%s/_apis/test/runs/%s/results/%s/attachments?api-version=7.1-preview.1", ORG, PROJ, runId, resultId); byte[] screenshotBytes = java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(screenshotPath)); String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; String body = String.format(""" --%s Content-Disposition: form-data; name="Attachment"; filename="screenshot.png" Content-Type: image/png %s --%s--""", boundary, new String(screenshotBytes, StandardCharsets.ISO_8859_1), boundary); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Authorization", authHeader) .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.ISO_8859_1)) .build(); client.send(request, HttpResponse.BodyHandlers.discarding()); }
6. 整合执行流程
在Selenium测试结束后,调用上述方法完成同步:
public static void main(String[] args) { try { String authHeader = "Basic " + Base64.getEncoder().encodeToString((":你的PAT").getBytes()); int planId = 123; int suiteId = 456; int testCaseId = 789; String screenshotPath = "测试失败截图的本地路径"; boolean testPassed = false; // 根据实际测试结果设置 String runId = createTestRun(authHeader, planId, suiteId); String testPointId = getTestPointId(authHeader, planId, suiteId, testCaseId); String resultId = createTestResult(authHeader, runId, testCaseId, testPointId, testPassed); // 仅测试失败时上传截图,可根据需求调整 if (!testPassed) { uploadScreenshot(authHeader, runId, resultId, screenshotPath); } } catch (Exception e) { e.printStackTrace(); } }
注意事项
- 实际项目建议用Jackson/Gson等JSON库解析API响应,避免字符串分割的脆弱性
- 流水线中执行时,将PAT作为秘密变量存储,禁止硬编码
- 检查API响应状态码:401代表PAT权限不足,404代表输入的ID不存在
内容的提问来源于stack exchange,提问作者Ahmed Alaa Ewis
相关产品推荐
相关产品推荐

