Skip to content

Commit 764f5b7

Browse files
Merge pull request #186 from manoj-k04/feature/edit-test-case-functionality
feat: Add updateTestCase functionality to Test Management
2 parents 94eb783 + 00ef595 commit 764f5b7

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { apiClient } from "../../lib/apiClient.js";
2+
import { z } from "zod";
3+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4+
import { formatAxiosError } from "../../lib/error.js";
5+
import { projectIdentifierToId } from "./TCG-utils/api.js";
6+
import { BrowserStackConfig } from "../../lib/types.js";
7+
import { getTMBaseURL } from "../../lib/tm-base-url.js";
8+
import { getBrowserStackAuth } from "../../lib/get-auth.js";
9+
import logger from "../../logger.js";
10+
11+
export interface TestCaseUpdateRequest {
12+
project_identifier: string;
13+
test_case_identifier: string;
14+
name?: string;
15+
description?: string;
16+
preconditions?: string;
17+
test_case_steps?: Array<{
18+
step: string;
19+
result: string;
20+
}>;
21+
}
22+
23+
export const UpdateTestCaseSchema = z.object({
24+
project_identifier: z
25+
.string()
26+
.describe(
27+
"The ID of the BrowserStack project containing the test case to update.",
28+
),
29+
test_case_identifier: z
30+
.string()
31+
.describe(
32+
"The ID of the test case to update. This can be found using the listTestCases tool.",
33+
),
34+
name: z.string().optional().describe("Updated name of the test case."),
35+
description: z
36+
.string()
37+
.optional()
38+
.describe("Updated brief description of the test case."),
39+
preconditions: z
40+
.string()
41+
.optional()
42+
.describe("Updated preconditions for the test case."),
43+
test_case_steps: z
44+
.array(
45+
z.object({
46+
step: z.string().describe("The action to perform in this step."),
47+
result: z.string().describe("The expected result of this step."),
48+
}),
49+
)
50+
.optional()
51+
.describe("Updated list of test case steps with expected results."),
52+
});
53+
54+
/**
55+
* Updates an existing test case in BrowserStack Test Management.
56+
*/
57+
export async function updateTestCase(
58+
params: TestCaseUpdateRequest,
59+
config: BrowserStackConfig,
60+
): Promise<CallToolResult> {
61+
const authString = getBrowserStackAuth(config);
62+
const [username, password] = authString.split(":");
63+
64+
// Build the request body with only the fields to update
65+
const testCaseBody: any = {};
66+
67+
if (params.name !== undefined) {
68+
testCaseBody.name = params.name;
69+
}
70+
71+
if (params.description !== undefined) {
72+
testCaseBody.description = params.description;
73+
}
74+
75+
if (params.preconditions !== undefined) {
76+
testCaseBody.preconditions = params.preconditions;
77+
}
78+
79+
if (params.test_case_steps !== undefined) {
80+
testCaseBody.steps = params.test_case_steps;
81+
}
82+
83+
const body = { test_case: testCaseBody };
84+
85+
try {
86+
const tmBaseUrl = await getTMBaseURL(config);
87+
const response = await apiClient.patch({
88+
url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(
89+
params.project_identifier,
90+
)}/test-cases/${encodeURIComponent(params.test_case_identifier)}`,
91+
headers: {
92+
"Content-Type": "application/json",
93+
Authorization:
94+
"Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
95+
},
96+
body,
97+
});
98+
99+
const { data } = response.data;
100+
if (!data.success) {
101+
return {
102+
content: [
103+
{
104+
type: "text",
105+
text: `Failed to update test case: ${JSON.stringify(
106+
response.data,
107+
)}`,
108+
isError: true,
109+
},
110+
],
111+
isError: true,
112+
};
113+
}
114+
115+
const tc = data.test_case;
116+
117+
// Convert project identifier to project ID for dashboard URL
118+
const projectId = await projectIdentifierToId(
119+
params.project_identifier,
120+
config,
121+
);
122+
123+
return {
124+
content: [
125+
{
126+
type: "text",
127+
text: `Test case successfully updated:
128+
129+
**Test Case Details:**
130+
- **ID**: ${tc.identifier}
131+
- **Name**: ${tc.title}
132+
- **Description**: ${tc.description || "N/A"}
133+
- **Case Type**: ${tc.case_type}
134+
- **Priority**: ${tc.priority}
135+
- **Status**: ${tc.status}
136+
137+
**View on BrowserStack Dashboard:**
138+
https://test-management.browserstack.com/projects/${projectId}/folders/${tc.folder_id}/test-cases/${tc.identifier}
139+
140+
The test case has been updated successfully and is now available in your BrowserStack Test Management project.`,
141+
},
142+
],
143+
};
144+
} catch (err: any) {
145+
logger.error("Failed to update test case: %s", err);
146+
logger.error(
147+
"Error details:",
148+
JSON.stringify(err.response?.data || err.message),
149+
);
150+
151+
if (err.response?.status === 404) {
152+
return {
153+
content: [
154+
{
155+
type: "text",
156+
text: `Test case not found. Please verify the project_identifier ("${params.project_identifier}") and test_case_identifier ("${params.test_case_identifier}") are correct. Make sure to use actual values, not placeholders like "your_project_id".
157+
158+
Error details: ${JSON.stringify(err.response?.data || err.message)}`,
159+
isError: true,
160+
},
161+
],
162+
isError: true,
163+
};
164+
}
165+
166+
if (err.response?.status === 403) {
167+
return {
168+
content: [
169+
{
170+
type: "text",
171+
text: "Access denied. You don't have permission to update this test case.",
172+
isError: true,
173+
},
174+
],
175+
isError: true,
176+
};
177+
}
178+
179+
const errorMessage = formatAxiosError(err, "Failed to update test case");
180+
return {
181+
content: [
182+
{
183+
type: "text",
184+
text: `Failed to update test case: ${errorMessage}. Please verify your credentials and try again.`,
185+
isError: true,
186+
},
187+
],
188+
isError: true,
189+
};
190+
}
191+
}

src/tools/testmanagement.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import {
1414
CreateTestCaseSchema,
1515
} from "./testmanagement-utils/create-testcase.js";
1616

17+
import {
18+
updateTestCase as updateTestCaseAPI,
19+
TestCaseUpdateRequest,
20+
UpdateTestCaseSchema,
21+
} from "./testmanagement-utils/update-testcase.js";
22+
1723
import {
1824
listTestCases,
1925
ListTestCasesSchema,
@@ -130,6 +136,40 @@ export async function createTestCaseTool(
130136
}
131137
}
132138

139+
/**
140+
* Updates an existing test case in BrowserStack Test Management.
141+
*/
142+
export async function updateTestCaseTool(
143+
args: TestCaseUpdateRequest,
144+
config: BrowserStackConfig,
145+
server: McpServer,
146+
): Promise<CallToolResult> {
147+
try {
148+
trackMCP(
149+
"updateTestCase",
150+
server.server.getClientVersion()!,
151+
undefined,
152+
config,
153+
);
154+
return await updateTestCaseAPI(args, config);
155+
} catch (err) {
156+
logger.error("Failed to update test case: %s", err);
157+
trackMCP("updateTestCase", server.server.getClientVersion()!, err, config);
158+
return {
159+
content: [
160+
{
161+
type: "text",
162+
text: `Failed to update test case: ${
163+
err instanceof Error ? err.message : "Unknown error"
164+
}. Please open an issue on GitHub if the problem persists`,
165+
isError: true,
166+
},
167+
],
168+
isError: true,
169+
};
170+
}
171+
}
172+
133173
/**
134174
* Lists test cases in a project with optional filters (status, priority, custom fields, etc.)
135175
*/
@@ -426,6 +466,13 @@ export default function addTestManagementTools(
426466
(args) => createTestCaseTool(args, config, server),
427467
);
428468

469+
tools.updateTestCase = server.tool(
470+
"updateTestCase",
471+
"Use this tool to update an existing test case in BrowserStack Test Management. Allows editing test case details like name, description, steps, owner, priority, and more.",
472+
UpdateTestCaseSchema.shape,
473+
(args) => updateTestCaseTool(args, config, server),
474+
);
475+
429476
tools.listTestCases = server.tool(
430477
"listTestCases",
431478
"List test cases in a project with optional filters (status, priority, custom fields, etc.)",

0 commit comments

Comments
 (0)