From fbdc4ea9cee0c62cc00ddc10717ab96ad93e810c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Sarsotti?= Date: Thu, 19 Feb 2026 18:15:45 -0300 Subject: [PATCH] fix: use test_case_steps instead of steps in updateTestCase The updateTestCase function was sending 'steps' in the request body, but the BrowserStack API expects 'test_case_steps'. This caused the steps to never be saved/updated. Changed line 80 in update-testcase.ts: - Before: testCaseBody.steps = params.test_case_steps; - After: testCaseBody.test_case_steps = params.test_case_steps; feat: add getTestCase tool to retrieve specific test cases by ID Added new tool to fetch a specific test case by ID from BrowserStack Test Management via GET /api/v2/projects/{project_id}/test-cases?id={test_case_id} Returns: - Complete test case details (title, type, priority, status) - Description and preconditions - Steps with expected results - Tags, owner, folder_id - Direct URL to BrowserStack - Full JSON data --- .../testmanagement-utils/get-testcase.ts | 123 ++++++++++++++++++ .../testmanagement-utils/update-testcase.ts | 2 +- src/tools/testmanagement.ts | 44 +++++++ 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/tools/testmanagement-utils/get-testcase.ts diff --git a/src/tools/testmanagement-utils/get-testcase.ts b/src/tools/testmanagement-utils/get-testcase.ts new file mode 100644 index 0000000..696471b --- /dev/null +++ b/src/tools/testmanagement-utils/get-testcase.ts @@ -0,0 +1,123 @@ +import { apiClient } from "../../lib/apiClient.js"; +import { z } from "zod"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { formatAxiosError } from "../../lib/error.js"; +import { getBrowserStackAuth } from "../../lib/get-auth.js"; +import { BrowserStackConfig } from "../../lib/types.js"; +import { getTMBaseURL } from "../../lib/tm-base-url.js"; + +/** + * Schema for getting a specific test case by ID. + */ +export const GetTestCaseSchema = z.object({ + project_identifier: z + .string() + .describe( + "Identifier of the project to fetch the test case from. This id starts with a PR- and is followed by a number.", + ), + test_case_id: z + .string() + .describe( + "Identifier of the test case to fetch (e.g., TC-16667 or 2). Multiple IDs can be provided separated by commas.", + ), +}); + +export type GetTestCaseArgs = z.infer; + +/** + * Calls BrowserStack Test Management to get a specific test case by ID. + */ +export async function getTestCase( + args: GetTestCaseArgs, + config: BrowserStackConfig, +): Promise { + try { + const tmBaseUrl = await getTMBaseURL(config); + const url = `${tmBaseUrl}/api/v2/projects/${encodeURIComponent( + args.project_identifier, + )}/test-cases?id=${encodeURIComponent(args.test_case_id)}`; + + const authString = getBrowserStackAuth(config); + const [username, password] = authString.split(":"); + const resp = await apiClient.get({ + url, + headers: { + Authorization: + "Basic " + Buffer.from(`${username}:${password}`).toString("base64"), + }, + }); + + const resp_data = resp.data; + if (!resp_data.success) { + return { + content: [ + { + type: "text", + text: `Failed to get test case: ${JSON.stringify(resp_data)}`, + }, + ], + isError: true, + }; + } + + const { test_cases } = resp_data; + + if (!test_cases || test_cases.length === 0) { + return { + content: [ + { + type: "text", + text: `No test case found with ID: ${args.test_case_id}`, + }, + ], + isError: true, + }; + } + + const tc = test_cases[0]; + + // Format steps if present + let stepsText = ""; + if (tc.steps && tc.steps.length > 0) { + stepsText = tc.steps + .map( + (step: any, index: number) => + `${index + 1}. ${step.step}\n Result: ${step.result}`, + ) + .join("\n\n"); + } + + const summary = `Test Case: ${tc.identifier} +Title: ${tc.title} +Type: ${tc.case_type} +Priority: ${tc.priority} +Status: ${tc.status} +Automation: ${tc.automation_status} +Owner: ${tc.owner || "Unassigned"} + +Description: +${tc.description || "N/A"} + +Preconditions: +${tc.preconditions || "N/A"} + +Steps:${stepsText ? "\n" + stepsText : " None"} + +URL: https://test-management.browserstack.com/projects/${args.project_identifier}/folders/${tc.folder_id}/test-cases/${tc.identifier}`; + + return { + content: [ + { + type: "text", + text: summary, + }, + { + type: "text", + text: JSON.stringify(tc, null, 2), + }, + ], + }; + } catch (err) { + return formatAxiosError(err, "Failed to get test case"); + } +} diff --git a/src/tools/testmanagement-utils/update-testcase.ts b/src/tools/testmanagement-utils/update-testcase.ts index fc875d5..0900329 100644 --- a/src/tools/testmanagement-utils/update-testcase.ts +++ b/src/tools/testmanagement-utils/update-testcase.ts @@ -77,7 +77,7 @@ export async function updateTestCase( } if (params.test_case_steps !== undefined) { - testCaseBody.steps = params.test_case_steps; + testCaseBody.test_case_steps = params.test_case_steps; } const body = { test_case: testCaseBody }; diff --git a/src/tools/testmanagement.ts b/src/tools/testmanagement.ts index f304496..e22cd9e 100644 --- a/src/tools/testmanagement.ts +++ b/src/tools/testmanagement.ts @@ -25,6 +25,11 @@ import { ListTestCasesSchema, } from "./testmanagement-utils/list-testcases.js"; +import { + getTestCase, + GetTestCaseSchema, +} from "./testmanagement-utils/get-testcase.js"; + import { CreateTestRunSchema, createTestRun, @@ -200,6 +205,38 @@ export async function listTestCasesTool( } } +/** + * Gets a specific test case by ID from BrowserStack Test Management. + */ +export async function getTestCaseTool( + args: z.infer, + config: BrowserStackConfig, + server: McpServer, +): Promise { + try { + trackMCP( + "getTestCase", + server.server.getClientVersion()!, + undefined, + config, + ); + return await getTestCase(args, config); + } catch (err) { + trackMCP("getTestCase", server.server.getClientVersion()!, err, config); + return { + content: [ + { + type: "text", + text: `Failed to get test case: ${ + err instanceof Error ? err.message : "Unknown error" + }. Please open an issue on GitHub if the problem persists`, + }, + ], + isError: true, + }; + } +} + /** * Creates a test run in BrowserStack Test Management. */ @@ -469,6 +506,13 @@ export default function addTestManagementTools( (args) => listTestCasesTool(args, config, server), ); + tools.getTestCase = server.tool( + "getTestCase", + "Get a specific test case by ID from BrowserStack Test Management. Returns full details including steps, description, preconditions, and metadata.", + GetTestCaseSchema.shape, + (args) => getTestCaseTool(args, config, server), + ); + tools.createTestRun = server.tool( "createTestRun", "Create a test run in BrowserStack Test Management.",