Skip to content

Commit 34b964d

Browse files
skmanojdanhellem
andauthored
Accepting expected result as a part of TestCase step instead of hardcoding (#381)
_Replace_ by description of the work done ## GitHub issue number #106 ## **Associated Risks** _Replace_ by possible risks this pull request can bring you might have thought of ## ✅ **PR Checklist** - [x] **I have read the [contribution guidelines](https://github.com/microsoft/azure-devops-mcp/blob/main/CONTRIBUTING.md)** - [x] **I have read the [code of conduct guidelines](https://github.com/microsoft/azure-devops-mcp/blob/main/CODE_OF_CONDUCT.md)** - [x] Title of the pull request is clear and informative. - [x] 👌 Code hygiene - [x] 🔭 Telemetry added, updated, or N/A - [x] 📄 Documentation added, updated, or N/A - [x] 🛡️ Automated tests added, or N/A ## 🧪 **How did you test it?** <img width="629" height="447" alt="image" src="https://github.com/user-attachments/assets/b01aa46a-ddf2-4c2c-9f11-23c54e62d42a" /> <img width="981" height="193" alt="image" src="https://github.com/user-attachments/assets/7b8bc3cf-f8fa-4c7c-92df-ed1fe0216f05" /> _Replace_ with use cases tested and models used --------- Co-authored-by: Dan Hellem <[email protected]>
1 parent 2a92403 commit 34b964d

File tree

2 files changed

+45
-4
lines changed

2 files changed

+45
-4
lines changed

src/tools/testplans.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ function configureTestPlanTools(server: McpServer, tokenProvider: () => Promise<
115115
{
116116
project: z.string().describe("The unique identifier (ID or name) of the Azure DevOps project."),
117117
title: z.string().describe("The title of the test case."),
118-
steps: z.string().optional().describe("The steps to reproduce the test case. Make sure to format each step as '1. Step one\\n2. Step two' etc."),
118+
steps: z.string().optional().describe("The steps to reproduce the test case. Make sure to format each step as '1. Step one|Expected result one\n2. Step two|Expected result two"),
119119
priority: z.number().optional().describe("The priority of the test case."),
120120
areaPath: z.string().optional().describe("The area path for the test case."),
121121
iterationPath: z.string().optional().describe("The iteration path for the test case."),
@@ -227,20 +227,24 @@ function configureTestPlanTools(server: McpServer, tokenProvider: () => Promise<
227227
* Helper function to convert steps text to XML format required
228228
*/
229229
function convertStepsToXml(steps: string): string {
230+
// Accepts steps in the format: '1. Step one|Expected result one\n2. Step two|Expected result two'
230231
const stepsLines = steps.split("\n").filter((line) => line.trim() !== "");
231232

232233
let xmlSteps = `<steps id="0" last="${stepsLines.length}">`;
233234

234235
for (let i = 0; i < stepsLines.length; i++) {
235236
const stepLine = stepsLines[i].trim();
236237
if (stepLine) {
237-
const stepMatch = stepLine.match(/^(\d+)\.\s*(.+)$/);
238-
const stepText = stepMatch ? stepMatch[2] : stepLine;
238+
// Split step and expected result by '|', fallback to default if not provided
239+
const [stepPart, expectedPart] = stepLine.split("|").map((s) => s.trim());
240+
const stepMatch = stepPart.match(/^(\d+)\.\s*(.+)$/);
241+
const stepText = stepMatch ? stepMatch[2] : stepPart;
242+
const expectedText = expectedPart || "Verify step completes successfully";
239243

240244
xmlSteps += `
241245
<step id="${i + 1}" type="ActionStep">
242246
<parameterizedString isformatted="true">${escapeXml(stepText)}</parameterizedString>
243-
<parameterizedString isformatted="true">Verify step completes successfully</parameterizedString>
247+
<parameterizedString isformatted="true">${escapeXml(expectedText)}</parameterizedString>
244248
</step>`;
245249
}
246250
}

test/src/tools/testplan.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,43 @@ describe("configureTestPlanTools", () => {
194194
);
195195
});
196196

197+
it("should create test case & expected result with proper parameters", async () => {
198+
configureTestPlanTools(server, tokenProvider, connectionProvider);
199+
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "testplan_create_test_case");
200+
if (!call) throw new Error("testplan_create_test_case tool not registered");
201+
const [, , , handler] = call;
202+
203+
(mockWitApi.createWorkItem as jest.Mock).mockResolvedValue({
204+
id: 1001,
205+
fields: {
206+
"System.Title": "New Test Case",
207+
"System.WorkItemType": "Test Case",
208+
},
209+
});
210+
211+
const params = {
212+
project: "proj1",
213+
title: "New Test Case",
214+
steps: "1. Test step 1 | Expected result 1\n2. Test step 2 | Expected result 2",
215+
};
216+
const result = await handler(params);
217+
218+
expect(mockWitApi.createWorkItem).toHaveBeenCalledWith({}, expect.any(Array), "proj1", "Test Case");
219+
expect(result.content[0].text).toBe(
220+
JSON.stringify(
221+
{
222+
id: 1001,
223+
fields: {
224+
"System.Title": "New Test Case",
225+
"System.WorkItemType": "Test Case",
226+
},
227+
},
228+
null,
229+
2
230+
)
231+
);
232+
});
233+
197234
it("should handle multiple steps in test case", async () => {
198235
configureTestPlanTools(server, tokenProvider, connectionProvider);
199236
const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "testplan_create_test_case");

0 commit comments

Comments
 (0)