Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 191 additions & 0 deletions src/tools/testmanagement-utils/update-testcase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { apiClient } from "../../lib/apiClient.js";
import { z } from "zod";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { formatAxiosError } from "../../lib/error.js";
import { projectIdentifierToId } from "./TCG-utils/api.js";
import { BrowserStackConfig } from "../../lib/types.js";
import { getTMBaseURL } from "../../lib/tm-base-url.js";
import { getBrowserStackAuth } from "../../lib/get-auth.js";
import logger from "../../logger.js";

export interface TestCaseUpdateRequest {
project_identifier: string;
test_case_identifier: string;
name?: string;
description?: string;
preconditions?: string;
test_case_steps?: Array<{
step: string;
result: string;
}>;
}

export const UpdateTestCaseSchema = z.object({
project_identifier: z
.string()
.describe(
"The ID of the BrowserStack project containing the test case to update.",
),
test_case_identifier: z
.string()
.describe(
"The ID of the test case to update. This can be found using the listTestCases tool.",
),
name: z.string().optional().describe("Updated name of the test case."),
description: z
.string()
.optional()
.describe("Updated brief description of the test case."),
preconditions: z
.string()
.optional()
.describe("Updated preconditions for the test case."),
test_case_steps: z
.array(
z.object({
step: z.string().describe("The action to perform in this step."),
result: z.string().describe("The expected result of this step."),
}),
)
.optional()
.describe("Updated list of test case steps with expected results."),
});

/**
* Updates an existing test case in BrowserStack Test Management.
*/
export async function updateTestCase(
params: TestCaseUpdateRequest,
config: BrowserStackConfig,
): Promise<CallToolResult> {
const authString = getBrowserStackAuth(config);
const [username, password] = authString.split(":");

// Build the request body with only the fields to update
const testCaseBody: any = {};

if (params.name !== undefined) {
testCaseBody.name = params.name;
}

if (params.description !== undefined) {
testCaseBody.description = params.description;
}

if (params.preconditions !== undefined) {
testCaseBody.preconditions = params.preconditions;
}

if (params.test_case_steps !== undefined) {
testCaseBody.steps = params.test_case_steps;
}

const body = { test_case: testCaseBody };

try {
const tmBaseUrl = await getTMBaseURL(config);
const response = await apiClient.patch({
url: `${tmBaseUrl}/api/v2/projects/${encodeURIComponent(
params.project_identifier,
)}/test-cases/${encodeURIComponent(params.test_case_identifier)}`,
headers: {
"Content-Type": "application/json",
Authorization:
"Basic " + Buffer.from(`${username}:${password}`).toString("base64"),
},
body,
});

const { data } = response.data;
if (!data.success) {
return {
content: [
{
type: "text",
text: `Failed to update test case: ${JSON.stringify(
response.data,
)}`,
isError: true,
},
],
isError: true,
};
}

const tc = data.test_case;

// Convert project identifier to project ID for dashboard URL
const projectId = await projectIdentifierToId(
params.project_identifier,
config,
);

return {
content: [
{
type: "text",
text: `Test case successfully updated:

**Test Case Details:**
- **ID**: ${tc.identifier}
- **Name**: ${tc.title}
- **Description**: ${tc.description || "N/A"}
- **Case Type**: ${tc.case_type}
- **Priority**: ${tc.priority}
- **Status**: ${tc.status}

**View on BrowserStack Dashboard:**
https://test-management.browserstack.com/projects/${projectId}/folders/${tc.folder_id}/test-cases/${tc.identifier}

The test case has been updated successfully and is now available in your BrowserStack Test Management project.`,
},
],
};
} catch (err: any) {
logger.error("Failed to update test case: %s", err);
logger.error(
"Error details:",
JSON.stringify(err.response?.data || err.message),
);

if (err.response?.status === 404) {
return {
content: [
{
type: "text",
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".

Error details: ${JSON.stringify(err.response?.data || err.message)}`,
isError: true,
},
],
isError: true,
};
}

if (err.response?.status === 403) {
return {
content: [
{
type: "text",
text: "Access denied. You don't have permission to update this test case.",
isError: true,
},
],
isError: true,
};
}

const errorMessage = formatAxiosError(err, "Failed to update test case");
return {
content: [
{
type: "text",
text: `Failed to update test case: ${errorMessage}. Please verify your credentials and try again.`,
isError: true,
},
],
isError: true,
};
}
}
47 changes: 47 additions & 0 deletions src/tools/testmanagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import {
CreateTestCaseSchema,
} from "./testmanagement-utils/create-testcase.js";

import {
updateTestCase as updateTestCaseAPI,
TestCaseUpdateRequest,
UpdateTestCaseSchema,
} from "./testmanagement-utils/update-testcase.js";

import {
listTestCases,
ListTestCasesSchema,
Expand Down Expand Up @@ -130,6 +136,40 @@ export async function createTestCaseTool(
}
}

/**
* Updates an existing test case in BrowserStack Test Management.
*/
export async function updateTestCaseTool(
args: TestCaseUpdateRequest,
config: BrowserStackConfig,
server: McpServer,
): Promise<CallToolResult> {
try {
trackMCP(
"updateTestCase",
server.server.getClientVersion()!,
undefined,
config,
);
return await updateTestCaseAPI(args, config);
} catch (err) {
logger.error("Failed to update test case: %s", err);
trackMCP("updateTestCase", server.server.getClientVersion()!, err, config);
return {
content: [
{
type: "text",
text: `Failed to update test case: ${
err instanceof Error ? err.message : "Unknown error"
}. Please open an issue on GitHub if the problem persists`,
isError: true,
},
],
isError: true,
};
}
}

/**
* Lists test cases in a project with optional filters (status, priority, custom fields, etc.)
*/
Expand Down Expand Up @@ -426,6 +466,13 @@ export default function addTestManagementTools(
(args) => createTestCaseTool(args, config, server),
);

tools.updateTestCase = server.tool(
"updateTestCase",
"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.",
UpdateTestCaseSchema.shape,
(args) => updateTestCaseTool(args, config, server),
);

tools.listTestCases = server.tool(
"listTestCases",
"List test cases in a project with optional filters (status, priority, custom fields, etc.)",
Expand Down